UNPKG

@openhps/core

Version:

Open Hybrid Positioning System - Core component

441 lines (399 loc) 15.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _NodeMaterial = _interopRequireDefault(require("./NodeMaterial.js")); var _PropertyNode = require("../../nodes/core/PropertyNode.js"); var _AttributeNode = require("../../nodes/core/AttributeNode.js"); var _Camera = require("../../nodes/accessors/Camera.js"); var _MaterialNode = require("../../nodes/accessors/MaterialNode.js"); var _ModelNode = require("../../nodes/accessors/ModelNode.js"); var _Position = require("../../nodes/accessors/Position.js"); var _MathNode = require("../../nodes/math/MathNode.js"); var _TSLBase = require("../../nodes/tsl/TSLBase.js"); var _UV = require("../../nodes/accessors/UV.js"); var _ScreenNode = require("../../nodes/display/ScreenNode.js"); var _ViewportSharedTextureNode = require("../../nodes/display/ViewportSharedTextureNode.js"); var _LineDashedMaterial = require("../LineDashedMaterial.js"); var _constants = require("../../constants.js"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const _defaultValues = /*@__PURE__*/new _LineDashedMaterial.LineDashedMaterial(); /** * This node material can be used to render lines with a size larger than one * by representing them as instanced meshes. * * @augments NodeMaterial */ class Line2NodeMaterial extends _NodeMaterial.default { static get type() { return 'Line2NodeMaterial'; } /** * Constructs a new node material for wide line rendering. * * @param {Object} [parameters={}] - The configuration parameter. */ constructor(parameters = {}) { super(); /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isLine2NodeMaterial = true; this.setDefaultValues(_defaultValues); /** * Whether vertex colors should be used or not. * * @type {boolean} * @default false */ this.useColor = parameters.vertexColors; /** * The dash offset. * * @type {number} * @default 0 */ this.dashOffset = 0; /** * The line width. * * @type {number} * @default 0 */ this.lineWidth = 1; /** * Defines the lines color. * * @type {?Node<vec3>} * @default null */ this.lineColorNode = null; /** * Defines the offset. * * @type {?Node<float>} * @default null */ this.offsetNode = null; /** * Defines the dash scale. * * @type {?Node<float>} * @default null */ this.dashScaleNode = null; /** * Defines the dash size. * * @type {?Node<float>} * @default null */ this.dashSizeNode = null; /** * Defines the gap size. * * @type {?Node<float>} * @default null */ this.gapSizeNode = null; /** * Blending is set to `NoBlending` since transparency * is not supported, yet. * * @type {number} * @default 0 */ this.blending = _constants.NoBlending; this._useDash = parameters.dashed; this._useAlphaToCoverage = true; this._useWorldUnits = false; this.setValues(parameters); } /** * Setups the vertex and fragment stage of this node material. * * @param {NodeBuilder} builder - The current node builder. */ setup(builder) { const { renderer } = builder; const useAlphaToCoverage = this._useAlphaToCoverage; const useColor = this.useColor; const useDash = this._useDash; const useWorldUnits = this._useWorldUnits; const trimSegment = (0, _TSLBase.Fn)(({ start, end }) => { const a = _Camera.cameraProjectionMatrix.element(2).element(2); // 3nd entry in 3th column const b = _Camera.cameraProjectionMatrix.element(3).element(2); // 3nd entry in 4th column const nearEstimate = b.mul(-0.5).div(a); const alpha = nearEstimate.sub(start.z).div(end.z.sub(start.z)); return (0, _TSLBase.vec4)((0, _MathNode.mix)(start.xyz, end.xyz, alpha), end.w); }).setLayout({ name: 'trimSegment', type: 'vec4', inputs: [{ name: 'start', type: 'vec4' }, { name: 'end', type: 'vec4' }] }); this.vertexNode = (0, _TSLBase.Fn)(() => { const instanceStart = (0, _AttributeNode.attribute)('instanceStart'); const instanceEnd = (0, _AttributeNode.attribute)('instanceEnd'); // camera space const start = (0, _TSLBase.vec4)(_ModelNode.modelViewMatrix.mul((0, _TSLBase.vec4)(instanceStart, 1.0))).toVar('start'); const end = (0, _TSLBase.vec4)(_ModelNode.modelViewMatrix.mul((0, _TSLBase.vec4)(instanceEnd, 1.0))).toVar('end'); if (useDash) { const dashScaleNode = this.dashScaleNode ? (0, _TSLBase.float)(this.dashScaleNode) : _MaterialNode.materialLineScale; const offsetNode = this.offsetNode ? (0, _TSLBase.float)(this.offsetNode) : _MaterialNode.materialLineDashOffset; const instanceDistanceStart = (0, _AttributeNode.attribute)('instanceDistanceStart'); const instanceDistanceEnd = (0, _AttributeNode.attribute)('instanceDistanceEnd'); let lineDistance = _Position.positionGeometry.y.lessThan(0.5).select(dashScaleNode.mul(instanceDistanceStart), dashScaleNode.mul(instanceDistanceEnd)); lineDistance = lineDistance.add(offsetNode); (0, _PropertyNode.varyingProperty)('float', 'lineDistance').assign(lineDistance); } if (useWorldUnits) { (0, _PropertyNode.varyingProperty)('vec3', 'worldStart').assign(start.xyz); (0, _PropertyNode.varyingProperty)('vec3', 'worldEnd').assign(end.xyz); } const aspect = _ScreenNode.viewport.z.div(_ScreenNode.viewport.w); // special case for perspective projection, and segments that terminate either in, or behind, the camera plane // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space // but we need to perform ndc-space calculations in the shader, so we must address this issue directly // perhaps there is a more elegant solution -- WestLangley const perspective = _Camera.cameraProjectionMatrix.element(2).element(3).equal(-1.0); // 4th entry in the 3rd column (0, _TSLBase.If)(perspective, () => { (0, _TSLBase.If)(start.z.lessThan(0.0).and(end.z.greaterThan(0.0)), () => { end.assign(trimSegment({ start: start, end: end })); }).ElseIf(end.z.lessThan(0.0).and(start.z.greaterThanEqual(0.0)), () => { start.assign(trimSegment({ start: end, end: start })); }); }); // clip space const clipStart = _Camera.cameraProjectionMatrix.mul(start); const clipEnd = _Camera.cameraProjectionMatrix.mul(end); // ndc space const ndcStart = clipStart.xyz.div(clipStart.w); const ndcEnd = clipEnd.xyz.div(clipEnd.w); // direction const dir = ndcEnd.xy.sub(ndcStart.xy).toVar(); // account for clip-space aspect ratio dir.x.assign(dir.x.mul(aspect)); dir.assign(dir.normalize()); const clip = (0, _TSLBase.vec4)().toVar(); if (useWorldUnits) { // get the offset direction as perpendicular to the view vector const worldDir = end.xyz.sub(start.xyz).normalize(); const tmpFwd = (0, _MathNode.mix)(start.xyz, end.xyz, 0.5).normalize(); const worldUp = worldDir.cross(tmpFwd).normalize(); const worldFwd = worldDir.cross(worldUp); const worldPos = (0, _PropertyNode.varyingProperty)('vec4', 'worldPos'); worldPos.assign(_Position.positionGeometry.y.lessThan(0.5).select(start, end)); // height offset const hw = _MaterialNode.materialLineWidth.mul(0.5); worldPos.addAssign((0, _TSLBase.vec4)(_Position.positionGeometry.x.lessThan(0.0).select(worldUp.mul(hw), worldUp.mul(hw).negate()), 0)); // don't extend the line if we're rendering dashes because we // won't be rendering the endcaps if (!useDash) { // cap extension worldPos.addAssign((0, _TSLBase.vec4)(_Position.positionGeometry.y.lessThan(0.5).select(worldDir.mul(hw).negate(), worldDir.mul(hw)), 0)); // add width to the box worldPos.addAssign((0, _TSLBase.vec4)(worldFwd.mul(hw), 0)); // endcaps (0, _TSLBase.If)(_Position.positionGeometry.y.greaterThan(1.0).or(_Position.positionGeometry.y.lessThan(0.0)), () => { worldPos.subAssign((0, _TSLBase.vec4)(worldFwd.mul(2.0).mul(hw), 0)); }); } // project the worldpos clip.assign(_Camera.cameraProjectionMatrix.mul(worldPos)); // shift the depth of the projected points so the line // segments overlap neatly const clipPose = (0, _TSLBase.vec3)().toVar(); clipPose.assign(_Position.positionGeometry.y.lessThan(0.5).select(ndcStart, ndcEnd)); clip.z.assign(clipPose.z.mul(clip.w)); } else { const offset = (0, _TSLBase.vec2)(dir.y, dir.x.negate()).toVar('offset'); // undo aspect ratio adjustment dir.x.assign(dir.x.div(aspect)); offset.x.assign(offset.x.div(aspect)); // sign flip offset.assign(_Position.positionGeometry.x.lessThan(0.0).select(offset.negate(), offset)); // endcaps (0, _TSLBase.If)(_Position.positionGeometry.y.lessThan(0.0), () => { offset.assign(offset.sub(dir)); }).ElseIf(_Position.positionGeometry.y.greaterThan(1.0), () => { offset.assign(offset.add(dir)); }); // adjust for linewidth offset.assign(offset.mul(_MaterialNode.materialLineWidth)); // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... offset.assign(offset.div(_ScreenNode.viewport.w)); // select end clip.assign(_Position.positionGeometry.y.lessThan(0.5).select(clipStart, clipEnd)); // back to clip space offset.assign(offset.mul(clip.w)); clip.assign(clip.add((0, _TSLBase.vec4)(offset, 0, 0))); } return clip; })(); const closestLineToLine = (0, _TSLBase.Fn)(({ p1, p2, p3, p4 }) => { const p13 = p1.sub(p3); const p43 = p4.sub(p3); const p21 = p2.sub(p1); const d1343 = p13.dot(p43); const d4321 = p43.dot(p21); const d1321 = p13.dot(p21); const d4343 = p43.dot(p43); const d2121 = p21.dot(p21); const denom = d2121.mul(d4343).sub(d4321.mul(d4321)); const numer = d1343.mul(d4321).sub(d1321.mul(d4343)); const mua = numer.div(denom).clamp(); const mub = d1343.add(d4321.mul(mua)).div(d4343).clamp(); return (0, _TSLBase.vec2)(mua, mub); }); this.colorNode = (0, _TSLBase.Fn)(() => { const vUv = (0, _UV.uv)(); if (useDash) { const dashSizeNode = this.dashSizeNode ? (0, _TSLBase.float)(this.dashSizeNode) : _MaterialNode.materialLineDashSize; const gapSizeNode = this.gapSizeNode ? (0, _TSLBase.float)(this.gapSizeNode) : _MaterialNode.materialLineGapSize; _PropertyNode.dashSize.assign(dashSizeNode); _PropertyNode.gapSize.assign(gapSizeNode); const vLineDistance = (0, _PropertyNode.varyingProperty)('float', 'lineDistance'); vUv.y.lessThan(-1.0).or(vUv.y.greaterThan(1.0)).discard(); // discard endcaps vLineDistance.mod(_PropertyNode.dashSize.add(_PropertyNode.gapSize)).greaterThan(_PropertyNode.dashSize).discard(); // todo - FIX } const alpha = (0, _TSLBase.float)(1).toVar('alpha'); if (useWorldUnits) { const worldStart = (0, _PropertyNode.varyingProperty)('vec3', 'worldStart'); const worldEnd = (0, _PropertyNode.varyingProperty)('vec3', 'worldEnd'); // Find the closest points on the view ray and the line segment const rayEnd = (0, _PropertyNode.varyingProperty)('vec4', 'worldPos').xyz.normalize().mul(1e5); const lineDir = worldEnd.sub(worldStart); const params = closestLineToLine({ p1: worldStart, p2: worldEnd, p3: (0, _TSLBase.vec3)(0.0, 0.0, 0.0), p4: rayEnd }); const p1 = worldStart.add(lineDir.mul(params.x)); const p2 = rayEnd.mul(params.y); const delta = p1.sub(p2); const len = delta.length(); const norm = len.div(_MaterialNode.materialLineWidth); if (!useDash) { if (useAlphaToCoverage && renderer.samples > 1) { const dnorm = norm.fwidth(); alpha.assign((0, _MathNode.smoothstep)(dnorm.negate().add(0.5), dnorm.add(0.5), norm).oneMinus()); } else { norm.greaterThan(0.5).discard(); } } } else { // round endcaps if (useAlphaToCoverage && renderer.samples > 1) { const a = vUv.x; const b = vUv.y.greaterThan(0.0).select(vUv.y.sub(1.0), vUv.y.add(1.0)); const len2 = a.mul(a).add(b.mul(b)); const dlen = (0, _TSLBase.float)(len2.fwidth()).toVar('dlen'); (0, _TSLBase.If)(vUv.y.abs().greaterThan(1.0), () => { alpha.assign((0, _MathNode.smoothstep)(dlen.oneMinus(), dlen.add(1), len2).oneMinus()); }); } else { (0, _TSLBase.If)(vUv.y.abs().greaterThan(1.0), () => { const a = vUv.x; const b = vUv.y.greaterThan(0.0).select(vUv.y.sub(1.0), vUv.y.add(1.0)); const len2 = a.mul(a).add(b.mul(b)); len2.greaterThan(1.0).discard(); }); } } let lineColorNode; if (this.lineColorNode) { lineColorNode = this.lineColorNode; } else { if (useColor) { const instanceColorStart = (0, _AttributeNode.attribute)('instanceColorStart'); const instanceColorEnd = (0, _AttributeNode.attribute)('instanceColorEnd'); const instanceColor = _Position.positionGeometry.y.lessThan(0.5).select(instanceColorStart, instanceColorEnd); lineColorNode = instanceColor.mul(_MaterialNode.materialColor); } else { lineColorNode = _MaterialNode.materialColor; } } return (0, _TSLBase.vec4)(lineColorNode, alpha); })(); if (this.transparent) { const opacityNode = this.opacityNode ? (0, _TSLBase.float)(this.opacityNode) : _MaterialNode.materialOpacity; this.outputNode = (0, _TSLBase.vec4)(this.colorNode.rgb.mul(opacityNode).add((0, _ViewportSharedTextureNode.viewportSharedTexture)().rgb.mul(opacityNode.oneMinus())), this.colorNode.a); } super.setup(builder); } /** * Whether the lines should sized in world units or not. * When set to `false` the unit is pixel. * * @type {boolean} * @default false */ get worldUnits() { return this._useWorldUnits; } set worldUnits(value) { if (this._useWorldUnits !== value) { this._useWorldUnits = value; this.needsUpdate = true; } } /** * Whether the lines should be dashed or not. * * @type {boolean} * @default false */ get dashed() { return this._useDash; } set dashed(value) { if (this._useDash !== value) { this._useDash = value; this.needsUpdate = true; } } /** * Whether alpha to coverage should be used or not. * * @type {boolean} * @default true */ get alphaToCoverage() { return this._useAlphaToCoverage; } set alphaToCoverage(value) { if (this._useAlphaToCoverage !== value) { this._useAlphaToCoverage = value; this.needsUpdate = true; } } } var _default = exports.default = Line2NodeMaterial;