MCP 3D Printer Server
by DMontgomery40
Verified
import DataMap from '../DataMap.js';
import ChainMap from '../ChainMap.js';
import NodeBuilderState from './NodeBuilderState.js';
import { cubeMapNode } from '../../../nodes/utils/CubeMapNode.js';
import { NodeFrame } from '../../../nodes/Nodes.js';
import { objectGroup, renderGroup, frameGroup, cubeTexture, texture, fog, rangeFogFactor, densityFogFactor, reference, pmremTexture, screenUV } from '../../../nodes/TSL.js';
import { CubeUVReflectionMapping, EquirectangularReflectionMapping, EquirectangularRefractionMapping } from '../../../constants.js';
import { hashArray } from '../../../nodes/core/NodeUtils.js';
const _outputNodeMap = new WeakMap();
const _chainKeys = [];
const _cacheKeyValues = [];
/**
* This renderer module manages node-related objects and is the
* primary interface between the renderer and the node system.
*
* @private
* @augments DataMap
*/
class Nodes extends DataMap {
/**
* Constructs a new nodes management component.
*
* @param {Renderer} renderer - The renderer.
* @param {Backend} backend - The renderer's backend.
*/
constructor( renderer, backend ) {
super();
/**
* The renderer.
*
* @type {Renderer}
*/
this.renderer = renderer;
/**
* The renderer's backend.
*
* @type {Backend}
*/
this.backend = backend;
/**
* The node frame.
*
* @type {Renderer}
*/
this.nodeFrame = new NodeFrame();
/**
* A cache for managing node builder states.
*
* @type {Map<Number,NodeBuilderState>}
*/
this.nodeBuilderCache = new Map();
/**
* A cache for managing data cache key data.
*
* @type {ChainMap}
*/
this.callHashCache = new ChainMap();
/**
* A cache for managing node uniforms group data.
*
* @type {ChainMap}
*/
this.groupsData = new ChainMap();
/**
* A cache for managing node objects of
* scene properties like fog or environments.
*
* @type {Object<String,WeakMap>}
*/
this.cacheLib = {};
}
/**
* Returns `true` if the given node uniforms group must be updated or not.
*
* @param {NodeUniformsGroup} nodeUniformsGroup - The node uniforms group.
* @return {Boolean} Whether the node uniforms group requires an update or not.
*/
updateGroup( nodeUniformsGroup ) {
const groupNode = nodeUniformsGroup.groupNode;
const name = groupNode.name;
// objectGroup is always updated
if ( name === objectGroup.name ) return true;
// renderGroup is updated once per render/compute call
if ( name === renderGroup.name ) {
const uniformsGroupData = this.get( nodeUniformsGroup );
const renderId = this.nodeFrame.renderId;
if ( uniformsGroupData.renderId !== renderId ) {
uniformsGroupData.renderId = renderId;
return true;
}
return false;
}
// frameGroup is updated once per frame
if ( name === frameGroup.name ) {
const uniformsGroupData = this.get( nodeUniformsGroup );
const frameId = this.nodeFrame.frameId;
if ( uniformsGroupData.frameId !== frameId ) {
uniformsGroupData.frameId = frameId;
return true;
}
return false;
}
// other groups are updated just when groupNode.needsUpdate is true
_chainKeys[ 0 ] = groupNode;
_chainKeys[ 1 ] = nodeUniformsGroup;
let groupData = this.groupsData.get( _chainKeys );
if ( groupData === undefined ) this.groupsData.set( _chainKeys, groupData = {} );
_chainKeys.length = 0;
if ( groupData.version !== groupNode.version ) {
groupData.version = groupNode.version;
return true;
}
return false;
}
/**
* Returns the cache key for the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @return {Number} The cache key.
*/
getForRenderCacheKey( renderObject ) {
return renderObject.initialCacheKey;
}
/**
* Returns a node builder state for the given render object.
*
* @param {RenderObject} renderObject - The render object.
* @return {NodeBuilderState} The node builder state.
*/
getForRender( renderObject ) {
const renderObjectData = this.get( renderObject );
let nodeBuilderState = renderObjectData.nodeBuilderState;
if ( nodeBuilderState === undefined ) {
const { nodeBuilderCache } = this;
const cacheKey = this.getForRenderCacheKey( renderObject );
nodeBuilderState = nodeBuilderCache.get( cacheKey );
if ( nodeBuilderState === undefined ) {
const nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer );
nodeBuilder.scene = renderObject.scene;
nodeBuilder.material = renderObject.material;
nodeBuilder.camera = renderObject.camera;
nodeBuilder.context.material = renderObject.material;
nodeBuilder.lightsNode = renderObject.lightsNode;
nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );
nodeBuilder.fogNode = this.getFogNode( renderObject.scene );
nodeBuilder.clippingContext = renderObject.clippingContext;
nodeBuilder.build();
nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
nodeBuilderCache.set( cacheKey, nodeBuilderState );
}
nodeBuilderState.usedTimes ++;
renderObjectData.nodeBuilderState = nodeBuilderState;
}
return nodeBuilderState;
}
/**
* Deletes the given object from the internal data map
*
* @param {Any} object - The object to delete.
* @return {Object?} The deleted dictionary.
*/
delete( object ) {
if ( object.isRenderObject ) {
const nodeBuilderState = this.get( object ).nodeBuilderState;
nodeBuilderState.usedTimes --;
if ( nodeBuilderState.usedTimes === 0 ) {
this.nodeBuilderCache.delete( this.getForRenderCacheKey( object ) );
}
}
return super.delete( object );
}
/**
* Returns a node builder state for the given compute node.
*
* @param {Node} computeNode - The compute node.
* @return {NodeBuilderState} The node builder state.
*/
getForCompute( computeNode ) {
const computeData = this.get( computeNode );
let nodeBuilderState = computeData.nodeBuilderState;
if ( nodeBuilderState === undefined ) {
const nodeBuilder = this.backend.createNodeBuilder( computeNode, this.renderer );
nodeBuilder.build();
nodeBuilderState = this._createNodeBuilderState( nodeBuilder );
computeData.nodeBuilderState = nodeBuilderState;
}
return nodeBuilderState;
}
/**
* Creates a node builder state for the given node builder.
*
* @private
* @param {NodeBuilder} nodeBuilder - The node builder.
* @return {NodeBuilderState} The node builder state.
*/
_createNodeBuilderState( nodeBuilder ) {
return new NodeBuilderState(
nodeBuilder.vertexShader,
nodeBuilder.fragmentShader,
nodeBuilder.computeShader,
nodeBuilder.getAttributesArray(),
nodeBuilder.getBindings(),
nodeBuilder.updateNodes,
nodeBuilder.updateBeforeNodes,
nodeBuilder.updateAfterNodes,
nodeBuilder.observer,
nodeBuilder.transforms
);
}
/**
* Returns an environment node for the current configured
* scene environment.
*
* @param {Scene} scene - The scene.
* @return {Node} A node representing the current scene environment.
*/
getEnvironmentNode( scene ) {
this.updateEnvironment( scene );
let environmentNode = null;
if ( scene.environmentNode && scene.environmentNode.isNode ) {
environmentNode = scene.environmentNode;
} else {
const sceneData = this.get( scene );
if ( sceneData.environmentNode ) {
environmentNode = sceneData.environmentNode;
}
}
return environmentNode;
}
/**
* Returns a background node for the current configured
* scene background.
*
* @param {Scene} scene - The scene.
* @return {Node} A node representing the current scene background.
*/
getBackgroundNode( scene ) {
this.updateBackground( scene );
let backgroundNode = null;
if ( scene.backgroundNode && scene.backgroundNode.isNode ) {
backgroundNode = scene.backgroundNode;
} else {
const sceneData = this.get( scene );
if ( sceneData.backgroundNode ) {
backgroundNode = sceneData.backgroundNode;
}
}
return backgroundNode;
}
/**
* Returns a fog node for the current configured scene fog.
*
* @param {Scene} scene - The scene.
* @return {Node} A node representing the current scene fog.
*/
getFogNode( scene ) {
this.updateFog( scene );
return scene.fogNode || this.get( scene ).fogNode || null;
}
/**
* Returns a cache key for the given scene and lights node.
* This key is used by `RenderObject` as a part of the dynamic
* cache key (a key that must be checked every time the render
* objects is drawn).
*
* @param {Scene} scene - The scene.
* @param {LightsNode} lightsNode - The lights node.
* @return {Number} The cache key.
*/
getCacheKey( scene, lightsNode ) {
_chainKeys[ 0 ] = scene;
_chainKeys[ 1 ] = lightsNode;
const callId = this.renderer.info.calls;
const cacheKeyData = this.callHashCache.get( _chainKeys ) || {};
if ( cacheKeyData.callId !== callId ) {
const environmentNode = this.getEnvironmentNode( scene );
const fogNode = this.getFogNode( scene );
if ( lightsNode ) _cacheKeyValues.push( lightsNode.getCacheKey( true ) );
if ( environmentNode ) _cacheKeyValues.push( environmentNode.getCacheKey() );
if ( fogNode ) _cacheKeyValues.push( fogNode.getCacheKey() );
_cacheKeyValues.push( this.renderer.shadowMap.enabled ? 1 : 0 );
cacheKeyData.callId = callId;
cacheKeyData.cacheKey = hashArray( _cacheKeyValues );
this.callHashCache.set( _chainKeys, cacheKeyData );
_cacheKeyValues.length = 0;
}
_chainKeys.length = 0;
return cacheKeyData.cacheKey;
}
/**
* A boolean that indicates whether tone mapping should be enabled
* or not.
*
* @type {Boolean}
*/
get isToneMappingState() {
return this.renderer.getRenderTarget() ? false : true;
}
/**
* If a scene background is configured, this method makes sure to
* represent the background with a corresponding node-based implementation.
*
* @param {Scene} scene - The scene.
*/
updateBackground( scene ) {
const sceneData = this.get( scene );
const background = scene.background;
if ( background ) {
const forceUpdate = ( scene.backgroundBlurriness === 0 && sceneData.backgroundBlurriness > 0 ) || ( scene.backgroundBlurriness > 0 && sceneData.backgroundBlurriness === 0 );
if ( sceneData.background !== background || forceUpdate ) {
const backgroundNode = this.getCacheNode( 'background', background, () => {
if ( background.isCubeTexture === true || ( background.mapping === EquirectangularReflectionMapping || background.mapping === EquirectangularRefractionMapping || background.mapping === CubeUVReflectionMapping ) ) {
if ( scene.backgroundBlurriness > 0 || background.mapping === CubeUVReflectionMapping ) {
return pmremTexture( background );
} else {
let envMap;
if ( background.isCubeTexture === true ) {
envMap = cubeTexture( background );
} else {
envMap = texture( background );
}
return cubeMapNode( envMap );
}
} else if ( background.isTexture === true ) {
return texture( background, screenUV.flipY() ).setUpdateMatrix( true );
} else if ( background.isColor !== true ) {
console.error( 'WebGPUNodes: Unsupported background configuration.', background );
}
}, forceUpdate );
sceneData.backgroundNode = backgroundNode;
sceneData.background = background;
sceneData.backgroundBlurriness = scene.backgroundBlurriness;
}
} else if ( sceneData.backgroundNode ) {
delete sceneData.backgroundNode;
delete sceneData.background;
}
}
/**
* This method is part of the caching of nodes which are used to represents the
* scene's background, fog or environment.
*
* @param {String} type - The type of object to cache.
* @param {Object} object - The object.
* @param {Function} callback - A callback that produces a node representation for the given object.
* @param {Boolean} [forceUpdate=false] - Whether an update should be enforced or not.
* @return {Node} The node representation.
*/
getCacheNode( type, object, callback, forceUpdate = false ) {
const nodeCache = this.cacheLib[ type ] || ( this.cacheLib[ type ] = new WeakMap() );
let node = nodeCache.get( object );
if ( node === undefined || forceUpdate ) {
node = callback();
nodeCache.set( object, node );
}
return node;
}
/**
* If a scene fog is configured, this method makes sure to
* represent the fog with a corresponding node-based implementation.
*
* @param {Scene} scene - The scene.
*/
updateFog( scene ) {
const sceneData = this.get( scene );
const sceneFog = scene.fog;
if ( sceneFog ) {
if ( sceneData.fog !== sceneFog ) {
const fogNode = this.getCacheNode( 'fog', sceneFog, () => {
if ( sceneFog.isFogExp2 ) {
const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup );
const density = reference( 'density', 'float', sceneFog ).setGroup( renderGroup );
return fog( color, densityFogFactor( density ) );
} else if ( sceneFog.isFog ) {
const color = reference( 'color', 'color', sceneFog ).setGroup( renderGroup );
const near = reference( 'near', 'float', sceneFog ).setGroup( renderGroup );
const far = reference( 'far', 'float', sceneFog ).setGroup( renderGroup );
return fog( color, rangeFogFactor( near, far ) );
} else {
console.error( 'THREE.Renderer: Unsupported fog configuration.', sceneFog );
}
} );
sceneData.fogNode = fogNode;
sceneData.fog = sceneFog;
}
} else {
delete sceneData.fogNode;
delete sceneData.fog;
}
}
/**
* If a scene environment is configured, this method makes sure to
* represent the environment with a corresponding node-based implementation.
*
* @param {Scene} scene - The scene.
*/
updateEnvironment( scene ) {
const sceneData = this.get( scene );
const environment = scene.environment;
if ( environment ) {
if ( sceneData.environment !== environment ) {
const environmentNode = this.getCacheNode( 'environment', environment, () => {
if ( environment.isCubeTexture === true ) {
return cubeTexture( environment );
} else if ( environment.isTexture === true ) {
return texture( environment );
} else {
console.error( 'Nodes: Unsupported environment configuration.', environment );
}
} );
sceneData.environmentNode = environmentNode;
sceneData.environment = environment;
}
} else if ( sceneData.environmentNode ) {
delete sceneData.environmentNode;
delete sceneData.environment;
}
}
getNodeFrame( renderer = this.renderer, scene = null, object = null, camera = null, material = null ) {
const nodeFrame = this.nodeFrame;
nodeFrame.renderer = renderer;
nodeFrame.scene = scene;
nodeFrame.object = object;
nodeFrame.camera = camera;
nodeFrame.material = material;
return nodeFrame;
}
getNodeFrameForRender( renderObject ) {
return this.getNodeFrame( renderObject.renderer, renderObject.scene, renderObject.object, renderObject.camera, renderObject.material );
}
/**
* Returns the current output cache key.
*
* @return {String} The output cache key.
*/
getOutputCacheKey() {
const renderer = this.renderer;
return renderer.toneMapping + ',' + renderer.currentColorSpace;
}
/**
* Checks if the output configuration (tone mapping and color space) for
* the given target has changed.
*
* @param {Texture} outputTarget - The output target.
* @return {Boolean} Whether the output configuration has changed or not.
*/
hasOutputChange( outputTarget ) {
const cacheKey = _outputNodeMap.get( outputTarget );
return cacheKey !== this.getOutputCacheKey();
}
/**
* Returns a node that represents the output configuration (tone mapping and
* color space) for the current target.
*
* @param {Texture} outputTarget - The output target.
* @return {Node} The output node.
*/
getOutputNode( outputTarget ) {
const renderer = this.renderer;
const cacheKey = this.getOutputCacheKey();
const output = texture( outputTarget, screenUV ).renderOutput( renderer.toneMapping, renderer.currentColorSpace );
_outputNodeMap.set( outputTarget, cacheKey );
return output;
}
/**
* Triggers the call of `updateBefore()` methods
* for all nodes of the given render object.
*
* @param {RenderObject} renderObject - The render object.
*/
updateBefore( renderObject ) {
const nodeBuilder = renderObject.getNodeBuilderState();
for ( const node of nodeBuilder.updateBeforeNodes ) {
// update frame state for each node
this.getNodeFrameForRender( renderObject ).updateBeforeNode( node );
}
}
/**
* Triggers the call of `updateAfter()` methods
* for all nodes of the given render object.
*
* @param {RenderObject} renderObject - The render object.
*/
updateAfter( renderObject ) {
const nodeBuilder = renderObject.getNodeBuilderState();
for ( const node of nodeBuilder.updateAfterNodes ) {
// update frame state for each node
this.getNodeFrameForRender( renderObject ).updateAfterNode( node );
}
}
/**
* Triggers the call of `update()` methods
* for all nodes of the given compute node.
*
* @param {Node} computeNode - The compute node.
*/
updateForCompute( computeNode ) {
const nodeFrame = this.getNodeFrame();
const nodeBuilder = this.getForCompute( computeNode );
for ( const node of nodeBuilder.updateNodes ) {
nodeFrame.updateNode( node );
}
}
/**
* Triggers the call of `update()` methods
* for all nodes of the given compute node.
*
* @param {RenderObject} renderObject - The render object.
*/
updateForRender( renderObject ) {
const nodeFrame = this.getNodeFrameForRender( renderObject );
const nodeBuilder = renderObject.getNodeBuilderState();
for ( const node of nodeBuilder.updateNodes ) {
nodeFrame.updateNode( node );
}
}
/**
* Returns `true` if the given render object requires a refresh.
*
* @param {RenderObject} renderObject - The render object.
* @return {Boolean} Whether the given render object requires a refresh or not.
*/
needsRefresh( renderObject ) {
const nodeFrame = this.getNodeFrameForRender( renderObject );
const monitor = renderObject.getMonitor();
return monitor.needsRefresh( renderObject, nodeFrame );
}
/**
* Frees the internal resources.
*/
dispose() {
super.dispose();
this.nodeFrame = new NodeFrame();
this.nodeBuilderCache = new Map();
this.cacheLib = {};
}
}
export default Nodes;