UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

193 lines (183 loc) 6.61 kB
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));