Buckets:
| import Node from '../core/Node.js'; | |
| import { nodeObject, Fn, bool, float } from '../tsl/TSLBase.js'; | |
| import { positionView } from './Position.js'; | |
| import { diffuseColor } from '../core/PropertyNode.js'; | |
| import { Loop } from '../utils/LoopNode.js'; | |
| import { smoothstep } from '../math/MathNode.js'; | |
| import { uniformArray } from './UniformArrayNode.js'; | |
| import { builtin } from './BuiltinNode.js'; | |
| /** | |
| * This node is used in {@link NodeMaterial} to setup the clipping | |
| * which can happen hardware-accelerated (if supported) and optionally | |
| * use alpha-to-coverage for anti-aliasing clipped edges. | |
| * | |
| * @augments Node | |
| */ | |
| class ClippingNode extends Node { | |
| static get type() { | |
| return 'ClippingNode'; | |
| } | |
| /** | |
| * Constructs a new clipping node. | |
| * | |
| * @param {('default'|'hardware'|'alphaToCoverage')} [scope='default'] - The node's scope. Similar to other nodes, | |
| * the selected scope influences the behavior of the node and what type of code is generated. | |
| */ | |
| constructor( scope = ClippingNode.DEFAULT ) { | |
| super(); | |
| /** | |
| * The node's scope. Similar to other nodes, the selected scope influences | |
| * the behavior of the node and what type of code is generated. | |
| * | |
| * @type {('default'|'hardware'|'alphaToCoverage')} | |
| */ | |
| this.scope = scope; | |
| } | |
| /** | |
| * Setups the node depending on the selected scope. | |
| * | |
| * @param {NodeBuilder} builder - The current node builder. | |
| * @return {Node} The result node. | |
| */ | |
| setup( builder ) { | |
| super.setup( builder ); | |
| const clippingContext = builder.clippingContext; | |
| const { intersectionPlanes, unionPlanes } = clippingContext; | |
| this.hardwareClipping = builder.material.hardwareClipping; | |
| if ( this.scope === ClippingNode.ALPHA_TO_COVERAGE ) { | |
| return this.setupAlphaToCoverage( intersectionPlanes, unionPlanes ); | |
| } else if ( this.scope === ClippingNode.HARDWARE ) { | |
| return this.setupHardwareClipping( unionPlanes, builder ); | |
| } else { | |
| return this.setupDefault( intersectionPlanes, unionPlanes ); | |
| } | |
| } | |
| /** | |
| * Setups alpha to coverage. | |
| * | |
| * @param {Array<Vector4>} intersectionPlanes - The intersection planes. | |
| * @param {Array<Vector4>} unionPlanes - The union planes. | |
| * @return {Node} The result node. | |
| */ | |
| setupAlphaToCoverage( intersectionPlanes, unionPlanes ) { | |
| return Fn( () => { | |
| const distanceToPlane = float().toVar( 'distanceToPlane' ); | |
| const distanceGradient = float().toVar( 'distanceToGradient' ); | |
| const clipOpacity = float( 1 ).toVar( 'clipOpacity' ); | |
| const numUnionPlanes = unionPlanes.length; | |
| if ( this.hardwareClipping === false && numUnionPlanes > 0 ) { | |
| const clippingPlanes = uniformArray( unionPlanes ); | |
| Loop( numUnionPlanes, ( { i } ) => { | |
| const plane = clippingPlanes.element( i ); | |
| distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) ); | |
| distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) ); | |
| clipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ) ); | |
| } ); | |
| } | |
| const numIntersectionPlanes = intersectionPlanes.length; | |
| if ( numIntersectionPlanes > 0 ) { | |
| const clippingPlanes = uniformArray( intersectionPlanes ); | |
| const intersectionClipOpacity = float( 1 ).toVar( 'intersectionClipOpacity' ); | |
| Loop( numIntersectionPlanes, ( { i } ) => { | |
| const plane = clippingPlanes.element( i ); | |
| distanceToPlane.assign( positionView.dot( plane.xyz ).negate().add( plane.w ) ); | |
| distanceGradient.assign( distanceToPlane.fwidth().div( 2.0 ) ); | |
| intersectionClipOpacity.mulAssign( smoothstep( distanceGradient.negate(), distanceGradient, distanceToPlane ).oneMinus() ); | |
| } ); | |
| clipOpacity.mulAssign( intersectionClipOpacity.oneMinus() ); | |
| } | |
| diffuseColor.a.mulAssign( clipOpacity ); | |
| diffuseColor.a.equal( 0.0 ).discard(); | |
| } )(); | |
| } | |
| /** | |
| * Setups the default clipping. | |
| * | |
| * @param {Array<Vector4>} intersectionPlanes - The intersection planes. | |
| * @param {Array<Vector4>} unionPlanes - The union planes. | |
| * @return {Node} The result node. | |
| */ | |
| setupDefault( intersectionPlanes, unionPlanes ) { | |
| return Fn( () => { | |
| const numUnionPlanes = unionPlanes.length; | |
| if ( this.hardwareClipping === false && numUnionPlanes > 0 ) { | |
| const clippingPlanes = uniformArray( unionPlanes ); | |
| Loop( numUnionPlanes, ( { i } ) => { | |
| const plane = clippingPlanes.element( i ); | |
| positionView.dot( plane.xyz ).greaterThan( plane.w ).discard(); | |
| } ); | |
| } | |
| const numIntersectionPlanes = intersectionPlanes.length; | |
| if ( numIntersectionPlanes > 0 ) { | |
| const clippingPlanes = uniformArray( intersectionPlanes ); | |
| const clipped = bool( true ).toVar( 'clipped' ); | |
| Loop( numIntersectionPlanes, ( { i } ) => { | |
| const plane = clippingPlanes.element( i ); | |
| clipped.assign( positionView.dot( plane.xyz ).greaterThan( plane.w ).and( clipped ) ); | |
| } ); | |
| clipped.discard(); | |
| } | |
| } )(); | |
| } | |
| /** | |
| * Setups hardware clipping. | |
| * | |
| * @param {Array<Vector4>} unionPlanes - The union planes. | |
| * @param {NodeBuilder} builder - The current node builder. | |
| * @return {Node} The result node. | |
| */ | |
| setupHardwareClipping( unionPlanes, builder ) { | |
| const numUnionPlanes = unionPlanes.length; | |
| builder.enableHardwareClipping( numUnionPlanes ); | |
| return Fn( () => { | |
| const clippingPlanes = uniformArray( unionPlanes ); | |
| const hw_clip_distances = builtin( builder.getClipDistance() ); | |
| Loop( numUnionPlanes, ( { i } ) => { | |
| const plane = clippingPlanes.element( i ); | |
| const distance = positionView.dot( plane.xyz ).sub( plane.w ).negate(); | |
| hw_clip_distances.element( i ).assign( distance ); | |
| } ); | |
| } )(); | |
| } | |
| } | |
| ClippingNode.ALPHA_TO_COVERAGE = 'alphaToCoverage'; | |
| ClippingNode.DEFAULT = 'default'; | |
| ClippingNode.HARDWARE = 'hardware'; | |
| export default ClippingNode; | |
| /** | |
| * TSL function for setting up the default clipping logic. | |
| * | |
| * @tsl | |
| * @function | |
| * @returns {ClippingNode} | |
| */ | |
| export const clipping = () => nodeObject( new ClippingNode() ); | |
| /** | |
| * TSL function for setting up alpha to coverage. | |
| * | |
| * @tsl | |
| * @function | |
| * @returns {ClippingNode} | |
| */ | |
| export const clippingAlpha = () => nodeObject( new ClippingNode( ClippingNode.ALPHA_TO_COVERAGE ) ); | |
| /** | |
| * TSL function for setting up hardware-based clipping. | |
| * | |
| * @tsl | |
| * @function | |
| * @returns {ClippingNode} | |
| */ | |
| export const hardwareClipping = () => nodeObject( new ClippingNode( ClippingNode.HARDWARE ) ); | |
Xet Storage Details
- Size:
- 6.44 kB
- Xet hash:
- b2b52cd0e28c53923878734bd627ab58357782958c01a299a972bf1201bf6f5c
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.