Favivon - Correção

This commit is contained in:
2026-05-30 19:59:39 -03:00
parent 76ddaa815d
commit d7dfd221f0
32859 changed files with 5459654 additions and 404 deletions
+755
View File
@@ -0,0 +1,755 @@
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 };
+769
View File
@@ -0,0 +1,769 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('three')) :
typeof define === 'function' && define.amd ? define(['exports', 'three'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.troika_three_utils = {}, global.THREE));
}(this, (function (exports, three) { 'use strict';
/**
* 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 = three.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, three.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 three.MeshDepthMaterial({ depthPacking: three.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 three.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 ? three.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 three.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 three.Vector3()},
controlA: {value: new three.Vector3()},
controlB: {value: new three.Vector3()},
pointB: {value: new three.Vector3()},
radius: {value: 0.01},
dashing: {value: new three.Vector3()} //on, off, offset
},
vertexDefs,
vertexTransform,
fragmentDefs,
fragmentMainIntro
}
)
}
let geometry = null;
const defaultBaseMaterial = /*#__PURE__*/new three.MeshStandardMaterial({color: 0xffffff, side: three.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 three.Mesh {
static getGeometry() {
return geometry || (geometry =
new three.CylinderGeometry(1, 1, 1, 6, 64).translate(0, 0.5, 0)
)
}
constructor() {
super(
BezierMesh.getGeometry(),
defaultBaseMaterial
);
this.pointA = new three.Vector3();
this.controlA = new three.Vector3();
this.controlB = new three.Vector3();
this.pointB = new three.Vector3();
this.radius = 0.01;
this.dashArray = new three.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
}
}
exports.BezierMesh = BezierMesh;
exports.createDerivedMaterial = createDerivedMaterial;
exports.expandShaderIncludes = expandShaderIncludes;
exports.getShaderUniformTypes = getShaderUniformTypes;
exports.getShadersForMaterial = getShadersForMaterial;
exports.invertMatrix4 = invertMatrix4;
exports.voidMainRegExp = voidMainRegExp;
Object.defineProperty(exports, '__esModule', { value: true });
})));
+36
View File
@@ -0,0 +1,36 @@
'use strict';(function(h,c){"object"===typeof exports&&"undefined"!==typeof module?c(exports,require("three")):"function"===typeof define&&define.amd?define(["exports","three"],c):(h="undefined"!==typeof globalThis?globalThis:h||self,c(h.troika_three_utils={},h.THREE))})(this,function(h,c){function q(a){return a.replace(/^[ \t]*#include +<([\w\d./]+)>/gm,function(a,k){return(k=c.ShaderChunk[k])?q(k):a})}function m(a,b){let k=D(b),e=n.get(a);e||n.set(a,e=Object.create(null));if(e[k])return new e[k];
let g=`_onBeforeCompile${k}`,h=function(d,e){a.onBeforeCompile.call(this,d,e);e=this.customProgramCacheKey()+"|"+d.vertexShader+"|"+d.fragmentShader;var c=v[e];c||(c=E(this,d,b,k),c=v[e]=c);d.vertexShader=c.vertexShader;d.fragmentShader=c.fragmentShader;l(d.uniforms,this.uniforms);b.timeUniform&&(d.uniforms[b.timeUniform]={get value(){return Date.now()-F}});if(this[g])this[g](d)},t=function(){return w(b.chained?a:a.clone())},w=function(d){const c=Object.create(d,G);Object.defineProperty(c,"baseMaterial",
{value:a});Object.defineProperty(c,"id",{value:H++});{var e=4294967295*Math.random()|0;let a=4294967295*Math.random()|0,b=4294967295*Math.random()|0,d=4294967295*Math.random()|0;e=(f[e&255]+f[e>>8&255]+f[e>>16&255]+f[e>>24&255]+"-"+f[a&255]+f[a>>8&255]+"-"+f[a>>16&15|64]+f[a>>24&255]+"-"+f[b&63|128]+f[b>>8&255]+"-"+f[b>>16&255]+f[b>>24&255]+f[d&255]+f[d>>8&255]+f[d>>16&255]+f[d>>24&255]).toUpperCase()}c.uuid=e;c.uniforms=l({},d.uniforms,b.uniforms);c.defines=l({},d.defines,b.defines);c.defines[`TROIKA_DERIVED_MATERIAL_${k}`]=
"";c.extensions=l({},d.extensions,b.extensions);c._listeners=void 0;return c},G={constructor:{value:t},isDerivedMaterial:{value:!0},type:{get:()=>a.type,set:b=>{a.type=b}},isDerivedFrom:{writable:!0,configurable:!0,value:function(a){const b=this.baseMaterial;return a===b||b.isDerivedMaterial&&b.isDerivedFrom(a)||!1}},customProgramCacheKey:{writable:!0,configurable:!0,value:function(){return a.customProgramCacheKey()+"|"+k}},onBeforeCompile:{get(){return h},set(a){this[g]=a}},copy:{writable:!0,configurable:!0,
value:function(b){a.copy.call(this,b);a.isShaderMaterial||a.isDerivedMaterial||(l(this.extensions,b.extensions),l(this.defines,b.defines),l(this.uniforms,c.UniformsUtils.clone(b.uniforms)));return this}},clone:{writable:!0,configurable:!0,value:function(){const b=new a.constructor;return w(b).copy(this)}},getDepthMaterial:{writable:!0,configurable:!0,value:function(){let d=this._depthMaterial;d||(d=this._depthMaterial=m(a.isDerivedMaterial?a.getDepthMaterial():new c.MeshDepthMaterial({depthPacking:c.RGBADepthPacking}),
b),d.defines.IS_DEPTH_MATERIAL="",d.uniforms=this.uniforms);return d}},getDistanceMaterial:{writable:!0,configurable:!0,value:function(){let d=this._distanceMaterial;d||(d=this._distanceMaterial=m(a.isDerivedMaterial?a.getDistanceMaterial():new c.MeshDistanceMaterial,b),d.defines.IS_DISTANCE_MATERIAL="",d.uniforms=this.uniforms);return d}},dispose:{writable:!0,configurable:!0,value(){const {_depthMaterial:b,_distanceMaterial:c}=this;b&&b.dispose();c&&c.dispose();a.dispose.call(this)}}};e[k]=t;return new t}
function E(a,{vertexShader:b,fragmentShader:c},e,g){let {vertexDefs:k,vertexMainIntro:f,vertexMainOutro:h,vertexTransform:l,fragmentDefs:d,fragmentMainIntro:m,fragmentMainOutro:p,fragmentColorTransform:u,customRewriter:r,timeUniform:n}=e;k=k||"";f=f||"";h=h||"";d=d||"";m=m||"";p=p||"";if(l||r)b=q(b);if(u||r)c=c.replace(/^[ \t]*#include <((?:tonemapping|encodings|colorspace|fog|premultiplied_alpha|dithering)_fragment)>/gm,"\n//!BEGIN_POST_CHUNK $1\n$&\n//!END_POST_CHUNK\n"),c=q(c);r&&(c=r({vertexShader:b,
fragmentShader:c}),b=c.vertexShader,c=c.fragmentShader);if(u){let a=[];c=c.replace(/^\/\/!BEGIN_POST_CHUNK[^]+?^\/\/!END_POST_CHUNK/gm,b=>{a.push(b);return""});p=`${u}\n${a.join("\n")}\n${p}`}n&&(e=`\nuniform float ${n};\n`,k=e+k,d=e+d);l&&(k=`${k}
void troikaVertexTransform${g}(inout vec3 position, inout vec3 normal, inout vec2 uv) {
${l}
}
`,f=`
troika_position_${g} = vec3(position);
troika_normal_${g} = vec3(normal);
troika_uv_${g} = vec2(uv);
troikaVertexTransform${g}(troika_position_${g}, troika_normal_${g}, troika_uv_${g});
${f}
`,b=`vec3 troika_position_${g};
vec3 troika_normal_${g};
vec2 troika_uv_${g};
${b}
`.replace(/\b(position|normal|uv)\b/g,(a,b,c,e)=>/\battribute\s+vec[23]\s+$/.test(e.substr(0,c))?b:`troika_${b}_${g}`),a.map&&0<a.map.channel||(b=b.replace(/\bMAP_UV\b/g,`troika_uv_${g}`)));b=x(b,g,k,f,h);c=x(c,g,d,m,p);return{vertexShader:b,fragmentShader:c}}function x(a,b,c,e,g){if(e||g||c)a=a.replace(y,`
${c}
void troikaOrigMain${b}() {`),a+=`
void main() {
${e}
troikaOrigMain${b}();
${g}
}`;return a}function I(a,b){return"uniforms"===a?void 0:"function"===typeof b?b.toString():b}function D(a){a=JSON.stringify(a,I);let b=z.get(a);null==b&&z.set(a,b=++J);return b}let y=/\bvoid\s+main\s*\(\s*\)\s*{/g,f=[];for(let a=0;256>a;a++)f[a]=(16>a?"0":"")+a.toString(16);let l=Object.assign||function(){let a=arguments[0];for(let b=1,c=arguments.length;b<c;b++){let c=arguments[b];if(c)for(let b in c)Object.prototype.hasOwnProperty.call(c,b)&&(a[b]=c[b])}return a},F=Date.now(),n=new WeakMap,v=new Map,
H=1E10,J=0,z=new Map,K={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"},A=null,B=new c.MeshStandardMaterial({color:16777215,side:c.DoubleSide});
class C extends c.Mesh{static getGeometry(){return A||(A=(new c.CylinderGeometry(1,1,1,6,64)).translate(0,.5,0))}constructor(){super(C.getGeometry(),B);this.pointA=new c.Vector3;this.controlA=new c.Vector3;this.controlB=new c.Vector3;this.pointB=new c.Vector3;this.radius=.01;this.dashArray=new c.Vector2;this.dashOffset=0;this.frustumCulled=!1}get material(){let a=this._derivedMaterial,b=this._baseMaterial||this._defaultMaterial||(this._defaultMaterial=B.clone());a&&a.baseMaterial===b||(a=this._derivedMaterial=
m(b,{chained:!0,uniforms:{pointA:{value:new c.Vector3},controlA:{value:new c.Vector3},controlB:{value:new c.Vector3},pointB:{value:new c.Vector3},radius:{value:.01},dashing:{value:new c.Vector3}},vertexDefs:"\nuniform vec3 pointA;\nuniform vec3 controlA;\nuniform vec3 controlB;\nuniform vec3 pointB;\nuniform float radius;\nvarying float bezierT;\n\nvec3 cubicBezier(vec3 p1, vec3 c1, vec3 c2, vec3 p2, float t) {\n float t2 = 1.0 - t;\n float b0 = t2 * t2 * t2;\n float b1 = 3.0 * t * t2 * t2;\n float b2 = 3.0 * t * t * t2;\n float b3 = t * t * t;\n return b0 * p1 + b1 * c1 + b2 * c2 + b3 * p2;\n}\n\nvec3 cubicBezierDerivative(vec3 p1, vec3 c1, vec3 c2, vec3 p2, float t) {\n float t2 = 1.0 - t;\n return -3.0 * p1 * t2 * t2 +\n c1 * (3.0 * t2 * t2 - 6.0 * t2 * t) +\n c2 * (6.0 * t2 * t - 3.0 * t * t) +\n 3.0 * p2 * t * t;\n}\n",
vertexTransform:'\nfloat t = position.y;\nbezierT = t;\nvec3 bezierCenterPos = cubicBezier(pointA, controlA, controlB, pointB, t);\nvec3 bezierDir = normalize(cubicBezierDerivative(pointA, controlA, controlB, pointB, t));\n\n// Make "sideways" always perpendicular to the camera ray; this ensures that any twists\n// in the cylinder occur where you won\'t see them: \nvec3 viewDirection = normalMatrix * vec3(0.0, 0.0, 1.0);\nif (bezierDir == viewDirection) {\n bezierDir = normalize(cubicBezierDerivative(pointA, controlA, controlB, pointB, t == 1.0 ? t - 0.0001 : t + 0.0001));\n}\nvec3 sideways = normalize(cross(bezierDir, viewDirection));\nvec3 upish = normalize(cross(sideways, bezierDir));\n\n// Build a matrix for transforming this disc in the cylinder:\nmat4 discTx;\ndiscTx[0].xyz = sideways * radius;\ndiscTx[1].xyz = bezierDir * radius;\ndiscTx[2].xyz = upish * radius;\ndiscTx[3].xyz = bezierCenterPos;\ndiscTx[3][3] = 1.0;\n\n// Apply transform, ignoring original y\nposition = (discTx * vec4(position.x, 0.0, position.z, 1.0)).xyz;\nnormal = normalize(mat3(discTx) * normal);\n',
fragmentDefs:"\nuniform vec3 dashing;\nvarying float bezierT;\n",fragmentMainIntro:"\nif (dashing.x + dashing.y > 0.0) {\n float dashFrac = mod(bezierT - dashing.z, dashing.x + dashing.y);\n if (dashFrac > dashing.x) {\n discard;\n }\n}\n"}),b.addEventListener("dispose",function e(){b.removeEventListener("dispose",e);a.dispose()}));return a}set material(a){this._baseMaterial=a}get customDepthMaterial(){return this.material.getDepthMaterial()}set customDepthMaterial(a){}get customDistanceMaterial(){return this.material.getDistanceMaterial()}set customDistanceMaterial(a){}onBeforeRender(){let {uniforms:a}=
this.material,{pointA:b,controlA:c,controlB:e,pointB:g,radius:f,dashArray:h,dashOffset:l}=this;a.pointA.value.copy(b);a.controlA.value.copy(c);a.controlB.value.copy(e);a.pointB.value.copy(g);a.radius.value=f;a.dashing.value.set(h.x,h.y,l||0)}raycast(){}}h.BezierMesh=C;h.createDerivedMaterial=m;h.expandShaderIncludes=q;h.getShaderUniformTypes=function(a){let b=/\buniform\s+(int|float|vec[234]|mat[34])\s+([A-Za-z_][\w]*)/g,c=Object.create(null),e;for(;null!==(e=b.exec(a));)c[e[2]]=e[1];return c};h.getShadersForMaterial=
function(a){let b=K[a.type];return b?c.ShaderLib[b]:a};h.invertMatrix4=function(a,b=new c.Matrix4){"function"===typeof b.invert?b.copy(a).invert():b.getInverse(a);return b};h.voidMainRegExp=y;Object.defineProperty(h,"__esModule",{value:!0})})