Buckets:
ktongue/docker_container / simsite /frontend /node_modules /troika-three-utils /dist /troika-three-utils.esm.js
| 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 <xyz>` 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 }; | |
Xet Storage Details
- Size:
- 28.1 kB
- Xet hash:
- 905db8405c3a9b9537eb908d36eb80cf7907610b44c260ee268a4e740473e2cf
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.