MCP 3D Printer Server

by DMontgomery40
Verified
import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3, TempNode, QuadMesh, NodeMaterial, RendererUtils } from 'three/webgpu'; import { reference, logarithmicDepthToViewZ, viewZToPerspectiveDepth, getNormalFromDepth, getScreenPosition, getViewPosition, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, div, mix, sqrt, sub, acos, clamp } from 'three/tsl'; /** @module GTAONode **/ const _quadMesh = /*@__PURE__*/ new QuadMesh(); const _size = /*@__PURE__*/ new Vector2(); let _rendererState; /** * Post processing node for applying Ground Truth Ambient Occlusion (GTAO) to a scene. * ```js * const postProcessing = new THREE.PostProcessing( renderer ); * * const scenePass = pass( scene, camera ); * scenePass.setMRT( mrt( { * output: output, * normal: normalView * } ) ); * * const scenePassColor = scenePass.getTextureNode( 'output' ); * const scenePassNormal = scenePass.getTextureNode( 'normal' ); * const scenePassDepth = scenePass.getTextureNode( 'depth' ); * * const aoPass = ao( scenePassDepth, scenePassNormal, camera ); * * postProcessing.outputNod = aoPass.getTextureNode().mul( scenePassColor ); * ``` * * Reference: {@link https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf}. * * @augments TempNode */ class GTAONode extends TempNode { static get type() { return 'GTAONode'; } /** * Constructs a new GTAO node. * * @param {Node<float>} depthNode - A node that represents the scene's depth. * @param {Node<vec3>?} normalNode - A node that represents the scene's normals. * @param {Camera} camera - The camera the scene is rendered with. */ constructor( depthNode, normalNode, camera ) { super( 'vec4' ); /** * A node that represents the scene's depth. * * @type {Node<float>} */ this.depthNode = depthNode; /** * A node that represents the scene's normals. If no normals are passed to the * constructor (because MRT is not available), normals can be automatically * reconstructed from depth values in the shader. * * @type {Node<vec3>?} */ this.normalNode = normalNode; /** * The resolution scale. By default the effect is rendered in full resolution * for best quality but a value of `0.5` should be sufficient for most scenes. * * @type {Number} * @default 1 */ this.resolutionScale = 1; /** * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders * its effect once per frame in `updateBefore()`. * * @type {String} * @default 'frame' */ this.updateBeforeType = NodeUpdateType.FRAME; /** * The render target the ambient occlusion is rendered into. * * @private * @type {RenderTarget} */ this._aoRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false } ); this._aoRenderTarget.texture.name = 'GTAONode.AO'; // uniforms /** * The radius of the ambient occlusion. * * @type {UniformNode<float>} */ this.radius = uniform( 0.25 ); /** * The resolution of the effect. Can be scaled via * `resolutionScale`. * * @type {UniformNode<vec2>} */ this.resolution = uniform( new Vector2() ); /** * The thickness of the ambient occlusion. * * @type {UniformNode<float>} */ this.thickness = uniform( 1 ); /** * Another option to tweak the occlusion. The recommended range is * `[1,2]` for attenuating the AO. * * @type {UniformNode<float>} */ this.distanceExponent = uniform( 1 ); /** * The distance fall off value of the ambient occlusion. * A lower value leads to a larger AO effect. The value * should lie in the range `[0,1]`. * * @type {UniformNode<float>} */ this.distanceFallOff = uniform( 1 ); /** * The scale of the ambient occlusion. * * @type {UniformNode<float>} */ this.scale = uniform( 1 ); /** * How many samples are used to compute the AO. * A higher value results in better quality but also * in a more expensive runtime behavior. * * @type {UniformNode<float>} */ this.samples = uniform( 16 ); /** * The node represents the internal noise texture used by the AO. * * @private * @type {TextureNode} */ this._noiseNode = texture( generateMagicSquareNoise() ); /** * Represents the projection matrix of the scene's camera. * * @private * @type {UniformNode<mat4>} */ this._cameraProjectionMatrix = uniform( camera.projectionMatrix ); /** * Represents the inverse projection matrix of the scene's camera. * * @private * @type {UniformNode<mat4>} */ this._cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse ); /** * Represents the near value of the scene's camera. * * @private * @type {ReferenceNode<float>} */ this._cameraNear = reference( 'near', 'float', camera ); /** * Represents the far value of the scene's camera. * * @private * @type {ReferenceNode<float>} */ this._cameraFar = reference( 'far', 'float', camera ); /** * The material that is used to render the effect. * * @private * @type {NodeMaterial} */ this._material = new NodeMaterial(); this._material.name = 'GTAO'; /** * The result of the effect is represented as a separate texture node. * * @private * @type {PassTextureNode} */ this._textureNode = passTexture( this, this._aoRenderTarget.texture ); } /** * Returns the result of the effect as a texture node. * * @return {PassTextureNode} A texture node that represents the result of the effect. */ getTextureNode() { return this._textureNode; } /** * Sets the size of the effect. * * @param {Number} width - The width of the effect. * @param {Number} height - The height of the effect. */ setSize( width, height ) { width = Math.round( this.resolutionScale * width ); height = Math.round( this.resolutionScale * height ); this.resolution.value.set( width, height ); this._aoRenderTarget.setSize( width, height ); } /** * This method is used to render the effect once per frame. * * @param {NodeFrame} frame - The current node frame. */ updateBefore( frame ) { const { renderer } = frame; _rendererState = RendererUtils.resetRendererState( renderer, _rendererState ); // const size = renderer.getDrawingBufferSize( _size ); this.setSize( size.width, size.height ); _quadMesh.material = this._material; // clear renderer.setClearColor( 0xffffff, 1 ); // ao renderer.setRenderTarget( this._aoRenderTarget ); _quadMesh.render( renderer ); // restore RendererUtils.restoreRendererState( renderer, _rendererState ); } /** * This method is used to setup the effect's TSL code. * * @param {NodeBuilder} builder - The current node builder. * @return {PassTextureNode} */ setup( builder ) { const uvNode = uv(); const sampleDepth = ( uv ) => { const depth = this.depthNode.sample( uv ).r; if ( builder.renderer.logarithmicDepthBuffer === true ) { const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar ); return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar ); } return depth; }; const sampleNoise = ( uv ) => this._noiseNode.sample( uv ); const sampleNormal = ( uv ) => ( this.normalNode !== null ) ? this.normalNode.sample( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse ); const ao = Fn( () => { const depth = sampleDepth( uvNode ).toVar(); depth.greaterThanEqual( 1.0 ).discard(); const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar(); const viewNormal = sampleNormal( uvNode ).toVar(); const radiusToUse = this.radius; const noiseResolution = textureSize( this._noiseNode, 0 ); let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() ); noiseUv = noiseUv.mul( this.resolution.div( noiseResolution ) ); const noiseTexel = sampleNoise( noiseUv ); const randomVec = noiseTexel.xyz.mul( 2.0 ).sub( 1.0 ); const tangent = vec3( randomVec.xy, 0.0 ).normalize(); const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 ); const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) ); const DIRECTIONS = this.samples.lessThan( 30 ).select( 3, 5 ).toVar(); const STEPS = add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar(); const ao = float( 0 ).toVar(); Loop( { start: int( 0 ), end: DIRECTIONS, type: 'int', condition: '<' }, ( { i } ) => { const angle = float( i ).div( float( DIRECTIONS ) ).mul( PI ).toVar(); const sampleDir = vec4( cos( angle ), sin( angle ), 0., add( 0.5, mul( 0.5, noiseTexel.w ) ) ); sampleDir.xyz = normalize( kernelMatrix.mul( sampleDir.xyz ) ); const viewDir = normalize( viewPosition.xyz.negate() ).toVar(); const sliceBitangent = normalize( cross( sampleDir.xyz, viewDir ) ).toVar(); const sliceTangent = cross( sliceBitangent, viewDir ); const normalInSlice = normalize( viewNormal.sub( sliceBitangent.mul( dot( viewNormal, sliceBitangent ) ) ) ); const tangentToNormalInSlice = cross( normalInSlice, sliceBitangent ).toVar(); const cosHorizons = vec2( dot( viewDir, tangentToNormalInSlice ), dot( viewDir, tangentToNormalInSlice.negate() ) ).toVar(); Loop( { end: STEPS, type: 'int', name: 'j', condition: '<' }, ( { j } ) => { const sampleViewOffset = sampleDir.xyz.mul( radiusToUse ).mul( sampleDir.w ).mul( pow( div( float( j ).add( 1.0 ), float( STEPS ) ), this.distanceExponent ) ); // x const sampleScreenPositionX = getScreenPosition( viewPosition.add( sampleViewOffset ), this._cameraProjectionMatrix ).toVar(); const sampleDepthX = sampleDepth( sampleScreenPositionX ).toVar(); const sampleSceneViewPositionX = getViewPosition( sampleScreenPositionX, sampleDepthX, this._cameraProjectionMatrixInverse ).toVar(); const viewDeltaX = sampleSceneViewPositionX.sub( viewPosition ).toVar(); If( abs( viewDeltaX.z ).lessThan( this.thickness ), () => { const sampleCosHorizon = dot( viewDir, normalize( viewDeltaX ) ); cosHorizons.x.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.x ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) ); } ); // y const sampleScreenPositionY = getScreenPosition( viewPosition.sub( sampleViewOffset ), this._cameraProjectionMatrix ).toVar(); const sampleDepthY = sampleDepth( sampleScreenPositionY ).toVar(); const sampleSceneViewPositionY = getViewPosition( sampleScreenPositionY, sampleDepthY, this._cameraProjectionMatrixInverse ).toVar(); const viewDeltaY = sampleSceneViewPositionY.sub( viewPosition ).toVar(); If( abs( viewDeltaY.z ).lessThan( this.thickness ), () => { const sampleCosHorizon = dot( viewDir, normalize( viewDeltaY ) ); cosHorizons.y.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.y ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) ); } ); } ); const sinHorizons = sqrt( sub( 1.0, cosHorizons.mul( cosHorizons ) ) ).toVar(); const nx = dot( normalInSlice, sliceTangent ); const ny = dot( normalInSlice, viewDir ); const nxb = mul( 0.5, acos( cosHorizons.y ).sub( acos( cosHorizons.x ) ).add( sinHorizons.x.mul( cosHorizons.x ).sub( sinHorizons.y.mul( cosHorizons.y ) ) ) ); const nyb = mul( 0.5, sub( 2.0, cosHorizons.x.mul( cosHorizons.x ) ).sub( cosHorizons.y.mul( cosHorizons.y ) ) ); const occlusion = nx.mul( nxb ).add( ny.mul( nyb ) ); ao.addAssign( occlusion ); } ); ao.assign( clamp( ao.div( DIRECTIONS ), 0, 1 ) ); ao.assign( pow( ao, this.scale ) ); return vec4( vec3( ao ), 1.0 ); } ); this._material.fragmentNode = ao().context( builder.getSharedContext() ); this._material.needsUpdate = true; // return this._textureNode; } /** * Frees internal resources. This method should be called * when the effect is no longer required. */ dispose() { this._aoRenderTarget.dispose(); this._material.dispose(); } } export default GTAONode; /** * Generates the AO's noise texture for the given size. * * @param {Number} [size=5] - The noise size. * @return {DataTexture} The generated noise texture. */ function generateMagicSquareNoise( size = 5 ) { const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size ); const magicSquare = generateMagicSquare( noiseSize ); const noiseSquareSize = magicSquare.length; const data = new Uint8Array( noiseSquareSize * 4 ); for ( let inx = 0; inx < noiseSquareSize; ++ inx ) { const iAng = magicSquare[ inx ]; const angle = ( 2 * Math.PI * iAng ) / noiseSquareSize; const randomVec = new Vector3( Math.cos( angle ), Math.sin( angle ), 0 ).normalize(); data[ inx * 4 ] = ( randomVec.x * 0.5 + 0.5 ) * 255; data[ inx * 4 + 1 ] = ( randomVec.y * 0.5 + 0.5 ) * 255; data[ inx * 4 + 2 ] = 127; data[ inx * 4 + 3 ] = 255; } const noiseTexture = new DataTexture( data, noiseSize, noiseSize ); noiseTexture.wrapS = RepeatWrapping; noiseTexture.wrapT = RepeatWrapping; noiseTexture.needsUpdate = true; return noiseTexture; } /** * Computes an array of magic square values required to generate the noise texture. * * @param {Number} size - The noise size. * @return {Array<Number>} The magic square values. */ function generateMagicSquare( size ) { const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size ); const noiseSquareSize = noiseSize * noiseSize; const magicSquare = Array( noiseSquareSize ).fill( 0 ); let i = Math.floor( noiseSize / 2 ); let j = noiseSize - 1; for ( let num = 1; num <= noiseSquareSize; ) { if ( i === - 1 && j === noiseSize ) { j = noiseSize - 2; i = 0; } else { if ( j === noiseSize ) { j = 0; } if ( i < 0 ) { i = noiseSize - 1; } } if ( magicSquare[ i * noiseSize + j ] !== 0 ) { j -= 2; i ++; continue; } else { magicSquare[ i * noiseSize + j ] = num ++; } j ++; i --; } return magicSquare; } /** * TSL function for creating a Ground Truth Ambient Occlusion (GTAO) effect. * * @function * @param {Node<float>} depthNode - A node that represents the scene's depth. * @param {Node<vec3>?} normalNode - A node that represents the scene's normals. * @param {Camera} camera - The camera the scene is rendered with. * @returns {GTAONode} */ export const ao = ( depthNode, normalNode, camera ) => nodeObject( new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera ) );