import { Light } from '@babylonjs/core/Lights/light.js' ; import { FresnelParameters } from '@babylonjs/core/Materials/fresnelParameters.js' ; import { BaseTexture } from '@babylonjs/core/Materials/Textures/baseTexture.js' ; import { Color3 , Color4 } from '@babylonjs/core/Maths/math.color.js' ; import { Quaternion , Vector3 } from '@babylonjs/core/Maths/math.vector.js' ; import { Observable } from '@babylonjs/core/Misc/observable.js' ; import { Nullable } from '@babylonjs/core/types.js' ; import { Control } from '@babylonjs/gui/2D/controls/control.js' ; export interface PropertyUpdate { value : any ; propertyName : string ; changeType : PropChangeType ; /**
     * When provided will dynamically target a property of 'babylonJSobject'.
     */ target ? : string ; } export type UpdatePayload = PropertyUpdate [ ] | null ; export interface PropsHandler < U > { getPropertyUpdates ( oldProps , newProps ) : UpdatePayload ; } export interface HasPropsHandlers < U > { getPropsHandlers ( ) : PropsHandler < U > [ ] ; addPropsHandler ( propHandler ) : void ; } type HandlerUpdateProcessResult < T > = { accepted : boolean ; } & PropertyUpdateProcessResult < T > ; export type PropertyUpdateProcessResult < T > = { processed : boolean ; value : Nullable < T > ; } ; /**
 * NOTE: the applyAnimatedValues from react-spring always has `oldProp` undefined, so we force set anything provided.
 * Would be more efficient to only handle the props passed in.
 */ export interface ICustomPropsHandler < T , U > { /**
     * Friendly name to identify the handler.
     */ readonly name : string ; /**
     * The type of prop (ie: Vector3, Color3) to register for
     */ readonly propChangeType : string ; todo readonly order ? : number ; param propChangeType accept ( newProp ) : boolean ; process ( oldProp , newProp ) : PropertyUpdateProcessResult < U > ; } export class CustomPropsHandler { private static _registeredPropsHandlers : Record < string , ICustomPropsHandler < any , any > [ ] > = { } ; param handler returns public static RegisterPropsHandler ( propsHandler ) { const propsChangeType = propsHandler . propChangeType ; if ( ! Array . isArray ( CustomPropsHandler . _registeredPropsHandlers [ propsChangeType ] ) ) { CustomPropsHandler . _registeredPropsHandlers [ propsChangeType ] = [ ] ; } const registeredHandlers = CustomPropsHandler . _registeredPropsHandlers [ propsChangeType ] ; const match = registeredHandlers . find ( ( h ) => h === propsHandler || h . name === propsHandler . name ) ; if ( match !== undefined ) { return match ; } registeredHandlers . push ( propsHandler ) ; return propsHandler ; } param propsHandler returns public static UnregisterPropsHandler ( propsHandlerToUnregister ) { const propsChangeType = propsHandlerToUnregister . propChangeType ; if ( ! Array . isArray ( CustomPropsHandler . _registeredPropsHandlers [ propsChangeType ] ) ) { console . warn ( `cannot find ${ propsHandlerToUnregister . name } to unregister.` ) ; return false ; } const registeredHandlers = CustomPropsHandler . _registeredPropsHandlers [ propsChangeType ] ; const index = registeredHandlers . indexOf ( propsHandlerToUnregister ) ; if ( index === - 1 ) { console . warn ( `cannot find ${ propsHandlerToUnregister . name } to unregister.` ) ; return false ; } CustomPropsHandler . _registeredPropsHandlers [ propsChangeType ] = registeredHandlers . slice ( index , 1 ) ; return true ; } private static NOT_ACCEPTED : Readonly < HandlerUpdateProcessResult < any > > = Object . freeze ( { accepted : false , processed : false , value : null , } ) ; private static ACCEPTED_NOT_PROCESSED : Readonly < HandlerUpdateProcessResult < any > > = Object . freeze ( { accepted : true , processed : false , value : null , } ) ; public static HandlePropsChange ( propsChangeType , oldProp , newProp ) { const registeredHandlers = CustomPropsHandler . _registeredPropsHandlers [ propsChangeType ] ; if ( registeredHandlers === undefined ) { return CustomPropsHandler . NOT_ACCEPTED ; } let accepted = false ; for ( const handler of registeredHandlers ) { if ( handler . accept ( newProp ) ) { accepted = true ; const propertyUpdatedProcessResult = handler . process ( oldProp , newProp ) ; if ( propertyUpdatedProcessResult . processed ) { return { accepted : true , processed : true , value : propertyUpdatedProcessResult . value , } ; } } } return accepted ? CustomPropsHandler . ACCEPTED_NOT_PROCESSED : CustomPropsHandler . NOT_ACCEPTED ; } } export enum PropChangeType { Color3 = 'Color3' , Color4 = 'Color4' , Control = 'Control' , FresnelParameters = 'FresnelParameters' , LambdaExpression = 'LambdaExpression' , Method = 'Method' , NumericArray = 'NumericArray' , Observable = 'Observable' , Primitive = 'Primitive' , Quaternion = 'Quaternion' , Texture = 'Texture' , Vector3 = 'Vector3' } /**
 * Encapsulates common error handling and handling of registered custom prop handlers.
 */ function propertyCheck < T > ( oldProp , newProp , propertyName , propChangeType , changedProps , templateMethod ) { try { const processedResult = CustomPropsHandler . HandlePropsChange ( propChangeType , oldProp , newProp ) ; if ( processedResult . accepted ) { if ( processedResult . processed ) { changedProps . push ( { propertyName , changeType : propChangeType , value : processedResult . value ! , } ) ; } return ; } templateMethod ( oldProp , newProp , changedProps ) ; } catch ( e ) { console . error ( `Unable to update '${ propertyName }' with ${ propChangeType }:` , newProp ) ; console . error ( e ) ; } } export const checkQuaternionDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < Quaternion > ( oldProp , newProp , propertyName , PropChangeType . Quaternion , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp && ( ! oldProp || ! newProp . equals ( oldProp ) ) ) { changedProps . push ( { propertyName , changeType : PropChangeType . Quaternion , value : newProp , } ) ; } } ) ; } ; export const checkVector3Diff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < Vector3 > ( oldProp , newProp , propertyName , PropChangeType . Vector3 , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp && ( ! oldProp || ! newProp . equals ( oldProp ) ) ) { changedProps . push ( { propertyName , changeType : PropChangeType . Vector3 , value : newProp , } ) ; } } ) ; } ; export const checkColor3Diff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < Color3 > ( oldProp , newProp , propertyName , PropChangeType . Color3 , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp && ( ! oldProp || ! newProp . equals ( oldProp ) ) ) { changedProps . push ( { propertyName , changeType : PropChangeType . Color3 , value : newProp , } ) ; } } ) ; } ; export const checkColor4Diff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < Color4 > ( oldProp , newProp , propertyName , PropChangeType . Color4 , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp && ( ! oldProp || oldProp . r !== newProp . r || oldProp . g !== newProp . g || oldProp . b !== newProp . b || oldProp . a !== newProp . a ) ) { changedProps . push ( { propertyName , changeType : PropChangeType . Color4 , value : newProp , } ) ; } } ) ; } ; export const checkFresnelParametersDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < FresnelParameters > ( oldProp , newProp , propertyName , PropChangeType . FresnelParameters , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp && ( ! oldProp || oldProp . bias !== newProp . bias || oldProp . power !== newProp . power || ! oldProp . leftColor || ! oldProp . leftColor . equals ( newProp . leftColor ) || ! oldProp . rightColor || ! oldProp . rightColor . equals ( newProp . rightColor ) || oldProp . isEnabled !== newProp . isEnabled ) ) { changedProps . push ( { propertyName , changeType : PropChangeType . FresnelParameters , value : newProp , } ) ; } } ) ; } ; export const checkLambdaDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < any > ( oldProp , newProp , propertyName , PropChangeType . LambdaExpression , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp !== oldProp ) { changedProps . push ( { propertyName , changeType : PropChangeType . LambdaExpression , value : newProp , } ) ; } } ) ; } ; export const checkControlDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < Control > ( oldProp , newProp , propertyName , PropChangeType . Control , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp && ! oldProp ) { changedProps . push ( { propertyName , changeType : PropChangeType . Control , value : newProp , } ) ; } } ) ; } ; export const checkObjectDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < Record < string , unknown > | Light > ( oldProp , newProp , propertyName , PropChangeType . Primitive , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp !== oldProp ) { changedProps . push ( { propertyName , changeType : PropChangeType . Primitive , value : newProp , } ) ; } } ) ; } ; export type PrimitiveType = string | number | undefined | null | boolean ; export const checkPrimitiveDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < PrimitiveType > ( oldProp , newProp , propertyName , PropChangeType . Primitive , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp !== oldProp ) { changedProps . push ( { propertyName , changeType : PropChangeType . Primitive , value : newProp , } ) ; } } ) ; } ; export const checkTextureDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < BaseTexture > ( oldProp , newProp , propertyName , PropChangeType . Texture , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp !== oldProp ) { changedProps . push ( { propertyName , changeType : PropChangeType . Texture , value : newProp , } ) ; } } ) ; } ; export const checkNumericArrayDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < number [ ] > ( oldProp , newProp , propertyName , PropChangeType . NumericArray , changedProps , ( oldProp , newProp , changedProps ) => { if ( newProp && ( ! oldProp || oldProp . length !== newProp . length ) ) { changedProps . push ( { propertyName , changeType : PropChangeType . NumericArray , value : newProp , } ) ; } } ) ; } ; export const checkObservableDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { propertyCheck < Observable < any > > ( oldProp , newProp , propertyName , PropChangeType . Observable , changedProps , ( oldProp , newProp , changedProps ) => { if ( oldProp !== newProp ) { changedProps . push ( { propertyName , changeType : PropChangeType . Observable , value : newProp , } ) ; } } ) ; } ; param oldProp param newProp param propertyName param changedProps export const checkMethodDiff = ( oldProp , newProp , propertyName , changedProps ) : void => { if ( oldProp !== newProp ) { changedProps . push ( { propertyName , changeType : PropChangeType . Method , value : newProp , } ) ; } } ;