diff --git a/packages/dev/core/src/Particles/Node/Blocks/Update/updateFlowMapBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/Update/updateFlowMapBlock.ts index d8238b79249..83e7fb34b83 100644 --- a/packages/dev/core/src/Particles/Node/Blocks/Update/updateFlowMapBlock.ts +++ b/packages/dev/core/src/Particles/Node/Blocks/Update/updateFlowMapBlock.ts @@ -1,14 +1,16 @@ +import type { Nullable } from "core/types"; import type { ThinParticleSystem } from "core/Particles/thinParticleSystem"; -import { RegisterClass } from "../../../../Misc/typeStore"; -import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; -import { NodeParticleBlock } from "../../nodeParticleBlock"; import type { NodeParticleConnectionPoint } from "../../nodeParticleBlockConnectionPoint"; import type { NodeParticleBuildState } from "../../nodeParticleBuildState"; import type { Particle } from "core/Particles/particle"; +import type { INodeParticleTextureData, ParticleTextureSourceBlock } from "../particleSourceTextureBlock"; + +import { RegisterClass } from "../../../../Misc/typeStore"; +import { NodeParticleBlockConnectionPointTypes } from "../../Enums/nodeParticleBlockConnectionPointTypes"; +import { NodeParticleBlock } from "../../nodeParticleBlock"; import { _ConnectAtTheEnd } from "core/Particles/Queue/executionQueue"; import { FlowMap } from "core/Particles/flowMap"; import { editableInPropertyPage, PropertyTypeForEdition } from "core/Decorators/nodeDecorator"; -import type { ParticleTextureSourceBlock } from "../particleSourceTextureBlock"; /** * Block used to update particle position based on a flow map @@ -72,7 +74,7 @@ export class UpdateFlowMapBlock extends NodeParticleBlock { let flowMap: FlowMap; // eslint-disable-next-line github/no-then - void flowMapTexture.extractTextureContentAsync().then((textureContent) => { + void flowMapTexture.extractTextureContentAsync().then((textureContent: Nullable) => { if (!textureContent) { return; } @@ -104,6 +106,10 @@ export class UpdateFlowMapBlock extends NodeParticleBlock { this.output._storedValue = system; } + /** + * Serializes the block into a json object + * @returns The serialized object + */ public override serialize(): any { const serializationObject = super.serialize(); @@ -112,6 +118,10 @@ export class UpdateFlowMapBlock extends NodeParticleBlock { return serializationObject; } + /** + * Deserializes the block from a json object + * @param serializationObject The object to deserialize from + */ public override _deserialize(serializationObject: any) { super._deserialize(serializationObject); diff --git a/packages/dev/core/src/Particles/Node/Blocks/Update/updateNoiseBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/Update/updateNoiseBlock.ts new file mode 100644 index 00000000000..fbe310cd624 --- /dev/null +++ b/packages/dev/core/src/Particles/Node/Blocks/Update/updateNoiseBlock.ts @@ -0,0 +1,137 @@ +import type { Nullable } from "core/types"; +import type { Particle } from "core/Particles/particle"; +import type { ThinParticleSystem } from "core/Particles/thinParticleSystem"; +import type { NodeParticleConnectionPoint } from "core/Particles/Node/nodeParticleBlockConnectionPoint"; +import type { NodeParticleBuildState } from "core/Particles/Node/nodeParticleBuildState"; +import type { INodeParticleTextureData, ParticleTextureSourceBlock } from "core/Particles/Node/Blocks/particleSourceTextureBlock"; + +import { TmpVectors, Vector3 } from "core/Maths/math.vector"; +import { RegisterClass } from "core/Misc/typeStore"; +import { NodeParticleBlock } from "core/Particles/Node/nodeParticleBlock"; +import { NodeParticleBlockConnectionPointTypes } from "core/Particles/Node/Enums/nodeParticleBlockConnectionPointTypes"; +import { _ConnectAtTheEnd } from "core/Particles/Queue/executionQueue"; + +/** + * Block used to update particle position based on a noise texture + */ +export class UpdateNoiseBlock extends NodeParticleBlock { + /** + * Create a new UpdateNoiseBlock + * @param name defines the block name + */ + public constructor(name: string) { + super(name); + + this.registerInput("particle", NodeParticleBlockConnectionPointTypes.Particle); + this.registerInput("noiseTexture", NodeParticleBlockConnectionPointTypes.Texture); + this.registerInput("strength", NodeParticleBlockConnectionPointTypes.Vector3, true, new Vector3(100, 100, 100)); + this.registerOutput("output", NodeParticleBlockConnectionPointTypes.Particle); + } + + /** + * Gets the particle component + */ + public get particle(): NodeParticleConnectionPoint { + return this._inputs[0]; + } + + /** + * Gets the noiseTexture input component + */ + public get noiseTexture(): NodeParticleConnectionPoint { + return this._inputs[1]; + } + + /** + * Gets the strength input component + */ + public get strength(): NodeParticleConnectionPoint { + return this._inputs[2]; + } + + /** + * Gets the output component + */ + public get output(): NodeParticleConnectionPoint { + return this._outputs[0]; + } + + /** + * Gets the current class name + * @returns the class name + */ + public override getClassName() { + return "UpdateNoiseBlock"; + } + + /** + * Builds the block + * @param state defines the current build state + */ + public override _build(state: NodeParticleBuildState) { + const system = this.particle.getConnectedValue(state) as ThinParticleSystem; + + const strength = this.strength.getConnectedValue(state) as Vector3; + if (!strength) { + return; + } + + const noiseTexture = this.noiseTexture.connectedPoint?.ownerBlock as ParticleTextureSourceBlock; + if (!noiseTexture) { + return; + } + + let noiseData: INodeParticleTextureData; + + // eslint-disable-next-line github/no-then + void noiseTexture.extractTextureContentAsync().then((textureContent: Nullable) => { + if (!textureContent) { + return; + } + noiseData = textureContent; + }); + + const processNoise = (particle: Particle) => { + if (!noiseData) { + // If the noise data is not ready, we skip processing + return; + } + + if (!particle._randomNoiseCoordinates1) { + particle._randomNoiseCoordinates1 = new Vector3(Math.random(), Math.random(), Math.random()); + } + + if (!particle._randomNoiseCoordinates2) { + particle._randomNoiseCoordinates2 = new Vector3(Math.random(), Math.random(), Math.random()); + } + + const fetchedColorR = system._fetchR(particle._randomNoiseCoordinates1.x, particle._randomNoiseCoordinates1.y, noiseData.width, noiseData.height, noiseData.data); + const fetchedColorG = system._fetchR(particle._randomNoiseCoordinates1.z, particle._randomNoiseCoordinates2.x, noiseData.width, noiseData.height, noiseData.data); + const fetchedColorB = system._fetchR(particle._randomNoiseCoordinates2.y, particle._randomNoiseCoordinates2.z, noiseData.width, noiseData.height, noiseData.data); + + const force = TmpVectors.Vector3[0]; + const scaledForce = TmpVectors.Vector3[1]; + + force.copyFromFloats((2 * fetchedColorR - 1) * strength.x, (2 * fetchedColorG - 1) * strength.y, (2 * fetchedColorB - 1) * strength.z); + + force.scaleToRef(system._tempScaledUpdateSpeed, scaledForce); + particle.direction.addInPlace(scaledForce); + }; + + const noiseProcessing = { + process: processNoise, + previousItem: null, + nextItem: null, + }; + + if (system._updateQueueStart) { + _ConnectAtTheEnd(noiseProcessing, system._updateQueueStart); + } else { + system._updateQueueStart = noiseProcessing; + } + + this.output._storedValue = system; + } +} + +RegisterClass("BABYLON.UpdateNoiseBlock", UpdateNoiseBlock); diff --git a/packages/dev/core/src/Particles/Node/Blocks/index.ts b/packages/dev/core/src/Particles/Node/Blocks/index.ts index f9c62ee0b77..d1cfdaf2586 100644 --- a/packages/dev/core/src/Particles/Node/Blocks/index.ts +++ b/packages/dev/core/src/Particles/Node/Blocks/index.ts @@ -17,6 +17,7 @@ export * from "./Update/basicSpriteUpdateBlock"; export * from "./Update/basicColorUpdateBlock"; export * from "./Update/updateSpriteCellIndexBlock"; export * from "./Update/updateFlowMapBlock"; +export * from "./Update/updateNoiseBlock"; export * from "./Update/updateAttractorBlock"; export * from "./Update/alignAngleBlock"; export * from "./Emitters/index"; diff --git a/packages/dev/core/src/Particles/Node/Blocks/particleSourceTextureBlock.ts b/packages/dev/core/src/Particles/Node/Blocks/particleSourceTextureBlock.ts index b6401fea590..be8f23c534d 100644 --- a/packages/dev/core/src/Particles/Node/Blocks/particleSourceTextureBlock.ts +++ b/packages/dev/core/src/Particles/Node/Blocks/particleSourceTextureBlock.ts @@ -7,6 +7,16 @@ import type { NodeParticleBuildState } from "../nodeParticleBuildState"; import type { Nullable } from "core/types"; import { TextureTools } from "core/Misc/textureTools"; import type { BaseTexture } from "../../../Materials/Textures/baseTexture"; +import type { ProceduralTexture } from "../../../Materials"; + +/** + * Interface used to define texture data + */ +export interface INodeParticleTextureData { + width: number; + height: number; + data: Uint8ClampedArray; +} /** * Block used to provide a texture for particles in a particle system @@ -15,11 +25,7 @@ export class ParticleTextureSourceBlock extends NodeParticleBlock { private _url: string = ""; private _textureDataUrl: string = ""; private _sourceTexture: Nullable = null; - private _cachedData: Nullable<{ - width: number; - height: number; - data: Uint8ClampedArray; - }> = null; + private _cachedData: Nullable = null; /** * Indicates if the texture data should be serialized as a base64 string. @@ -136,19 +142,37 @@ export class ParticleTextureSourceBlock extends NodeParticleBlock { return; } const size = texture.getSize(); - TextureTools.GetTextureDataAsync(texture, size.width, size.height) - // eslint-disable-next-line github/no-then - .then((data) => { - this._cachedData = { - width: size.width, - height: size.height, - data: new Uint8ClampedArray(data), - }; - texture.dispose(); - resolve(this._cachedData); - }) - // eslint-disable-next-line github/no-then - .catch(reject); + if (texture.getContent) { + const proceduralTexture = texture as ProceduralTexture; + proceduralTexture + .getContent() + // eslint-disable-next-line github/no-then + ?.then((data) => { + this._cachedData = { + width: size.width, + height: size.height, + data: data as Uint8ClampedArray, + }; + texture.dispose(); + resolve(this._cachedData); + }) + // eslint-disable-next-line github/no-then + .catch(reject); + } else { + TextureTools.GetTextureDataAsync(texture, size.width, size.height) + // eslint-disable-next-line github/no-then + .then((data) => { + this._cachedData = { + width: size.width, + height: size.height, + data: new Uint8ClampedArray(data), + }; + texture.dispose(); + resolve(this._cachedData); + }) + // eslint-disable-next-line github/no-then + .catch(reject); + } }); } diff --git a/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.helper.ts b/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.helper.ts index d175cf405fe..38573ebbcba 100644 --- a/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.helper.ts +++ b/packages/dev/core/src/Particles/Node/nodeParticleSystemSet.helper.ts @@ -1,6 +1,8 @@ import type { Nullable } from "core/types"; import type { Color4 } from "core/Maths/math.color"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { Texture } from "core/Materials/Textures/texture"; +import type { ProceduralTexture } from "core/Materials/Textures/Procedurals/proceduralTexture"; import type { Mesh } from "core/Meshes/mesh"; import type { ColorGradient, FactorGradient } from "core/Misc"; import type { ParticleSystem } from "core/Particles/particleSystem"; @@ -43,6 +45,7 @@ import { SphereShapeBlock } from "./Blocks/Emitters/sphereShapeBlock"; import { UpdateAngleBlock } from "./Blocks/Update/updateAngleBlock"; import { UpdateColorBlock } from "./Blocks/Update/updateColorBlock"; import { UpdateDirectionBlock } from "./Blocks/Update/updateDirectionBlock"; +import { UpdateNoiseBlock } from "./Blocks/Update/updateNoiseBlock"; import { UpdatePositionBlock } from "./Blocks/Update/updatePositionBlock"; import { UpdateSizeBlock } from "./Blocks/Update/updateSizeBlock"; @@ -420,10 +423,10 @@ function _EmitterShapeBlock(oldSystem: IParticleSystem): IShapeBlock { * @returns The output connection point after all updates have been applied */ function _UpdateParticleBlockGroup(inputParticle: NodeParticleConnectionPoint, oldSystem: ParticleSystem, context: RuntimeConversionContext): NodeParticleConnectionPoint { - let updateBlockGroupOutput: NodeParticleConnectionPoint = inputParticle; + let updatedParticle: NodeParticleConnectionPoint = inputParticle; - updateBlockGroupOutput = _UpdateParticleColorBlockGroup(updateBlockGroupOutput, oldSystem._colorGradients, context); - updateBlockGroupOutput = _UpdateParticleAngleBlockGroup(updateBlockGroupOutput, oldSystem, context); + updatedParticle = _UpdateParticleColorBlockGroup(updatedParticle, oldSystem._colorGradients, context); + updatedParticle = _UpdateParticleAngleBlockGroup(updatedParticle, oldSystem, context); if (oldSystem._velocityGradients && oldSystem._velocityGradients.length > 0) { context.scaledDirection = _UpdateParticleVelocityGradientBlockGroup(oldSystem._velocityGradients, context); @@ -433,21 +436,25 @@ function _UpdateParticleBlockGroup(inputParticle: NodeParticleConnectionPoint, o context.scaledDirection = _UpdateParticleDragGradientBlockGroup(oldSystem._dragGradients, context); } - updateBlockGroupOutput = _UpdateParticlePositionBlockGroup(updateBlockGroupOutput, oldSystem.isLocal, context); + updatedParticle = _UpdateParticlePositionBlockGroup(updatedParticle, oldSystem.isLocal, context); if (oldSystem._limitVelocityGradients && oldSystem._limitVelocityGradients.length > 0 && oldSystem.limitVelocityDamping !== 0) { - updateBlockGroupOutput = _UpdateParticleVelocityLimitGradientBlockGroup(updateBlockGroupOutput, oldSystem._limitVelocityGradients, oldSystem.limitVelocityDamping, context); + updatedParticle = _UpdateParticleVelocityLimitGradientBlockGroup(updatedParticle, oldSystem._limitVelocityGradients, oldSystem.limitVelocityDamping, context); + } + + if (oldSystem.noiseTexture && oldSystem.noiseStrength) { + updatedParticle = _UpdateParticleNoiseBlockGroup(updatedParticle, oldSystem.noiseTexture, oldSystem.noiseStrength); } if (oldSystem._sizeGradients && oldSystem._sizeGradients.length > 0) { - updateBlockGroupOutput = _UpdateParticleSizeGradientBlockGroup(updateBlockGroupOutput, oldSystem._sizeGradients, context); + updatedParticle = _UpdateParticleSizeGradientBlockGroup(updatedParticle, oldSystem._sizeGradients, context); } if (oldSystem.gravity.equalsToFloats(0, 0, 0) === false) { - updateBlockGroupOutput = _UpdateParticleGravityBlockGroup(updateBlockGroupOutput, oldSystem.gravity); + updatedParticle = _UpdateParticleGravityBlockGroup(updatedParticle, oldSystem.gravity); } - return updateBlockGroupOutput; + return updatedParticle; } /** @@ -602,6 +609,21 @@ function _UpdateParticleVelocityLimitGradientBlockGroup( return updateDirection.output; } +/** + * Creates the group of blocks that represent the particle noise update + * @param inputParticle The particle to update + * @param noiseTexture The noise texture + * @param noiseStrength The strength of the noise + * @returns The output of the group of blocks that represent the particle noise update + */ +function _UpdateParticleNoiseBlockGroup(inputParticle: NodeParticleConnectionPoint, noiseTexture: ProceduralTexture, noiseStrength: Vector3): NodeParticleConnectionPoint { + const noiseUpdate = new UpdateNoiseBlock("Noise Update"); + inputParticle.connectTo(noiseUpdate.particle); + _CreateTextureBlock(noiseTexture).connectTo(noiseUpdate.noiseTexture); + _CreateAndConnectInput("Noise Strength", noiseStrength, noiseUpdate.strength); + return noiseUpdate.output; +} + /** * Creates the group of blocks that represent the particle drag update * @param dragGradients The drag gradients @@ -789,7 +811,7 @@ function _SystemBlockGroup(oldSystem: ParticleSystem, context: RuntimeConversion newSystem.disposeOnStop = oldSystem.disposeOnStop; _SystemEmitRateValue(oldSystem, newSystem, context); - _SystemTextureBlock(oldSystem).connectTo(newSystem.texture); + _CreateTextureBlock(oldSystem.particleTexture).connectTo(newSystem.texture); _SystemTargetStopDuration(oldSystem, newSystem, context); return newSystem; @@ -812,19 +834,6 @@ function _SystemEmitRateValue(oldSystem: ParticleSystem, newSystem: SystemBlock, } } -function _SystemTextureBlock(oldSystem: ParticleSystem): NodeParticleConnectionPoint { - // Texture - const textureBlock = new ParticleTextureSourceBlock("Texture"); - const url = (oldSystem.particleTexture as Texture).url || ""; - if (url) { - textureBlock.url = url; - } else { - textureBlock.sourceTexture = oldSystem.particleTexture; - } - - return textureBlock.texture; -} - function _SystemTargetStopDuration(oldSystem: ParticleSystem, newSystem: SystemBlock, context: RuntimeConversionContext): void { // If something else uses the target stop duration (like a gradient), // then the block is already created and stored in the context @@ -1026,3 +1035,16 @@ function _CreateGradientValueBlockGroup( return gradientValueBlock.output; } + +function _CreateTextureBlock(texture: Nullable): NodeParticleConnectionPoint { + // Texture + const textureBlock = new ParticleTextureSourceBlock("Texture"); + const url = (texture as Texture).url || ""; + if (url) { + textureBlock.url = url; + } else { + textureBlock.sourceTexture = texture; + } + + return textureBlock.texture; +} diff --git a/packages/dev/core/src/Particles/particle.ts b/packages/dev/core/src/Particles/particle.ts index e05bf4fbf05..cf8e6e0499e 100644 --- a/packages/dev/core/src/Particles/particle.ts +++ b/packages/dev/core/src/Particles/particle.ts @@ -153,9 +153,9 @@ export class Particle { public _currentDrag2 = 0; /** @internal */ - public _randomNoiseCoordinates1: Vector3; + public _randomNoiseCoordinates1: Nullable; /** @internal */ - public _randomNoiseCoordinates2: Vector3; + public _randomNoiseCoordinates2: Nullable; /** @internal */ public _localPosition?: Vector3; @@ -255,6 +255,8 @@ export class Particle { this._currentDragGradient = null; this.cellIndex = this.particleSystem.startSpriteCellID; this._randomCellOffset = undefined; + this._randomNoiseCoordinates1 = null; + this._randomNoiseCoordinates2 = null; } /** @@ -337,8 +339,8 @@ export class Particle { other.remapData = new Vector4(0, 0, 0, 0); } } - if (this._randomNoiseCoordinates1) { - if (other._randomNoiseCoordinates1) { + if (this._randomNoiseCoordinates1 && this._randomNoiseCoordinates2) { + if (other._randomNoiseCoordinates1 && other._randomNoiseCoordinates2) { other._randomNoiseCoordinates1.copyFrom(this._randomNoiseCoordinates1); other._randomNoiseCoordinates2.copyFrom(this._randomNoiseCoordinates2); } else { diff --git a/packages/dev/core/src/Particles/thinParticleSystem.function.ts b/packages/dev/core/src/Particles/thinParticleSystem.function.ts index c2701729c3c..0a61e8c9790 100644 --- a/packages/dev/core/src/Particles/thinParticleSystem.function.ts +++ b/packages/dev/core/src/Particles/thinParticleSystem.function.ts @@ -216,7 +216,7 @@ export function _ProcessDragGradients(particle: Particle, system: ThinParticleSy /** @internal */ export function _CreateNoiseData(particle: Particle, _system: ThinParticleSystem) { - if (particle._randomNoiseCoordinates1) { + if (particle._randomNoiseCoordinates1 && particle._randomNoiseCoordinates2) { particle._randomNoiseCoordinates1.copyFromFloats(Math.random(), Math.random(), Math.random()); particle._randomNoiseCoordinates2.copyFromFloats(Math.random(), Math.random(), Math.random()); } else { @@ -230,7 +230,7 @@ export function _ProcessNoise(particle: Particle, system: ThinParticleSystem) { const noiseTextureData = system._noiseTextureData; const noiseTextureSize = system._noiseTextureSize; - if (noiseTextureData && noiseTextureSize && particle._randomNoiseCoordinates1) { + if (noiseTextureData && noiseTextureSize && particle._randomNoiseCoordinates1 && particle._randomNoiseCoordinates2) { const fetchedColorR = system._fetchR( particle._randomNoiseCoordinates1.x, particle._randomNoiseCoordinates1.y, diff --git a/packages/dev/core/src/Particles/thinParticleSystem.ts b/packages/dev/core/src/Particles/thinParticleSystem.ts index fbac1fb3603..25e50775e78 100644 --- a/packages/dev/core/src/Particles/thinParticleSystem.ts +++ b/packages/dev/core/src/Particles/thinParticleSystem.ts @@ -1398,7 +1398,7 @@ export class ThinParticleSystem extends BaseParticleSystem implements IDisposabl } /** @internal */ - public _fetchR(u: number, v: number, width: number, height: number, pixels: Uint8Array): number { + public _fetchR(u: number, v: number, width: number, height: number, pixels: Uint8Array | Uint8ClampedArray): number { u = Math.abs(u) * 0.5 + 0.5; v = Math.abs(v) * 0.5 + 0.5; diff --git a/packages/tools/nodeParticleEditor/src/blockTools.ts b/packages/tools/nodeParticleEditor/src/blockTools.ts index 89035bc04c8..42351ea9892 100644 --- a/packages/tools/nodeParticleEditor/src/blockTools.ts +++ b/packages/tools/nodeParticleEditor/src/blockTools.ts @@ -28,6 +28,7 @@ import { SetupSpriteSheetBlock } from "core/Particles/Node/Blocks/Emitters/setup import { BasicSpriteUpdateBlock } from "core/Particles/Node/Blocks/Update/basicSpriteUpdateBlock"; import { UpdateSpriteCellIndexBlock } from "core/Particles/Node/Blocks/Update/updateSpriteCellIndexBlock"; import { UpdateFlowMapBlock } from "core/Particles/Node/Blocks/Update/updateFlowMapBlock"; +import { UpdateNoiseBlock } from "core/Particles/Node/Blocks/Update/updateNoiseBlock"; import { ParticleConditionBlock, ParticleConditionBlockTests } from "core/Particles/Node/Blocks/Conditions/particleConditionBlock"; import { CreateParticleBlock } from "core/Particles/Node/Blocks/Emitters/createParticleBlock"; import { BoxShapeBlock } from "core/Particles/Node/Blocks/Emitters/boxShapeBlock"; @@ -150,6 +151,8 @@ export class BlockTools { return new UpdateAgeBlock("Update age"); case "UpdateFlowMapBlock": return new UpdateFlowMapBlock("Update flow map"); + case "UpdateNoiseBlock": + return new UpdateNoiseBlock("Update noise"); case "UpdateAttractorBlock": return new UpdateAttractorBlock("Update attractor"); case "SystemBlock": diff --git a/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx b/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx index 0d888b14a03..c730470774b 100644 --- a/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx +++ b/packages/tools/nodeParticleEditor/src/components/nodeList/nodeListComponent.tsx @@ -109,6 +109,7 @@ export class NodeListComponent extends React.Component { DisplayLedger.RegisteredControls["BasicPositionUpdateBlock"] = UpdateDisplayManager; DisplayLedger.RegisteredControls["BasicColorUpdateBlock"] = UpdateDisplayManager; DisplayLedger.RegisteredControls["UpdateFlowMapBlock"] = UpdateDisplayManager; + DisplayLedger.RegisteredControls["UpdateNoiseBlock"] = UpdateDisplayManager; DisplayLedger.RegisteredControls["SystemBlock"] = SystemDisplayManager; DisplayLedger.RegisteredControls["ParticleDebugBlock"] = DebugDisplayManager; DisplayLedger.RegisteredControls["ParticleElbowBlock"] = ElbowDisplayManager;