import { ShaderChunk, UniformsUtils, MeshDepthMaterial, RGBADepthPacking, MeshDistanceMaterial, ShaderLib, Matrix4, Vector3, Mesh, CylinderGeometry, Vector2, MeshStandardMaterial, DoubleSide } from 'three'; /** * Regular expression for matching the `void main() {` opener line in GLSL. * @type {RegExp} */ const voidMainRegExp = /\bvoid\s+main\s*\(\s*\)\s*{/g; /** * Recursively expands all `#include ` statements within string of shader code. * Copied from three's WebGLProgram#parseIncludes for external use. * * @param {string} source - The GLSL source code to evaluate * @return {string} The GLSL code with all includes expanded */ function expandShaderIncludes( source ) { const pattern = /^[ \t]*#include +<([\w\d./]+)>/gm; function replace(match, include) { let chunk = ShaderChunk[include]; return chunk ? expandShaderIncludes(chunk) : match } return source.replace( pattern, replace ) } /* * This is a direct copy of MathUtils.generateUUID from Three.js, to preserve compatibility with three * versions before 0.113.0 as it was changed from Math to MathUtils in that version. * https://github.com/mrdoob/three.js/blob/dd8b5aa3b270c17096b90945cd2d6d1b13aaec53/src/math/MathUtils.js#L16 */ const _lut = []; for (let i = 0; i < 256; i++) { _lut[i] = (i < 16 ? '0' : '') + (i).toString(16); } function generateUUID() { // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136 const d0 = Math.random() * 0xffffffff | 0; const d1 = Math.random() * 0xffffffff | 0; const d2 = Math.random() * 0xffffffff | 0; const d3 = Math.random() * 0xffffffff | 0; const uuid = _lut[d0 & 0xff] + _lut[d0 >> 8 & 0xff] + _lut[d0 >> 16 & 0xff] + _lut[d0 >> 24 & 0xff] + '-' + _lut[d1 & 0xff] + _lut[d1 >> 8 & 0xff] + '-' + _lut[d1 >> 16 & 0x0f | 0x40] + _lut[d1 >> 24 & 0xff] + '-' + _lut[d2 & 0x3f | 0x80] + _lut[d2 >> 8 & 0xff] + '-' + _lut[d2 >> 16 & 0xff] + _lut[d2 >> 24 & 0xff] + _lut[d3 & 0xff] + _lut[d3 >> 8 & 0xff] + _lut[d3 >> 16 & 0xff] + _lut[d3 >> 24 & 0xff]; // .toUpperCase() here flattens concatenated strings to save heap memory space. return uuid.toUpperCase() } // Local assign polyfill to avoid importing troika-core const assign = Object.assign || function(/*target, ...sources*/) { let target = arguments[0]; for (let i = 1, len = arguments.length; i < len; i++) { let source = arguments[i]; if (source) { for (let prop in source) { if (Object.prototype.hasOwnProperty.call(source, prop)) { target[prop] = source[prop]; } } } } return target }; const epoch = Date.now(); const CONSTRUCTOR_CACHE = new WeakMap(); const SHADER_UPGRADE_CACHE = new Map(); // Material ids must be integers, but we can't access the increment from Three's `Material` module, // so let's choose a sufficiently large starting value that should theoretically never collide. let materialInstanceId = 1e10; /** * A utility for creating a custom shader material derived from another material's * shaders. This allows you to inject custom shader logic and transforms into the * builtin ThreeJS materials without having to recreate them from scratch. * * @param {THREE.Material} baseMaterial - the original material to derive from * * @param {Object} options - How the base material should be modified. * @param {Object=} options.defines - Custom `defines` for the material * @param {Object=} options.extensions - Custom `extensions` for the material, e.g. `{derivatives: true}` * @param {Object=} options.uniforms - Custom `uniforms` for use in the modified shader. These can * be accessed and manipulated via the resulting material's `uniforms` property, just like * in a ShaderMaterial. You do not need to repeat the base material's own uniforms here. * @param {String=} options.timeUniform - If specified, a uniform of this name will be injected into * both shaders, and it will automatically be updated on each render frame with a number of * elapsed milliseconds. The "zero" epoch time is not significant so don't rely on this as a * true calendar time. * @param {String=} options.vertexDefs - Custom GLSL code to inject into the vertex shader's top-level * definitions, above the `void main()` function. * @param {String=} options.vertexMainIntro - Custom GLSL code to inject at the top of the vertex * shader's `void main` function. * @param {String=} options.vertexMainOutro - Custom GLSL code to inject at the end of the vertex * shader's `void main` function. * @param {String=} options.vertexTransform - Custom GLSL code to manipulate the `position`, `normal`, * and/or `uv` vertex attributes. This code will be wrapped within a standalone function with * those attributes exposed by their normal names as read/write values. * @param {String=} options.fragmentDefs - Custom GLSL code to inject into the fragment shader's top-level * definitions, above the `void main()` function. * @param {String=} options.fragmentMainIntro - Custom GLSL code to inject at the top of the fragment * shader's `void main` function. * @param {String=} options.fragmentMainOutro - Custom GLSL code to inject at the end of the fragment * shader's `void main` function. You can manipulate `gl_FragColor` here but keep in mind it goes * after any of ThreeJS's color postprocessing shader chunks (tonemapping, fog, etc.), so if you * want those to apply to your changes use `fragmentColorTransform` instead. * @param {String=} options.fragmentColorTransform - Custom GLSL code to manipulate the `gl_FragColor` * output value. Will be injected near the end of the `void main` function, but before any * of ThreeJS's color postprocessing shader chunks (tonemapping, fog, etc.), and before the * `fragmentMainOutro`. * @param {function({fragmentShader: string, vertexShader:string}): * {fragmentShader: string, vertexShader:string}} options.customRewriter - A function * for performing custom rewrites of the full shader code. Useful if you need to do something * special that's not covered by the other builtin options. This function will be executed before * any other transforms are applied. * @param {boolean=} options.chained - Set to `true` to prototype-chain the derived material to the base * material, rather than the default behavior of copying it. This allows the derived material to * automatically pick up changes made to the base material and its properties. This can be useful * where the derived material is hidden from the user as an implementation detail, allowing them * to work with the original material like normal. But it can result in unexpected behavior if not * handled carefully. * * @return {THREE.Material} * * The returned material will also have two new methods, `getDepthMaterial()` and `getDistanceMaterial()`, * which can be called to get a variant of the derived material for use in shadow casting. If the * target mesh is expected to cast shadows, then you can assign these to the mesh's `customDepthMaterial` * (for directional and spot lights) and/or `customDistanceMaterial` (for point lights) properties to * allow the cast shadow to honor your derived shader's vertex transforms and discarded fragments. These * will also set a custom `#define IS_DEPTH_MATERIAL` or `#define IS_DISTANCE_MATERIAL` that you can look * for in your derived shaders with `#ifdef` to customize their behavior for the depth or distance * scenarios, e.g. skipping antialiasing or expensive shader logic. */ function createDerivedMaterial(baseMaterial, options) { // Generate a key that is unique to the content of these `options`. We'll use this // throughout for caching and for generating the upgraded shader code. This increases // the likelihood that the resulting shaders will line up across multiple calls so // their GL programs can be shared and cached. const optionsKey = getKeyForOptions(options); // First check to see if we've already derived from this baseMaterial using this // unique set of options, and if so reuse the constructor to avoid some allocations. let ctorsByDerivation = CONSTRUCTOR_CACHE.get(baseMaterial); if (!ctorsByDerivation) { CONSTRUCTOR_CACHE.set(baseMaterial, (ctorsByDerivation = Object.create(null))); } if (ctorsByDerivation[optionsKey]) { return new ctorsByDerivation[optionsKey]() } const privateBeforeCompileProp = `_onBeforeCompile${optionsKey}`; // Private onBeforeCompile handler that injects the modified shaders and uniforms when // the renderer switches to this material's program const onBeforeCompile = function (shaderInfo, renderer) { baseMaterial.onBeforeCompile.call(this, shaderInfo, renderer); // Upgrade the shaders, caching the result by incoming source code const cacheKey = this.customProgramCacheKey() + '|' + shaderInfo.vertexShader + '|' + shaderInfo.fragmentShader; let upgradedShaders = SHADER_UPGRADE_CACHE[cacheKey]; if (!upgradedShaders) { const upgraded = upgradeShaders(this, shaderInfo, options, optionsKey); upgradedShaders = SHADER_UPGRADE_CACHE[cacheKey] = upgraded; } // Inject upgraded shaders and uniforms into the program shaderInfo.vertexShader = upgradedShaders.vertexShader; shaderInfo.fragmentShader = upgradedShaders.fragmentShader; assign(shaderInfo.uniforms, this.uniforms); // Inject auto-updating time uniform if requested if (options.timeUniform) { shaderInfo.uniforms[options.timeUniform] = { get value() {return Date.now() - epoch} }; } // Users can still add their own handlers on top of ours if (this[privateBeforeCompileProp]) { this[privateBeforeCompileProp](shaderInfo); } }; const DerivedMaterial = function DerivedMaterial() { return derive(options.chained ? baseMaterial : baseMaterial.clone()) }; const derive = function(base) { // Prototype chain to the base material const derived = Object.create(base, descriptor); // Store the baseMaterial for reference; this is always the original even when cloning Object.defineProperty(derived, 'baseMaterial', { value: baseMaterial }); // Needs its own ids Object.defineProperty(derived, 'id', { value: materialInstanceId++ }); derived.uuid = generateUUID(); // Merge uniforms, defines, and extensions derived.uniforms = assign({}, base.uniforms, options.uniforms); derived.defines = assign({}, base.defines, options.defines); derived.defines[`TROIKA_DERIVED_MATERIAL_${optionsKey}`] = ''; //force a program change from the base material derived.extensions = assign({}, base.extensions, options.extensions); // Don't inherit EventDispatcher listeners derived._listeners = undefined; return derived }; const descriptor = { constructor: {value: DerivedMaterial}, isDerivedMaterial: {value: true}, type: { get: () => baseMaterial.type, set: (value) => {baseMaterial.type = value;} }, isDerivedFrom: { writable: true, configurable: true, value: function (testMaterial) { const base = this.baseMaterial; return testMaterial === base || (base.isDerivedMaterial && base.isDerivedFrom(testMaterial)) || false } }, customProgramCacheKey: { writable: true, configurable: true, value: function () { return baseMaterial.customProgramCacheKey() + '|' + optionsKey } }, onBeforeCompile: { get() { return onBeforeCompile }, set(fn) { this[privateBeforeCompileProp] = fn; } }, copy: { writable: true, configurable: true, value: function (source) { baseMaterial.copy.call(this, source); if (!baseMaterial.isShaderMaterial && !baseMaterial.isDerivedMaterial) { assign(this.extensions, source.extensions); assign(this.defines, source.defines); assign(this.uniforms, UniformsUtils.clone(source.uniforms)); } return this } }, clone: { writable: true, configurable: true, value: function () { const newBase = new baseMaterial.constructor(); return derive(newBase).copy(this) } }, /** * Utility to get a MeshDepthMaterial that will honor this derived material's vertex * transformations and discarded fragments. */ getDepthMaterial: { writable: true, configurable: true, value: function() { let depthMaterial = this._depthMaterial; if (!depthMaterial) { depthMaterial = this._depthMaterial = createDerivedMaterial( baseMaterial.isDerivedMaterial ? baseMaterial.getDepthMaterial() : new MeshDepthMaterial({ depthPacking: RGBADepthPacking }), options ); depthMaterial.defines.IS_DEPTH_MATERIAL = ''; depthMaterial.uniforms = this.uniforms; //automatically recieve same uniform values } return depthMaterial } }, /** * Utility to get a MeshDistanceMaterial that will honor this derived material's vertex * transformations and discarded fragments. */ getDistanceMaterial: { writable: true, configurable: true, value: function() { let distanceMaterial = this._distanceMaterial; if (!distanceMaterial) { distanceMaterial = this._distanceMaterial = createDerivedMaterial( baseMaterial.isDerivedMaterial ? baseMaterial.getDistanceMaterial() : new MeshDistanceMaterial(), options ); distanceMaterial.defines.IS_DISTANCE_MATERIAL = ''; distanceMaterial.uniforms = this.uniforms; //automatically recieve same uniform values } return distanceMaterial } }, dispose: { writable: true, configurable: true, value() { const {_depthMaterial, _distanceMaterial} = this; if (_depthMaterial) _depthMaterial.dispose(); if (_distanceMaterial) _distanceMaterial.dispose(); baseMaterial.dispose.call(this); } } }; ctorsByDerivation[optionsKey] = DerivedMaterial; return new DerivedMaterial() } function upgradeShaders(material, {vertexShader, fragmentShader}, options, key) { let { vertexDefs, vertexMainIntro, vertexMainOutro, vertexTransform, fragmentDefs, fragmentMainIntro, fragmentMainOutro, fragmentColorTransform, customRewriter, timeUniform } = options; vertexDefs = vertexDefs || ''; vertexMainIntro = vertexMainIntro || ''; vertexMainOutro = vertexMainOutro || ''; fragmentDefs = fragmentDefs || ''; fragmentMainIntro = fragmentMainIntro || ''; fragmentMainOutro = fragmentMainOutro || ''; // Expand includes if needed if (vertexTransform || customRewriter) { vertexShader = expandShaderIncludes(vertexShader); } if (fragmentColorTransform || customRewriter) { // We need to be able to find postprocessing chunks after include expansion in order to // put them after the fragmentColorTransform, so mark them with comments first. Even if // this particular derivation doesn't have a fragmentColorTransform, other derivations may, // so we still mark them. fragmentShader = fragmentShader.replace( /^[ \t]*#include <((?:tonemapping|encodings|colorspace|fog|premultiplied_alpha|dithering)_fragment)>/gm, '\n//!BEGIN_POST_CHUNK $1\n$&\n//!END_POST_CHUNK\n' ); fragmentShader = expandShaderIncludes(fragmentShader); } // Apply custom rewriter function if (customRewriter) { let res = customRewriter({vertexShader, fragmentShader}); vertexShader = res.vertexShader; fragmentShader = res.fragmentShader; } // The fragmentColorTransform needs to go before any postprocessing chunks, so extract // those and re-insert them into the outro in the correct place: if (fragmentColorTransform) { let postChunks = []; fragmentShader = fragmentShader.replace( /^\/\/!BEGIN_POST_CHUNK[^]+?^\/\/!END_POST_CHUNK/gm, // [^]+? = non-greedy match of any chars including newlines match => { postChunks.push(match); return '' } ); fragmentMainOutro = `${fragmentColorTransform}\n${postChunks.join('\n')}\n${fragmentMainOutro}`; } // Inject auto-updating time uniform if requested if (timeUniform) { const code = `\nuniform float ${timeUniform};\n`; vertexDefs = code + vertexDefs; fragmentDefs = code + fragmentDefs; } // Inject a function for the vertexTransform and rename all usages of position/normal/uv if (vertexTransform) { // Hoist these defs to the very top so they work in other function defs vertexShader = `vec3 troika_position_${key}; vec3 troika_normal_${key}; vec2 troika_uv_${key}; ${vertexShader} `; vertexDefs = `${vertexDefs} void troikaVertexTransform${key}(inout vec3 position, inout vec3 normal, inout vec2 uv) { ${vertexTransform} } `; vertexMainIntro = ` troika_position_${key} = vec3(position); troika_normal_${key} = vec3(normal); troika_uv_${key} = vec2(uv); troikaVertexTransform${key}(troika_position_${key}, troika_normal_${key}, troika_uv_${key}); ${vertexMainIntro} `; vertexShader = vertexShader.replace(/\b(position|normal|uv)\b/g, (match, match1, index, fullStr) => { return /\battribute\s+vec[23]\s+$/.test(fullStr.substr(0, index)) ? match1 : `troika_${match1}_${key}` }); // Three r152 introduced the MAP_UV token, replace it too if it's pointing to the main 'uv' // Perhaps the other textures too going forward? if (!(material.map && material.map.channel > 0)) { vertexShader = vertexShader.replace(/\bMAP_UV\b/g, `troika_uv_${key}`); } } // Inject defs and intro/outro snippets vertexShader = injectIntoShaderCode(vertexShader, key, vertexDefs, vertexMainIntro, vertexMainOutro); fragmentShader = injectIntoShaderCode(fragmentShader, key, fragmentDefs, fragmentMainIntro, fragmentMainOutro); return { vertexShader, fragmentShader } } function injectIntoShaderCode(shaderCode, id, defs, intro, outro) { if (intro || outro || defs) { shaderCode = shaderCode.replace(voidMainRegExp, ` ${defs} void troikaOrigMain${id}() {` ); shaderCode += ` void main() { ${intro} troikaOrigMain${id}(); ${outro} }`; } return shaderCode } function optionsJsonReplacer(key, value) { return key === 'uniforms' ? undefined : typeof value === 'function' ? value.toString() : value } let _idCtr = 0; const optionsHashesToIds = new Map(); function getKeyForOptions(options) { const optionsHash = JSON.stringify(options, optionsJsonReplacer); let id = optionsHashesToIds.get(optionsHash); if (id == null) { optionsHashesToIds.set(optionsHash, (id = ++_idCtr)); } return id } // Copied from threejs WebGLPrograms.js so we can resolve builtin materials to their shaders // TODO how can we keep this from getting stale? const MATERIAL_TYPES_TO_SHADERS = { MeshDepthMaterial: 'depth', MeshDistanceMaterial: 'distanceRGBA', MeshNormalMaterial: 'normal', MeshBasicMaterial: 'basic', MeshLambertMaterial: 'lambert', MeshPhongMaterial: 'phong', MeshToonMaterial: 'toon', MeshStandardMaterial: 'physical', MeshPhysicalMaterial: 'physical', MeshMatcapMaterial: 'matcap', LineBasicMaterial: 'basic', LineDashedMaterial: 'dashed', PointsMaterial: 'points', ShadowMaterial: 'shadow', SpriteMaterial: 'sprite' }; /** * Given a Three.js `Material` instance, find the shaders/uniforms that will be * used to render that material. * * @param material - the Material instance * @return {object} - the material's shader info: `{uniforms:{}, fragmentShader:'', vertexShader:''}` */ function getShadersForMaterial(material) { let builtinType = MATERIAL_TYPES_TO_SHADERS[material.type]; return builtinType ? ShaderLib[builtinType] : material //TODO fallback for unknown type? } /** * Find all uniforms and their types within a shader code string. * * @param {string} shader - The shader code to parse * @return {object} mapping of uniform names to their glsl type */ function getShaderUniformTypes(shader) { let uniformRE = /\buniform\s+(int|float|vec[234]|mat[34])\s+([A-Za-z_][\w]*)/g; let uniforms = Object.create(null); let match; while ((match = uniformRE.exec(shader)) !== null) { uniforms[match[2]] = match[1]; } return uniforms } /** * Helper for smoothing out the `m.getInverse(x)` --> `m.copy(x).invert()` conversion * that happened in ThreeJS r123. * @param {Matrix4} srcMatrix * @param {Matrix4} [tgtMatrix] */ function invertMatrix4(srcMatrix, tgtMatrix = new Matrix4()) { if (typeof tgtMatrix.invert === 'function') { tgtMatrix.copy(srcMatrix).invert(); } else { tgtMatrix.getInverse(srcMatrix); } return tgtMatrix } /* Input geometry is a cylinder with r=1, height in y dimension from 0 to 1, divided into a reasonable number of height segments. */ const vertexDefs = ` uniform vec3 pointA; uniform vec3 controlA; uniform vec3 controlB; uniform vec3 pointB; uniform float radius; varying float bezierT; vec3 cubicBezier(vec3 p1, vec3 c1, vec3 c2, vec3 p2, float t) { float t2 = 1.0 - t; float b0 = t2 * t2 * t2; float b1 = 3.0 * t * t2 * t2; float b2 = 3.0 * t * t * t2; float b3 = t * t * t; return b0 * p1 + b1 * c1 + b2 * c2 + b3 * p2; } vec3 cubicBezierDerivative(vec3 p1, vec3 c1, vec3 c2, vec3 p2, float t) { float t2 = 1.0 - t; return -3.0 * p1 * t2 * t2 + c1 * (3.0 * t2 * t2 - 6.0 * t2 * t) + c2 * (6.0 * t2 * t - 3.0 * t * t) + 3.0 * p2 * t * t; } `; const vertexTransform = ` float t = position.y; bezierT = t; vec3 bezierCenterPos = cubicBezier(pointA, controlA, controlB, pointB, t); vec3 bezierDir = normalize(cubicBezierDerivative(pointA, controlA, controlB, pointB, t)); // Make "sideways" always perpendicular to the camera ray; this ensures that any twists // in the cylinder occur where you won't see them: vec3 viewDirection = normalMatrix * vec3(0.0, 0.0, 1.0); if (bezierDir == viewDirection) { bezierDir = normalize(cubicBezierDerivative(pointA, controlA, controlB, pointB, t == 1.0 ? t - 0.0001 : t + 0.0001)); } vec3 sideways = normalize(cross(bezierDir, viewDirection)); vec3 upish = normalize(cross(sideways, bezierDir)); // Build a matrix for transforming this disc in the cylinder: mat4 discTx; discTx[0].xyz = sideways * radius; discTx[1].xyz = bezierDir * radius; discTx[2].xyz = upish * radius; discTx[3].xyz = bezierCenterPos; discTx[3][3] = 1.0; // Apply transform, ignoring original y position = (discTx * vec4(position.x, 0.0, position.z, 1.0)).xyz; normal = normalize(mat3(discTx) * normal); `; const fragmentDefs = ` uniform vec3 dashing; varying float bezierT; `; const fragmentMainIntro = ` if (dashing.x + dashing.y > 0.0) { float dashFrac = mod(bezierT - dashing.z, dashing.x + dashing.y); if (dashFrac > dashing.x) { discard; } } `; // Debugging: separate color for each of the 6 sides: // const fragmentColorTransform = ` // float sideNum = floor(vUV.x * 6.0); // vec3 mixColor = sideNum < 1.0 ? vec3(1.0, 0.0, 0.0) : // sideNum < 2.0 ? vec3(0.0, 1.0, 1.0) : // sideNum < 3.0 ? vec3(1.0, 1.0, 0.0) : // sideNum < 4.0 ? vec3(0.0, 0.0, 1.0) : // sideNum < 5.0 ? vec3(0.0, 1.0, 0.0) : // vec3(1.0, 0.0, 1.0); // gl_FragColor.xyz = mix(gl_FragColor.xyz, mixColor, 0.5); // ` function createBezierMeshMaterial(baseMaterial) { return createDerivedMaterial( baseMaterial, { chained: true, uniforms: { pointA: {value: new Vector3()}, controlA: {value: new Vector3()}, controlB: {value: new Vector3()}, pointB: {value: new Vector3()}, radius: {value: 0.01}, dashing: {value: new Vector3()} //on, off, offset }, vertexDefs, vertexTransform, fragmentDefs, fragmentMainIntro } ) } let geometry = null; const defaultBaseMaterial = /*#__PURE__*/new MeshStandardMaterial({color: 0xffffff, side: DoubleSide}); /** * A ThreeJS `Mesh` that bends a tube shape along a 3D cubic bezier path. The bending is done * by deforming a straight cylindrical geometry in the vertex shader based on a set of four * control point uniforms. It patches the necessary GLSL into the mesh's assigned `material` * automatically. * * The cubiz bezier path is determined by its four `Vector3` properties: * - `pointA` * - `controlA` * - `controlB` * - `pointB` * * The tube's radius is controlled by its `radius` property, which defaults to `0.01`. * * You can also give the tube a dashed appearance with two properties: * * - `dashArray` - an array of two numbers, defining the length of "on" and "off" parts of * the dash. Each is a 0-1 ratio of the entire path's length. (Actually this is the `t` length * used as input to the cubic bezier function, not its visible length.) * - `dashOffset` - offset of where the dash starts. You can animate this to make the dashes move. * * Note that the dashes will appear like a hollow tube, not solid. This will be more apparent on * thicker tubes. * * TODO: proper geometry bounding sphere and raycasting * TODO: allow control of the geometry's segment counts */ class BezierMesh extends Mesh { static getGeometry() { return geometry || (geometry = new CylinderGeometry(1, 1, 1, 6, 64).translate(0, 0.5, 0) ) } constructor() { super( BezierMesh.getGeometry(), defaultBaseMaterial ); this.pointA = new Vector3(); this.controlA = new Vector3(); this.controlB = new Vector3(); this.pointB = new Vector3(); this.radius = 0.01; this.dashArray = new Vector2(); this.dashOffset = 0; // TODO - disabling frustum culling until I figure out how to customize the // geometry's bounding sphere that gets used this.frustumCulled = false; } // Handler for automatically wrapping the base material with our upgrades. We do the wrapping // lazily on _read_ rather than write to avoid unnecessary wrapping on transient values. get material() { let derivedMaterial = this._derivedMaterial; const baseMaterial = this._baseMaterial || this._defaultMaterial || (this._defaultMaterial = defaultBaseMaterial.clone()); if (!derivedMaterial || derivedMaterial.baseMaterial !== baseMaterial) { derivedMaterial = this._derivedMaterial = createBezierMeshMaterial(baseMaterial); // dispose the derived material when its base material is disposed: baseMaterial.addEventListener('dispose', function onDispose() { baseMaterial.removeEventListener('dispose', onDispose); derivedMaterial.dispose(); }); } return derivedMaterial } set material(baseMaterial) { this._baseMaterial = baseMaterial; } // Create and update material for shadows upon request: get customDepthMaterial() { return this.material.getDepthMaterial() } set customDepthMaterial(m) { // future: let the user override with their own? } get customDistanceMaterial() { return this.material.getDistanceMaterial() } set customDistanceMaterial(m) { // future: let the user override with their own? } onBeforeRender() { const {uniforms} = this.material; const {pointA, controlA, controlB, pointB, radius, dashArray, dashOffset} = this; uniforms.pointA.value.copy(pointA); uniforms.controlA.value.copy(controlA); uniforms.controlB.value.copy(controlB); uniforms.pointB.value.copy(pointB); uniforms.radius.value = radius; uniforms.dashing.value.set(dashArray.x, dashArray.y, dashOffset || 0); } raycast(/*raycaster, intersects*/) { // TODO - just fail for now } } export { BezierMesh, createDerivedMaterial, expandShaderIncludes, getShaderUniformTypes, getShadersForMaterial, invertMatrix4, voidMainRegExp };