diff --git a/package.json b/package.json index e3df419b..490d7bc3 100644 --- a/package.json +++ b/package.json @@ -26,19 +26,18 @@ "module": "./dist/index.mjs", "sideEffects": false, "devDependencies": { - "@types/three": "^0.139.0", + "@types/three": "^0.128.0", "copyfiles": "^2.4.1", "prettier": "^2.2.1", "rimraf": "^3.0.2", - "three": "^0.139.2", - "typescript": "^4.2.4", + "three": "^0.128.0", + "typescript": "^4.7.4", "vite": "^4.3.8" }, "dependencies": { "@types/draco3d": "^1.4.0", "@types/offscreencanvas": "^2019.6.4", "@types/webxr": "^0.5.2", - "@webgpu/glslang": "^0.0.15", "chevrotain": "^10.1.2", "draco3d": "^1.4.1", "fflate": "^0.6.9", @@ -49,7 +48,7 @@ "zstddec": "^0.0.2" }, "peerDependencies": { - "three": ">=0.122.0" + "three": ">=0.128.0" }, "scripts": { "build": "rimraf dist && vite build && tsc --emitDeclarationOnly && copyfiles -u 1 \"src/**/*.d.ts\" dist", diff --git a/src/animation/AnimationClipCreator.js b/src/animation/AnimationClipCreator.js index 21b81135..2befa398 100644 --- a/src/animation/AnimationClipCreator.js +++ b/src/animation/AnimationClipCreator.js @@ -7,98 +7,98 @@ import { VectorKeyframeTrack, } from 'three' -const AnimationClipCreator = () => {} +class AnimationClipCreator { + static CreateRotationAnimation(period, axis = 'x') { + const times = [0, period], + values = [0, 360] -AnimationClipCreator.CreateRotationAnimation = (period, axis) => { - const times = [0, period], - values = [0, 360] + const trackName = '.rotation[' + axis + ']' - axis = axis || 'x' - const trackName = `.rotation[${axis}]` + const track = new NumberKeyframeTrack(trackName, times, values) - const track = new NumberKeyframeTrack(trackName, times, values) + return new AnimationClip(null, period, [track]) + } - return new AnimationClip(null, period, [track]) -} + static CreateScaleAxisAnimation(period, axis = 'x') { + const times = [0, period], + values = [0, 1] -AnimationClipCreator.CreateScaleAxisAnimation = (period, axis) => { - const times = [0, period], - values = [0, 1] + const trackName = '.scale[' + axis + ']' - axis = axis || 'x' - const trackName = `.scale[${axis}]` + const track = new NumberKeyframeTrack(trackName, times, values) - const track = new NumberKeyframeTrack(trackName, times, values) + return new AnimationClip(null, period, [track]) + } - return new AnimationClip(null, period, [track]) -} + static CreateShakeAnimation(duration, shakeScale) { + const times = [], + values = [], + tmp = new Vector3() -AnimationClipCreator.CreateShakeAnimation = (duration, shakeScale) => { - const times = [], - values = [], - tmp = new Vector3() + for (let i = 0; i < duration * 10; i++) { + times.push(i / 10) - for (let i = 0; i < duration * 10; i++) { - times.push(i / 10) + tmp + .set(Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0) + .multiply(shakeScale) + .toArray(values, values.length) + } - tmp - .set(Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0) - .multiply(shakeScale) - .toArray(values, values.length) - } + const trackName = '.position' - const trackName = '.position' + const track = new VectorKeyframeTrack(trackName, times, values) - const track = new VectorKeyframeTrack(trackName, times, values) + return new AnimationClip(null, duration, [track]) + } - return new AnimationClip(null, duration, [track]) -} + static CreatePulsationAnimation(duration, pulseScale) { + const times = [], + values = [], + tmp = new Vector3() -AnimationClipCreator.CreatePulsationAnimation = (duration, pulseScale) => { - const times = [], - values = [], - tmp = new Vector3() + for (let i = 0; i < duration * 10; i++) { + times.push(i / 10) - for (let i = 0; i < duration * 10; i++) { - times.push(i / 10) + const scaleFactor = Math.random() * pulseScale + tmp.set(scaleFactor, scaleFactor, scaleFactor).toArray(values, values.length) + } - const scaleFactor = Math.random() * pulseScale - tmp.set(scaleFactor, scaleFactor, scaleFactor).toArray(values, values.length) - } + const trackName = '.scale' - const trackName = '.scale' + const track = new VectorKeyframeTrack(trackName, times, values) - const track = new VectorKeyframeTrack(trackName, times, values) + return new AnimationClip(null, duration, [track]) + } - return new AnimationClip(null, duration, [track]) -} + static CreateVisibilityAnimation(duration) { + const times = [0, duration / 2, duration], + values = [true, false, true] -AnimationClipCreator.CreateVisibilityAnimation = (duration) => { - const times = [0, duration / 2, duration], - values = [true, false, true] + const trackName = '.visible' - const trackName = '.visible' + const track = new BooleanKeyframeTrack(trackName, times, values) - const track = new BooleanKeyframeTrack(trackName, times, values) + return new AnimationClip(null, duration, [track]) + } - return new AnimationClip(null, duration, [track]) -} + static CreateMaterialColorAnimation(duration, colors) { + const times = [], + values = [], + timeStep = duration / colors.length -AnimationClipCreator.CreateMaterialColorAnimation = (duration, colors) => { - const times = [], - values = [], - timeStep = duration / colors.length + for (let i = 0; i < colors.length; i++) { + times.push(i * timeStep) - for (let i = 0; i <= colors.length; i++) { - times.push(i * timeStep) - values.push(colors[i % colors.length]) - } + const color = colors[i] + values.push(color.r, color.g, color.b) + } - const trackName = '.material[0].color' + const trackName = '.material.color' - const track = new ColorKeyframeTrack(trackName, times, values) + const track = new ColorKeyframeTrack(trackName, times, values) - return new AnimationClip(null, duration, [track]) + return new AnimationClip(null, duration, [track]) + } } export { AnimationClipCreator } diff --git a/src/animation/CCDIKSolver.d.ts b/src/animation/CCDIKSolver.d.ts index aac25725..4d72de98 100644 --- a/src/animation/CCDIKSolver.d.ts +++ b/src/animation/CCDIKSolver.d.ts @@ -1,13 +1,17 @@ -import { Object3D, SkinnedMesh } from 'three' +import { Object3D, SkinnedMesh, Vector3 } from 'three' // tslint:disable-next-line:interface-name export interface IKS { effector: number iteration: number - links: { + links: Array<{ enabled: boolean index: number - } + limitation?: Vector3 + rotationMin?: Vector3 + rotationMax?: Vector3 + }> + minAngle: number maxAngle: number target: number } @@ -21,5 +25,6 @@ export class CCDIKSolver { } export class CCDIKHelper extends Object3D { - constructor(mesh: SkinnedMesh, iks: IKS[]) + constructor(mesh: SkinnedMesh, iks?: IKS[], sphereSize?: number) + dispose(): void } diff --git a/src/animation/CCDIKSolver.js b/src/animation/CCDIKSolver.js index 21eeeedb..75fff7e4 100644 --- a/src/animation/CCDIKSolver.js +++ b/src/animation/CCDIKSolver.js @@ -13,10 +13,227 @@ import { Vector3, } from 'three' -class CCDIKHelper extends Object3D { - _m = new Matrix4() - _v = new Vector3() +const _q = /* @__PURE__ */ new Quaternion() +const _targetPos = /* @__PURE__ */ new Vector3() +const _targetVec = /* @__PURE__ */ new Vector3() +const _effectorPos = /* @__PURE__ */ new Vector3() +const _effectorVec = /* @__PURE__ */ new Vector3() +const _linkPos = /* @__PURE__ */ new Vector3() +const _invLinkQ = /* @__PURE__ */ new Quaternion() +const _linkScale = /* @__PURE__ */ new Vector3() +const _axis = /* @__PURE__ */ new Vector3() +const _vector = /* @__PURE__ */ new Vector3() +const _matrix = /* @__PURE__ */ new Matrix4() + +/** + * CCD Algorithm + * - https://sites.google.com/site/auraliusproject/ccd-algorithm + * + * // ik parameter example + * // + * // target, effector, index in links are bone index in skeleton.bones. + * // the bones relation should be + * // <-- parent child --> + * // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector + * iks = [ { + * target: 1, + * effector: 2, + * links: [ { index: 5, limitation: new Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ], + * iteration: 10, + * minAngle: 0.0, + * maxAngle: 1.0, + * } ]; + */ + +class CCDIKSolver { + /** + * @param {THREE.SkinnedMesh} mesh + * @param {Array} iks + */ + constructor(mesh, iks = []) { + this.mesh = mesh + this.iks = iks + + this._valid() + } + + /** + * Update all IK bones. + * + * @return {CCDIKSolver} + */ + update() { + const iks = this.iks + + for (let i = 0, il = iks.length; i < il; i++) { + this.updateOne(iks[i]) + } + + return this + } + + /** + * Update one IK bone + * + * @param {Object} ik parameter + * @return {CCDIKSolver} + */ + updateOne(ik) { + const bones = this.mesh.skeleton.bones + + // for reference overhead reduction in loop + const math = Math + + const effector = bones[ik.effector] + const target = bones[ik.target] + + // don't use getWorldPosition() here for the performance + // because it calls updateMatrixWorld( true ) inside. + _targetPos.setFromMatrixPosition(target.matrixWorld) + + const links = ik.links + const iteration = ik.iteration !== undefined ? ik.iteration : 1 + + for (let i = 0; i < iteration; i++) { + let rotated = false + + for (let j = 0, jl = links.length; j < jl; j++) { + const link = bones[links[j].index] + + // skip this link and following links. + // this skip is used for MMD performance optimization. + if (links[j].enabled === false) break + + const limitation = links[j].limitation + const rotationMin = links[j].rotationMin + const rotationMax = links[j].rotationMax + + // don't use getWorldPosition/Quaternion() here for the performance + // because they call updateMatrixWorld( true ) inside. + link.matrixWorld.decompose(_linkPos, _invLinkQ, _linkScale) + _invLinkQ.invert() + _effectorPos.setFromMatrixPosition(effector.matrixWorld) + + // work in link world + _effectorVec.subVectors(_effectorPos, _linkPos) + _effectorVec.applyQuaternion(_invLinkQ) + _effectorVec.normalize() + + _targetVec.subVectors(_targetPos, _linkPos) + _targetVec.applyQuaternion(_invLinkQ) + _targetVec.normalize() + + let angle = _targetVec.dot(_effectorVec) + + if (angle > 1.0) { + angle = 1.0 + } else if (angle < -1.0) { + angle = -1.0 + } + + angle = math.acos(angle) + + // skip if changing angle is too small to prevent vibration of bone + if (angle < 1e-5) continue + + if (ik.minAngle !== undefined && angle < ik.minAngle) { + angle = ik.minAngle + } + + if (ik.maxAngle !== undefined && angle > ik.maxAngle) { + angle = ik.maxAngle + } + + _axis.crossVectors(_effectorVec, _targetVec) + _axis.normalize() + + _q.setFromAxisAngle(_axis, angle) + link.quaternion.multiply(_q) + + // TODO: re-consider the limitation specification + if (limitation !== undefined) { + let c = link.quaternion.w + if (c > 1.0) c = 1.0 + + const c2 = math.sqrt(1 - c * c) + link.quaternion.set(limitation.x * c2, limitation.y * c2, limitation.z * c2, c) + } + + if (rotationMin !== undefined) { + link.rotation.setFromVector3(_vector.setFromEuler(link.rotation).max(rotationMin)) + } + + if (rotationMax !== undefined) { + link.rotation.setFromVector3(_vector.setFromEuler(link.rotation).min(rotationMax)) + } + + link.updateMatrixWorld(true) + + rotated = true + } + + if (!rotated) break + } + + return this + } + + /** + * Creates Helper + * + * @return {CCDIKHelper} + */ + createHelper() { + return new CCDIKHelper(this.mesh, this.iks) + } + + // private methods + + _valid() { + const iks = this.iks + const bones = this.mesh.skeleton.bones + + for (let i = 0, il = iks.length; i < il; i++) { + const ik = iks[i] + const effector = bones[ik.effector] + const links = ik.links + let link0, link1 + + link0 = effector + + for (let j = 0, jl = links.length; j < jl; j++) { + link1 = bones[links[j].index] + + if (link0.parent !== link1) { + console.warn('THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name) + } + + link0 = link1 + } + } + } +} + +function getPosition(bone, matrixWorldInv) { + return _vector.setFromMatrixPosition(bone.matrixWorld).applyMatrix4(matrixWorldInv) +} + +function setPositionOfBoneToAttributeArray(array, index, bone, matrixWorldInv) { + const v = getPosition(bone, matrixWorldInv) + + array[index * 3 + 0] = v.x + array[index * 3 + 1] = v.y + array[index * 3 + 2] = v.z +} + +/** + * Visualize IK bones + * + * @param {SkinnedMesh} mesh + * @param {Array} iks + */ +class CCDIKHelper extends Object3D { constructor(mesh, iks = [], sphereSize = 0.25) { super() @@ -56,57 +273,12 @@ class CCDIKHelper extends Object3D { transparent: true, }) - function createLineGeometry(ik) { - const geometry = new BufferGeometry() - const vertices = new Float32Array((2 + ik.links.length) * 3) - geometry.setAttribute('position', new BufferAttribute(vertices, 3)) - - return geometry - } - - const scope = this - function createTargetMesh() { - return new Mesh(scope.sphereGeometry, scope.targetSphereMaterial) - } - - function createEffectorMesh() { - return new Mesh(scope.sphereGeometry, scope.effectorSphereMaterial) - } - - function createLinkMesh() { - return new Mesh(scope.sphereGeometry, scope.linkSphereMaterial) - } - - function createLine(ik) { - return new Line(createLineGeometry(ik), scope.lineMaterial) - } - - for (let i = 0, il = iks.length; i < il; i++) { - const ik = iks[i] - - this.add(createTargetMesh()) - this.add(createEffectorMesh()) - - for (let j = 0, jl = ik.links.length; j < jl; j++) { - this.add(createLinkMesh()) - } - - this.add(createLine(ik)) - } - } - - _getPosition(bone, matrixWorldInv) { - return this._v.setFromMatrixPosition(bone.matrixWorld).applyMatrix4(matrixWorldInv) - } - - _setPositionOfBoneToAttributeArray(array, index, bone, matrixWorldInv) { - const v = this._getPosition(bone, matrixWorldInv) - - array[index * 3 + 0] = v.x - array[index * 3 + 1] = v.y - array[index * 3 + 2] = v.z + this._init() } + /** + * Updates IK bones visualization. + */ updateMatrixWorld(force) { const mesh = this.root @@ -116,7 +288,7 @@ class CCDIKHelper extends Object3D { const iks = this.iks const bones = mesh.skeleton.bones - this._m.copy(mesh.matrixWorld).invert() + _matrix.copy(mesh.matrixWorld).invert() for (let i = 0, il = iks.length; i < il; i++) { const ik = iks[i] @@ -127,8 +299,8 @@ class CCDIKHelper extends Object3D { const targetMesh = this.children[offset++] const effectorMesh = this.children[offset++] - targetMesh.position.copy(this._getPosition(targetBone, this._m)) - effectorMesh.position.copy(this._getPosition(effectorBone, this._m)) + targetMesh.position.copy(getPosition(targetBone, _matrix)) + effectorMesh.position.copy(getPosition(effectorBone, _matrix)) for (let j = 0, jl = ik.links.length; j < jl; j++) { const link = ik.links[j] @@ -136,19 +308,19 @@ class CCDIKHelper extends Object3D { const linkMesh = this.children[offset++] - linkMesh.position.copy(this._getPosition(linkBone, this._m)) + linkMesh.position.copy(getPosition(linkBone, _matrix)) } const line = this.children[offset++] const array = line.geometry.attributes.position.array - this._setPositionOfBoneToAttributeArray(array, 0, targetBone, this._m) - this._setPositionOfBoneToAttributeArray(array, 1, effectorBone, this._m) + setPositionOfBoneToAttributeArray(array, 0, targetBone, _matrix) + setPositionOfBoneToAttributeArray(array, 1, effectorBone, _matrix) for (let j = 0, jl = ik.links.length; j < jl; j++) { const link = ik.links[j] const linkBone = bones[link.index] - this._setPositionOfBoneToAttributeArray(array, j + 2, linkBone, this._m) + setPositionOfBoneToAttributeArray(array, j + 2, linkBone, _matrix) } line.geometry.attributes.position.needsUpdate = true @@ -159,174 +331,69 @@ class CCDIKHelper extends Object3D { super.updateMatrixWorld(force) } -} - -/** - * CCD Algorithm - * - https://sites.google.com/site/auraliusproject/ccd-algorithm - * - * // ik parameter example - * // - * // target, effector, index in links are bone index in skeleton.bones. - * // the bones relation should be - * // <-- parent child --> - * // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector - * iks = [ { - * target: 1, - * effector: 2, - * links: [ { index: 5, limitation: new Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ], - * iteration: 10, - * minAngle: 0.0, - * maxAngle: 1.0, - * } ]; - */ - -class CCDIKSolver { - q = new Quaternion() - targetPos = new Vector3() - targetVec = new Vector3() - effectorPos = new Vector3() - effectorVec = new Vector3() - linkPos = new Vector3() - invLinkQ = new Quaternion() - linkScale = new Vector3() - axis = new Vector3() - vector = new Vector3() - - constructor(mesh, iks) { - this.mesh = mesh - this.iks = iks - const bones = this.mesh.skeleton.bones + /** + * Frees the GPU-related resources allocated by this instance. Call this method whenever this instance is no longer used in your app. + */ + dispose() { + this.sphereGeometry.dispose() - for (let i = 0, il = this.iks.length; i < il; i++) { - const ik = this.iks[i] - const effector = bones[ik.effector] - const links = ik.links - let link0, link1 + this.targetSphereMaterial.dispose() + this.effectorSphereMaterial.dispose() + this.linkSphereMaterial.dispose() + this.lineMaterial.dispose() - link0 = effector + const children = this.children - for (let j = 0, jl = links.length; j < jl; j++) { - link1 = bones[links[j].index] + for (let i = 0; i < children.length; i++) { + const child = children[i] - if (link0.parent !== link1) { - console.warn(`THREE.CCDIKSolver: bone ${link0.name} is not the child of bone ${link1.name}`) - } - - link0 = link1 - } + if (child.isLine) child.geometry.dispose() } } - update() { - const bones = this.mesh.skeleton.bones - const iks = this.iks - - // for reference overhead reduction in loop - const math = Math - - for (let i = 0, il = iks.length; i < il; i++) { - const ik = iks[i] - const effector = bones[ik.effector] - const target = bones[ik.target] - - // don't use getWorldPosition() here for the performance - // because it calls updateMatrixWorld( true ) inside. - this.targetPos.setFromMatrixPosition(target.matrixWorld) - - const links = ik.links - const iteration = ik.iteration !== undefined ? ik.iteration : 1 - - for (let j = 0; j < iteration; j++) { - let rotated = false - - for (let k = 0, kl = links.length; k < kl; k++) { - const link = bones[links[k].index] - - // skip this link and following links. - // this skip is used for MMD performance optimization. - if (links[k].enabled === false) break - - const limitation = links[k].limitation - const rotationMin = links[k].rotationMin - const rotationMax = links[k].rotationMax - - // don't use getWorldPosition/Quaternion() here for the performance - // because they call updateMatrixWorld( true ) inside. - link.matrixWorld.decompose(this.linkPos, this.invLinkQ, this.linkScale) - this.invLinkQ.invert() - this.effectorPos.setFromMatrixPosition(effector.matrixWorld) - - // work in link world - this.effectorVec.subVectors(this.effectorPos, this.linkPos) - this.effectorVec.applyQuaternion(this.invLinkQ) - this.effectorVec.normalize() - - this.targetVec.subVectors(this.targetPos, this.linkPos) - this.targetVec.applyQuaternion(this.invLinkQ) - this.targetVec.normalize() - - let angle = this.targetVec.dot(this.effectorVec) + // private method - if (angle > 1.0) { - angle = 1.0 - } else if (angle < -1.0) { - angle = -1.0 - } - - angle = math.acos(angle) - - // skip if changing angle is too small to prevent vibration of bone - // Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js - if (angle < 1e-5) continue - - if (ik.minAngle !== undefined && angle < ik.minAngle) { - angle = ik.minAngle - } - - if (ik.maxAngle !== undefined && angle > ik.maxAngle) { - angle = ik.maxAngle - } - - this.axis.crossVectors(this.effectorVec, this.targetVec) - this.axis.normalize() + _init() { + const scope = this + const iks = this.iks - this.q.setFromAxisAngle(this.axis, angle) - link.quaternion.multiply(this.q) + function createLineGeometry(ik) { + const geometry = new BufferGeometry() + const vertices = new Float32Array((2 + ik.links.length) * 3) + geometry.setAttribute('position', new BufferAttribute(vertices, 3)) - // TODO: re-consider the limitation specification - if (limitation !== undefined) { - let c = link.quaternion.w + return geometry + } - if (c > 1.0) c = 1.0 + function createTargetMesh() { + return new Mesh(scope.sphereGeometry, scope.targetSphereMaterial) + } - const c2 = math.sqrt(1 - c * c) - link.quaternion.set(limitation.x * c2, limitation.y * c2, limitation.z * c2, c) - } + function createEffectorMesh() { + return new Mesh(scope.sphereGeometry, scope.effectorSphereMaterial) + } - if (rotationMin !== undefined) { - link.rotation.setFromVector3(this.vector.setFromEuler(link.rotation).max(rotationMin)) - } + function createLinkMesh() { + return new Mesh(scope.sphereGeometry, scope.linkSphereMaterial) + } - if (rotationMax !== undefined) { - link.rotation.setFromVector3(this.vector.setFromEuler(link.rotation).min(rotationMax)) - } + function createLine(ik) { + return new Line(createLineGeometry(ik), scope.lineMaterial) + } - link.updateMatrixWorld(true) + for (let i = 0, il = iks.length; i < il; i++) { + const ik = iks[i] - rotated = true - } + this.add(createTargetMesh()) + this.add(createEffectorMesh()) - if (!rotated) break + for (let j = 0, jl = ik.links.length; j < jl; j++) { + this.add(createLinkMesh()) } - } - - return this - } - createHelper() { - return new CCDIKHelper(this.mesh, this.mesh.geometry.userData.MMD.iks) + this.add(createLine(ik)) + } } } diff --git a/src/animation/MMDAnimationHelper.js b/src/animation/MMDAnimationHelper.js index 7581b9ee..396c7424 100644 --- a/src/animation/MMDAnimationHelper.js +++ b/src/animation/MMDAnimationHelper.js @@ -14,17 +14,14 @@ import { MMDPhysics } from '../animation/MMDPhysics' * TODO * - more precise grant skinning support. */ - -const MMDAnimationHelper = (() => { +class MMDAnimationHelper { /** * @param {Object} params - (optional) * @param {boolean} params.sync - Whether animation durations of added objects are synched. Default is true. * @param {Number} params.afterglow - Default is 0.0. * @param {boolean} params.resetPhysicsOnLoop - Default is true. */ - function MMDAnimationHelper(params) { - params = params || {} - + constructor(params = {}) { this.meshes = [] this.camera = null @@ -40,6 +37,7 @@ const MMDAnimationHelper = (() => { sync: params.sync !== undefined ? params.sync : true, afterglow: params.afterglow !== undefined ? params.afterglow : 0.0, resetPhysicsOnLoop: params.resetPhysicsOnLoop !== undefined ? params.resetPhysicsOnLoop : true, + pmxAnimation: params.pmxAnimation !== undefined ? params.pmxAnimation : false, } this.enabled = { @@ -50,378 +48,397 @@ const MMDAnimationHelper = (() => { cameraAnimation: true, } - this.onBeforePhysics = () => /* mesh */ {} + this.onBeforePhysics = function (/* mesh */) {} // experimental this.sharedPhysics = false this.masterPhysics = null } - MMDAnimationHelper.prototype = { - constructor: MMDAnimationHelper, - - /** - * Adds an Three.js Object to helper and setups animation. - * The anmation durations of added objects are synched - * if this.configuration.sync is true. - * - * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object - * @param {Object} params - (optional) - * @param {THREE.AnimationClip|Array} params.animation - Only for THREE.SkinnedMesh and THREE.Camera. Default is undefined. - * @param {boolean} params.physics - Only for THREE.SkinnedMesh. Default is true. - * @param {Integer} params.warmup - Only for THREE.SkinnedMesh and physics is true. Default is 60. - * @param {Number} params.unitStep - Only for THREE.SkinnedMesh and physics is true. Default is 1 / 65. - * @param {Integer} params.maxStepNum - Only for THREE.SkinnedMesh and physics is true. Default is 3. - * @param {Vector3} params.gravity - Only for THREE.SkinnedMesh and physics is true. Default ( 0, - 9.8 * 10, 0 ). - * @param {Number} params.delayTime - Only for THREE.Audio. Default is 0.0. - * @return {MMDAnimationHelper} - */ - add: function (object, params) { - params = params || {} - - if (object.isSkinnedMesh) { - this._addMesh(object, params) - } else if (object.isCamera) { - this._setupCamera(object, params) - } else if (object.type === 'Audio') { - this._setupAudio(object, params) - } else { - throw new Error( - 'THREE.MMDAnimationHelper.add: ' + - 'accepts only ' + - 'THREE.SkinnedMesh or ' + - 'THREE.Camera or ' + - 'THREE.Audio instance.', - ) - } + /** + * Adds an Three.js Object to helper and setups animation. + * The anmation durations of added objects are synched + * if this.configuration.sync is true. + * + * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object + * @param {Object} params - (optional) + * @param {THREE.AnimationClip|Array} params.animation - Only for THREE.SkinnedMesh and THREE.Camera. Default is undefined. + * @param {boolean} params.physics - Only for THREE.SkinnedMesh. Default is true. + * @param {Integer} params.warmup - Only for THREE.SkinnedMesh and physics is true. Default is 60. + * @param {Number} params.unitStep - Only for THREE.SkinnedMesh and physics is true. Default is 1 / 65. + * @param {Integer} params.maxStepNum - Only for THREE.SkinnedMesh and physics is true. Default is 3. + * @param {Vector3} params.gravity - Only for THREE.SkinnedMesh and physics is true. Default ( 0, - 9.8 * 10, 0 ). + * @param {Number} params.delayTime - Only for THREE.Audio. Default is 0.0. + * @return {MMDAnimationHelper} + */ + add(object, params = {}) { + if (object.isSkinnedMesh) { + this._addMesh(object, params) + } else if (object.isCamera) { + this._setupCamera(object, params) + } else if (object.type === 'Audio') { + this._setupAudio(object, params) + } else { + throw new Error( + 'THREE.MMDAnimationHelper.add: ' + + 'accepts only ' + + 'THREE.SkinnedMesh or ' + + 'THREE.Camera or ' + + 'THREE.Audio instance.', + ) + } - if (this.configuration.sync) this._syncDuration() - - return this - }, - - /** - * Removes an Three.js Object from helper. - * - * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object - * @return {MMDAnimationHelper} - */ - remove: function (object) { - if (object.isSkinnedMesh) { - this._removeMesh(object) - } else if (object.isCamera) { - this._clearCamera(object) - } else if (object.type === 'Audio') { - this._clearAudio(object) - } else { - throw new Error( - 'THREE.MMDAnimationHelper.remove: ' + - 'accepts only ' + - 'THREE.SkinnedMesh or ' + - 'THREE.Camera or ' + - 'THREE.Audio instance.', - ) - } + if (this.configuration.sync) this._syncDuration() - if (this.configuration.sync) this._syncDuration() + return this + } - return this - }, + /** + * Removes an Three.js Object from helper. + * + * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object + * @return {MMDAnimationHelper} + */ + remove(object) { + if (object.isSkinnedMesh) { + this._removeMesh(object) + } else if (object.isCamera) { + this._clearCamera(object) + } else if (object.type === 'Audio') { + this._clearAudio(object) + } else { + throw new Error( + 'THREE.MMDAnimationHelper.remove: ' + + 'accepts only ' + + 'THREE.SkinnedMesh or ' + + 'THREE.Camera or ' + + 'THREE.Audio instance.', + ) + } - /** - * Updates the animation. - * - * @param {Number} delta - * @return {MMDAnimationHelper} - */ - update: function (delta) { - if (this.audioManager !== null) this.audioManager.control(delta) + if (this.configuration.sync) this._syncDuration() - for (let i = 0; i < this.meshes.length; i++) { - this._animateMesh(this.meshes[i], delta) - } + return this + } - if (this.sharedPhysics) this._updateSharedPhysics(delta) + /** + * Updates the animation. + * + * @param {Number} delta + * @return {MMDAnimationHelper} + */ + update(delta) { + if (this.audioManager !== null) this.audioManager.control(delta) - if (this.camera !== null) this._animateCamera(this.camera, delta) + for (let i = 0; i < this.meshes.length; i++) { + this._animateMesh(this.meshes[i], delta) + } - return this - }, + if (this.sharedPhysics) this._updateSharedPhysics(delta) - /** - * Changes the pose of SkinnedMesh as VPD specifies. - * - * @param {THREE.SkinnedMesh} mesh - * @param {Object} vpd - VPD content parsed MMDParser - * @param {Object} params - (optional) - * @param {boolean} params.resetPose - Default is true. - * @param {boolean} params.ik - Default is true. - * @param {boolean} params.grant - Default is true. - * @return {MMDAnimationHelper} - */ - pose: function (mesh, vpd, params) { - params = params || {} + if (this.camera !== null) this._animateCamera(this.camera, delta) - if (params.resetPose !== false) mesh.pose() + return this + } - const bones = mesh.skeleton.bones - const boneParams = vpd.bones + /** + * Changes the pose of SkinnedMesh as VPD specifies. + * + * @param {THREE.SkinnedMesh} mesh + * @param {Object} vpd - VPD content parsed MMDParser + * @param {Object} params - (optional) + * @param {boolean} params.resetPose - Default is true. + * @param {boolean} params.ik - Default is true. + * @param {boolean} params.grant - Default is true. + * @return {MMDAnimationHelper} + */ + pose(mesh, vpd, params = {}) { + if (params.resetPose !== false) mesh.pose() - const boneNameDictionary = {} + const bones = mesh.skeleton.bones + const boneParams = vpd.bones - for (let i = 0, il = bones.length; i < il; i++) { - boneNameDictionary[bones[i].name] = i - } + const boneNameDictionary = {} - const vector = new Vector3() - const quaternion = new Quaternion() + for (let i = 0, il = bones.length; i < il; i++) { + boneNameDictionary[bones[i].name] = i + } - for (let i = 0, il = boneParams.length; i < il; i++) { - const boneParam = boneParams[i] - const boneIndex = boneNameDictionary[boneParam.name] + const vector = new Vector3() + const quaternion = new Quaternion() - if (boneIndex === undefined) continue + for (let i = 0, il = boneParams.length; i < il; i++) { + const boneParam = boneParams[i] + const boneIndex = boneNameDictionary[boneParam.name] - const bone = bones[boneIndex] - bone.position.add(vector.fromArray(boneParam.translation)) - bone.quaternion.multiply(quaternion.fromArray(boneParam.quaternion)) - } + if (boneIndex === undefined) continue + + const bone = bones[boneIndex] + bone.position.add(vector.fromArray(boneParam.translation)) + bone.quaternion.multiply(quaternion.fromArray(boneParam.quaternion)) + } - mesh.updateMatrixWorld(true) + mesh.updateMatrixWorld(true) + // PMX animation system special path + if (this.configuration.pmxAnimation && mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx') { + const sortedBonesData = this._sortBoneDataArray(mesh.geometry.userData.MMD.bones.slice()) + const ikSolver = params.ik !== false ? this._createCCDIKSolver(mesh) : null + const grantSolver = params.grant !== false ? this.createGrantSolver(mesh) : null + this._animatePMXMesh(mesh, sortedBonesData, ikSolver, grantSolver) + } else { if (params.ik !== false) { - this._createCCDIKSolver(mesh).update(params.saveOriginalBonesBeforeIK) // this param is experimental + this._createCCDIKSolver(mesh).update() } if (params.grant !== false) { this.createGrantSolver(mesh).update() } + } - return this - }, - - /** - * Enabes/Disables an animation feature. - * - * @param {string} key - * @param {boolean} enabled - * @return {MMDAnimationHelper} - */ - enable: function (key, enabled) { - if (this.enabled[key] === undefined) { - throw new Error(`THREE.MMDAnimationHelper.enable: unknown key ${key}`) - } + return this + } - this.enabled[key] = enabled + /** + * Enabes/Disables an animation feature. + * + * @param {string} key + * @param {boolean} enabled + * @return {MMDAnimationHelper} + */ + enable(key, enabled) { + if (this.enabled[key] === undefined) { + throw new Error('THREE.MMDAnimationHelper.enable: ' + 'unknown key ' + key) + } - if (key === 'physics') { - for (let i = 0, il = this.meshes.length; i < il; i++) { - this._optimizeIK(this.meshes[i], enabled) - } - } + this.enabled[key] = enabled - return this - }, + if (key === 'physics') { + for (let i = 0, il = this.meshes.length; i < il; i++) { + this._optimizeIK(this.meshes[i], enabled) + } + } - /** - * Creates an GrantSolver instance. - * - * @param {THREE.SkinnedMesh} mesh - * @return {GrantSolver} - */ - createGrantSolver: function (mesh) { - return new GrantSolver(mesh, mesh.geometry.userData.MMD.grants) - }, + return this + } - // private methods + /** + * Creates an GrantSolver instance. + * + * @param {THREE.SkinnedMesh} mesh + * @return {GrantSolver} + */ + createGrantSolver(mesh) { + return new GrantSolver(mesh, mesh.geometry.userData.MMD.grants) + } - _addMesh: function (mesh, params) { - if (this.meshes.indexOf(mesh) >= 0) { - throw new Error(`THREE.MMDAnimationHelper._addMesh: SkinnedMesh '${mesh.name}' has already been added.`) - } + // private methods - this.meshes.push(mesh) - this.objects.set(mesh, { looped: false }) + _addMesh(mesh, params) { + if (this.meshes.indexOf(mesh) >= 0) { + throw new Error('THREE.MMDAnimationHelper._addMesh: ' + "SkinnedMesh '" + mesh.name + "' has already been added.") + } - this._setupMeshAnimation(mesh, params.animation) + this.meshes.push(mesh) + this.objects.set(mesh, { looped: false }) - if (params.physics !== false) { - this._setupMeshPhysics(mesh, params) - } + this._setupMeshAnimation(mesh, params.animation) - return this - }, + if (params.physics !== false) { + this._setupMeshPhysics(mesh, params) + } - _setupCamera: function (camera, params) { - if (this.camera === camera) { - throw new Error(`THREE.MMDAnimationHelper._setupCamera: Camera '${camera.name}' has already been set.`) - } + return this + } - if (this.camera) this.clearCamera(this.camera) + _setupCamera(camera, params) { + if (this.camera === camera) { + throw new Error('THREE.MMDAnimationHelper._setupCamera: ' + "Camera '" + camera.name + "' has already been set.") + } - this.camera = camera + if (this.camera) this.clearCamera(this.camera) - camera.add(this.cameraTarget) + this.camera = camera - this.objects.set(camera, {}) + camera.add(this.cameraTarget) - if (params.animation !== undefined) { - this._setupCameraAnimation(camera, params.animation) - } + this.objects.set(camera, {}) - return this - }, + if (params.animation !== undefined) { + this._setupCameraAnimation(camera, params.animation) + } - _setupAudio: function (audio, params) { - if (this.audio === audio) { - throw new Error(`THREE.MMDAnimationHelper._setupAudio: Audio '${audio.name}' has already been set.`) - } + return this + } - if (this.audio) this.clearAudio(this.audio) + _setupAudio(audio, params) { + if (this.audio === audio) { + throw new Error('THREE.MMDAnimationHelper._setupAudio: ' + "Audio '" + audio.name + "' has already been set.") + } - this.audio = audio - this.audioManager = new AudioManager(audio, params) + if (this.audio) this.clearAudio(this.audio) - this.objects.set(this.audioManager, { - duration: this.audioManager.duration, - }) + this.audio = audio + this.audioManager = new AudioManager(audio, params) - return this - }, + this.objects.set(this.audioManager, { + duration: this.audioManager.duration, + }) - _removeMesh: function (mesh) { - let found = false - let writeIndex = 0 + return this + } - for (let i = 0, il = this.meshes.length; i < il; i++) { - if (this.meshes[i] === mesh) { - this.objects.delete(mesh) - found = true + _removeMesh(mesh) { + let found = false + let writeIndex = 0 - continue - } + for (let i = 0, il = this.meshes.length; i < il; i++) { + if (this.meshes[i] === mesh) { + this.objects.delete(mesh) + found = true - this.meshes[writeIndex++] = this.meshes[i] + continue } - if (!found) { - throw new Error(`THREE.MMDAnimationHelper._removeMesh: SkinnedMesh '${mesh.name}' has not been added yet.`) - } + this.meshes[writeIndex++] = this.meshes[i] + } - this.meshes.length = writeIndex + if (!found) { + throw new Error( + 'THREE.MMDAnimationHelper._removeMesh: ' + "SkinnedMesh '" + mesh.name + "' has not been added yet.", + ) + } - return this - }, + this.meshes.length = writeIndex - _clearCamera: function (camera) { - if (camera !== this.camera) { - throw new Error(`THREE.MMDAnimationHelper._clearCamera: Camera '${camera.name}' has not been set yet.`) - } + return this + } - this.camera.remove(this.cameraTarget) + _clearCamera(camera) { + if (camera !== this.camera) { + throw new Error('THREE.MMDAnimationHelper._clearCamera: ' + "Camera '" + camera.name + "' has not been set yet.") + } - this.objects.delete(this.camera) - this.camera = null + this.camera.remove(this.cameraTarget) - return this - }, + this.objects.delete(this.camera) + this.camera = null - _clearAudio: function (audio) { - if (audio !== this.audio) { - throw new Error(`THREE.MMDAnimationHelper._clearAudio: Audio '${audio.name}' has not been set yet.`) - } + return this + } - this.objects.delete(this.audioManager) + _clearAudio(audio) { + if (audio !== this.audio) { + throw new Error('THREE.MMDAnimationHelper._clearAudio: ' + "Audio '" + audio.name + "' has not been set yet.") + } - this.audio = null - this.audioManager = null + this.objects.delete(this.audioManager) - return this - }, + this.audio = null + this.audioManager = null - _setupMeshAnimation: function (mesh, animation) { - const objects = this.objects.get(mesh) + return this + } - if (animation !== undefined) { - const animations = Array.isArray(animation) ? animation : [animation] + _setupMeshAnimation(mesh, animation) { + const objects = this.objects.get(mesh) - objects.mixer = new AnimationMixer(mesh) + if (animation !== undefined) { + const animations = Array.isArray(animation) ? animation : [animation] - for (let i = 0, il = animations.length; i < il; i++) { - objects.mixer.clipAction(animations[i]).play() - } + objects.mixer = new AnimationMixer(mesh) - // TODO: find a workaround not to access ._clip looking like a private property - objects.mixer.addEventListener('loop', (event) => { - const tracks = event.action._clip.tracks + for (let i = 0, il = animations.length; i < il; i++) { + objects.mixer.clipAction(animations[i]).play() + } - if (tracks.length > 0 && tracks[0].name.slice(0, 6) !== '.bones') return + // TODO: find a workaround not to access ._clip looking like a private property + objects.mixer.addEventListener('loop', function (event) { + const tracks = event.action._clip.tracks - objects.looped = true - }) - } + if (tracks.length > 0 && tracks[0].name.slice(0, 6) !== '.bones') return - objects.ikSolver = this._createCCDIKSolver(mesh) - objects.grantSolver = this.createGrantSolver(mesh) + objects.looped = true + }) + } - return this - }, + objects.ikSolver = this._createCCDIKSolver(mesh) + objects.grantSolver = this.createGrantSolver(mesh) - _setupCameraAnimation: function (camera, animation) { - const animations = Array.isArray(animation) ? animation : [animation] + return this + } - const objects = this.objects.get(camera) + _setupCameraAnimation(camera, animation) { + const animations = Array.isArray(animation) ? animation : [animation] - objects.mixer = new AnimationMixer(camera) + const objects = this.objects.get(camera) - for (let i = 0, il = animations.length; i < il; i++) { - objects.mixer.clipAction(animations[i]).play() - } - }, + objects.mixer = new AnimationMixer(camera) - _setupMeshPhysics: function (mesh, params) { - const objects = this.objects.get(mesh) + for (let i = 0, il = animations.length; i < il; i++) { + objects.mixer.clipAction(animations[i]).play() + } + } - // shared physics is experimental + _setupMeshPhysics(mesh, params) { + const objects = this.objects.get(mesh) - if (params.world === undefined && this.sharedPhysics) { - const masterPhysics = this._getMasterPhysics() + // shared physics is experimental - if (masterPhysics !== null) world = masterPhysics.world - } + if (params.world === undefined && this.sharedPhysics) { + const masterPhysics = this._getMasterPhysics() - objects.physics = this._createMMDPhysics(mesh, params) + if (masterPhysics !== null) world = masterPhysics.world // eslint-disable-line no-undef + } - if (objects.mixer && params.animationWarmup !== false) { - this._animateMesh(mesh, 0) - objects.physics.reset() - } + objects.physics = this._createMMDPhysics(mesh, params) + + if (objects.mixer && params.animationWarmup !== false) { + this._animateMesh(mesh, 0) + objects.physics.reset() + } - objects.physics.warmup(params.warmup !== undefined ? params.warmup : 60) + objects.physics.warmup(params.warmup !== undefined ? params.warmup : 60) - this._optimizeIK(mesh, true) - }, + this._optimizeIK(mesh, true) + } - _animateMesh: function (mesh, delta) { - const objects = this.objects.get(mesh) + _animateMesh(mesh, delta) { + const objects = this.objects.get(mesh) - const mixer = objects.mixer - const ikSolver = objects.ikSolver - const grantSolver = objects.grantSolver - const physics = objects.physics - const looped = objects.looped + const mixer = objects.mixer + const ikSolver = objects.ikSolver + const grantSolver = objects.grantSolver + const physics = objects.physics + const looped = objects.looped + if (mixer && this.enabled.animation) { // alternate solution to save/restore bones but less performant? //mesh.pose(); //this._updatePropertyMixersBuffer( mesh ); - if (mixer && this.enabled.animation) { - this._restoreBones(mesh) + this._restoreBones(mesh) - mixer.update(delta) + mixer.update(delta) - this._saveBones(mesh) + this._saveBones(mesh) + // PMX animation system special path + if ( + this.configuration.pmxAnimation && + mesh.geometry.userData.MMD && + mesh.geometry.userData.MMD.format === 'pmx' + ) { + if (!objects.sortedBonesData) + objects.sortedBonesData = this._sortBoneDataArray(mesh.geometry.userData.MMD.bones.slice()) + + this._animatePMXMesh( + mesh, + objects.sortedBonesData, + ikSolver && this.enabled.ik ? ikSolver : null, + grantSolver && this.enabled.grant ? grantSolver : null, + ) + } else { if (ikSolver && this.enabled.ik) { mesh.updateMatrixWorld(true) ikSolver.update() @@ -431,97 +448,142 @@ const MMDAnimationHelper = (() => { grantSolver.update() } } + } - if (looped === true && this.enabled.physics) { - if (physics && this.configuration.resetPhysicsOnLoop) physics.reset() + if (looped === true && this.enabled.physics) { + if (physics && this.configuration.resetPhysicsOnLoop) physics.reset() - objects.looped = false - } + objects.looped = false + } - if (physics && this.enabled.physics && !this.sharedPhysics) { - this.onBeforePhysics(mesh) - physics.update(delta) + if (physics && this.enabled.physics && !this.sharedPhysics) { + this.onBeforePhysics(mesh) + physics.update(delta) + } + } + + // Sort bones in order by 1. transformationClass and 2. bone index. + // In PMX animation system, bone transformations should be processed + // in this order. + _sortBoneDataArray(boneDataArray) { + return boneDataArray.sort(function (a, b) { + if (a.transformationClass !== b.transformationClass) { + return a.transformationClass - b.transformationClass + } else { + return a.index - b.index } - }, + }) + } - _animateCamera: function (camera, delta) { - const mixer = this.objects.get(camera).mixer + // PMX Animation system is a bit too complex and doesn't great match to + // Three.js Animation system. This method attempts to simulate it as much as + // possible but doesn't perfectly simulate. + // This method is more costly than the regular one so + // you are recommended to set constructor parameter "pmxAnimation: true" + // only if your PMX model animation doesn't work well. + // If you need better method you would be required to write your own. + _animatePMXMesh(mesh, sortedBonesData, ikSolver, grantSolver) { + _quaternionIndex = 0 + _grantResultMap.clear() + + for (let i = 0, il = sortedBonesData.length; i < il; i++) { + updateOne(mesh, sortedBonesData[i].index, ikSolver, grantSolver) + } - if (mixer && this.enabled.cameraAnimation) { - mixer.update(delta) + mesh.updateMatrixWorld(true) + return this + } - camera.updateProjectionMatrix() + _animateCamera(camera, delta) { + const mixer = this.objects.get(camera).mixer - camera.up.set(0, 1, 0) - camera.up.applyQuaternion(camera.quaternion) - camera.lookAt(this.cameraTarget.position) - } - }, + if (mixer && this.enabled.cameraAnimation) { + mixer.update(delta) - _optimizeIK: function (mesh, physicsEnabled) { - const iks = mesh.geometry.userData.MMD.iks - const bones = mesh.geometry.userData.MMD.bones + camera.updateProjectionMatrix() - for (let i = 0, il = iks.length; i < il; i++) { - const ik = iks[i] - const links = ik.links + camera.up.set(0, 1, 0) + camera.up.applyQuaternion(camera.quaternion) + camera.lookAt(this.cameraTarget.position) + } + } - for (let j = 0, jl = links.length; j < jl; j++) { - const link = links[j] + _optimizeIK(mesh, physicsEnabled) { + const iks = mesh.geometry.userData.MMD.iks + const bones = mesh.geometry.userData.MMD.bones - if (physicsEnabled === true) { - // disable IK of the bone the corresponding rigidBody type of which is 1 or 2 - // because its rotation will be overriden by physics - link.enabled = bones[link.index].rigidBodyType > 0 ? false : true - } else { - link.enabled = true - } + for (let i = 0, il = iks.length; i < il; i++) { + const ik = iks[i] + const links = ik.links + + for (let j = 0, jl = links.length; j < jl; j++) { + const link = links[j] + + if (physicsEnabled === true) { + // disable IK of the bone the corresponding rigidBody type of which is 1 or 2 + // because its rotation will be overriden by physics + link.enabled = bones[link.index].rigidBodyType > 0 ? false : true + } else { + link.enabled = true } } - }, + } + } - _createCCDIKSolver: function (mesh) { - if (CCDIKSolver === undefined) { - throw new Error('THREE.MMDAnimationHelper: Import CCDIKSolver.') - } + _createCCDIKSolver(mesh) { + if (CCDIKSolver === undefined) { + throw new Error('THREE.MMDAnimationHelper: Import CCDIKSolver.') + } - return new CCDIKSolver(mesh, mesh.geometry.userData.MMD.iks) - }, + return new CCDIKSolver(mesh, mesh.geometry.userData.MMD.iks) + } - _createMMDPhysics: function (mesh, params) { - if (MMDPhysics === undefined) { - throw new Error('THREE.MMDPhysics: Import MMDPhysics.') - } + _createMMDPhysics(mesh, params) { + if (MMDPhysics === undefined) { + throw new Error('THREE.MMDPhysics: Import MMDPhysics.') + } - return new MMDPhysics( - mesh, - mesh.geometry.userData.MMD.rigidBodies, - mesh.geometry.userData.MMD.constraints, - params, - ) - }, + return new MMDPhysics(mesh, mesh.geometry.userData.MMD.rigidBodies, mesh.geometry.userData.MMD.constraints, params) + } + + /* + * Detects the longest duration and then sets it to them to sync. + * TODO: Not to access private properties ( ._actions and ._clip ) + */ + _syncDuration() { + let max = 0.0 + + const objects = this.objects + const meshes = this.meshes + const camera = this.camera + const audioManager = this.audioManager - /* - * Detects the longest duration and then sets it to them to sync. - * TODO: Not to access private properties ( ._actions and ._clip ) - */ - _syncDuration: function () { - let max = 0.0 + // get the longest duration - const objects = this.objects - const meshes = this.meshes - const camera = this.camera - const audioManager = this.audioManager + for (let i = 0, il = meshes.length; i < il; i++) { + const mixer = this.objects.get(meshes[i]).mixer - // get the longest duration + if (mixer === undefined) continue - for (let i = 0, il = meshes.length; i < il; i++) { - var mixer = this.objects.get(meshes[i]).mixer + for (let j = 0; j < mixer._actions.length; j++) { + const clip = mixer._actions[j]._clip - if (mixer === undefined) continue + if (!objects.has(clip)) { + objects.set(clip, { + duration: clip.duration, + }) + } + + max = Math.max(max, objects.get(clip).duration) + } + } - for (let j = 0; j < mixer._actions.length; j++) { - var clip = mixer._actions[j]._clip + if (camera !== null) { + const mixer = this.objects.get(camera).mixer + + if (mixer !== undefined) { + for (let i = 0, il = mixer._actions.length; i < il; i++) { + const clip = mixer._actions[i]._clip if (!objects.has(clip)) { objects.set(clip, { @@ -532,174 +594,227 @@ const MMDAnimationHelper = (() => { max = Math.max(max, objects.get(clip).duration) } } + } - if (camera !== null) { - var mixer = this.objects.get(camera).mixer + if (audioManager !== null) { + max = Math.max(max, objects.get(audioManager).duration) + } - if (mixer !== undefined) { - for (let i = 0, il = mixer._actions.length; i < il; i++) { - var clip = mixer._actions[i]._clip + max += this.configuration.afterglow - if (!objects.has(clip)) { - objects.set(clip, { - duration: clip.duration, - }) - } + // update the duration - max = Math.max(max, objects.get(clip).duration) - } - } + for (let i = 0, il = this.meshes.length; i < il; i++) { + const mixer = this.objects.get(this.meshes[i]).mixer + + if (mixer === undefined) continue + + for (let j = 0, jl = mixer._actions.length; j < jl; j++) { + mixer._actions[j]._clip.duration = max } + } + + if (camera !== null) { + const mixer = this.objects.get(camera).mixer - if (audioManager !== null) { - max = Math.max(max, objects.get(audioManager).duration) + if (mixer !== undefined) { + for (let i = 0, il = mixer._actions.length; i < il; i++) { + mixer._actions[i]._clip.duration = max + } } + } - max += this.configuration.afterglow + if (audioManager !== null) { + audioManager.duration = max + } + } - // update the duration + // workaround - for (let i = 0, il = this.meshes.length; i < il; i++) { - var mixer = this.objects.get(this.meshes[i]).mixer + _updatePropertyMixersBuffer(mesh) { + const mixer = this.objects.get(mesh).mixer - if (mixer === undefined) continue + const propertyMixers = mixer._bindings + const accuIndex = mixer._accuIndex - for (let j = 0, jl = mixer._actions.length; j < jl; j++) { - mixer._actions[j]._clip.duration = max - } - } + for (let i = 0, il = propertyMixers.length; i < il; i++) { + const propertyMixer = propertyMixers[i] + const buffer = propertyMixer.buffer + const stride = propertyMixer.valueSize + const offset = (accuIndex + 1) * stride - if (camera !== null) { - var mixer = this.objects.get(camera).mixer + propertyMixer.binding.getValue(buffer, offset) + } + } - if (mixer !== undefined) { - for (let i = 0, il = mixer._actions.length; i < il; i++) { - mixer._actions[i]._clip.duration = max - } - } - } + /* + * Avoiding these two issues by restore/save bones before/after mixer animation. + * + * 1. PropertyMixer used by AnimationMixer holds cache value in .buffer. + * Calculating IK, Grant, and Physics after mixer animation can break + * the cache coherency. + * + * 2. Applying Grant two or more times without reset the posing breaks model. + */ + _saveBones(mesh) { + const objects = this.objects.get(mesh) - if (audioManager !== null) { - audioManager.duration = max - } - }, + const bones = mesh.skeleton.bones - // workaround + let backupBones = objects.backupBones - _updatePropertyMixersBuffer: function (mesh) { - const mixer = this.objects.get(mesh).mixer + if (backupBones === undefined) { + backupBones = new Float32Array(bones.length * 7) + objects.backupBones = backupBones + } - const propertyMixers = mixer._bindings - const accuIndex = mixer._accuIndex + for (let i = 0, il = bones.length; i < il; i++) { + const bone = bones[i] + bone.position.toArray(backupBones, i * 7) + bone.quaternion.toArray(backupBones, i * 7 + 3) + } + } - for (let i = 0, il = propertyMixers.length; i < il; i++) { - const propertyMixer = propertyMixers[i] - const buffer = propertyMixer.buffer - const stride = propertyMixer.valueSize - const offset = (accuIndex + 1) * stride + _restoreBones(mesh) { + const objects = this.objects.get(mesh) - propertyMixer.binding.getValue(buffer, offset) - } - }, - - /* - * Avoiding these two issues by restore/save bones before/after mixer animation. - * - * 1. PropertyMixer used by AnimationMixer holds cache value in .buffer. - * Calculating IK, Grant, and Physics after mixer animation can break - * the cache coherency. - * - * 2. Applying Grant two or more times without reset the posing breaks model. - */ - _saveBones: function (mesh) { - const objects = this.objects.get(mesh) - - const bones = mesh.skeleton.bones - - let backupBones = objects.backupBones - - if (backupBones === undefined) { - backupBones = new Float32Array(bones.length * 7) - objects.backupBones = backupBones - } + const backupBones = objects.backupBones - for (let i = 0, il = bones.length; i < il; i++) { - const bone = bones[i] - bone.position.toArray(backupBones, i * 7) - bone.quaternion.toArray(backupBones, i * 7 + 3) - } - }, + if (backupBones === undefined) return + + const bones = mesh.skeleton.bones - _restoreBones: function (mesh) { - const objects = this.objects.get(mesh) + for (let i = 0, il = bones.length; i < il; i++) { + const bone = bones[i] + bone.position.fromArray(backupBones, i * 7) + bone.quaternion.fromArray(backupBones, i * 7 + 3) + } + } - const backupBones = objects.backupBones + // experimental - if (backupBones === undefined) return + _getMasterPhysics() { + if (this.masterPhysics !== null) return this.masterPhysics - const bones = mesh.skeleton.bones + for (let i = 0, il = this.meshes.length; i < il; i++) { + const physics = this.meshes[i].physics - for (let i = 0, il = bones.length; i < il; i++) { - const bone = bones[i] - bone.position.fromArray(backupBones, i * 7) - bone.quaternion.fromArray(backupBones, i * 7 + 3) + if (physics !== undefined && physics !== null) { + this.masterPhysics = physics + return this.masterPhysics } - }, + } - // experimental + return null + } - _getMasterPhysics: function () { - if (this.masterPhysics !== null) return this.masterPhysics + _updateSharedPhysics(delta) { + if (this.meshes.length === 0 || !this.enabled.physics || !this.sharedPhysics) return - for (let i = 0, il = this.meshes.length; i < il; i++) { - const physics = this.meshes[i].physics + const physics = this._getMasterPhysics() - if (physics !== undefined && physics !== null) { - this.masterPhysics = physics - return this.masterPhysics - } + if (physics === null) return + + for (let i = 0, il = this.meshes.length; i < il; i++) { + const p = this.meshes[i].physics + + if (p !== null && p !== undefined) { + p.updateRigidBodies() } + } - return null - }, + physics.stepSimulation(delta) - _updateSharedPhysics: function (delta) { - if (this.meshes.length === 0 || !this.enabled.physics || !this.sharedPhysics) return + for (let i = 0, il = this.meshes.length; i < il; i++) { + const p = this.meshes[i].physics - const physics = this._getMasterPhysics() + if (p !== null && p !== undefined) { + p.updateBones() + } + } + } +} - if (physics === null) return +// Keep working quaternions for less GC +const _quaternions = [] +let _quaternionIndex = 0 - for (let i = 0, il = this.meshes.length; i < il; i++) { - var p = this.meshes[i].physics +function getQuaternion() { + if (_quaternionIndex >= _quaternions.length) { + _quaternions.push(new Quaternion()) + } - if (p !== null && p !== undefined) { - p.updateRigidBodies() - } - } + return _quaternions[_quaternionIndex++] +} - physics.stepSimulation(delta) +// Save rotation whose grant and IK are already applied +// used by grant children +const _grantResultMap = new Map() - for (let i = 0, il = this.meshes.length; i < il; i++) { - var p = this.meshes[i].physics +function updateOne(mesh, boneIndex, ikSolver, grantSolver) { + const bones = mesh.skeleton.bones + const bonesData = mesh.geometry.userData.MMD.bones + const boneData = bonesData[boneIndex] + const bone = bones[boneIndex] - if (p !== null && p !== undefined) { - p.updateBones() - } + // Return if already updated by being referred as a grant parent. + if (_grantResultMap.has(boneIndex)) return + + const quaternion = getQuaternion() + + // Initialize grant result here to prevent infinite loop. + // If it's referred before updating with actual result later + // result without applyting IK or grant is gotten + // but better than composing of infinite loop. + _grantResultMap.set(boneIndex, quaternion.copy(bone.quaternion)) + + // @TODO: Support global grant and grant position + if (grantSolver && boneData.grant && !boneData.grant.isLocal && boneData.grant.affectRotation) { + const parentIndex = boneData.grant.parentIndex + const ratio = boneData.grant.ratio + + if (!_grantResultMap.has(parentIndex)) { + updateOne(mesh, parentIndex, ikSolver, grantSolver) + } + + grantSolver.addGrantRotation(bone, _grantResultMap.get(parentIndex), ratio) + } + + if (ikSolver && boneData.ik) { + // @TODO: Updating world matrices every time solving an IK bone is + // costly. Optimize if possible. + mesh.updateMatrixWorld(true) + ikSolver.updateOne(boneData.ik) + + // No confident, but it seems the grant results with ik links should be updated? + const links = boneData.ik.links + + for (let i = 0, il = links.length; i < il; i++) { + const link = links[i] + + if (link.enabled === false) continue + + const linkIndex = link.index + + if (_grantResultMap.has(linkIndex)) { + _grantResultMap.set(linkIndex, _grantResultMap.get(linkIndex).copy(bones[linkIndex].quaternion)) } - }, + } } - // + // Update with the actual result here + quaternion.copy(bone.quaternion) +} + +// +class AudioManager { /** * @param {THREE.Audio} audio * @param {Object} params - (optional) * @param {Nuumber} params.delayTime */ - function AudioManager(audio, params) { - params = params || {} - + constructor(audio, params = {}) { this.audio = audio this.elapsedTime = 0.0 @@ -710,99 +825,110 @@ const MMDAnimationHelper = (() => { this.duration = this.audioDuration + this.delayTime } - AudioManager.prototype = { - constructor: AudioManager, + /** + * @param {Number} delta + * @return {AudioManager} + */ + control(delta) { + this.elapsed += delta + this.currentTime += delta + + if (this._shouldStopAudio()) this.audio.stop() + if (this._shouldStartAudio()) this.audio.play() + + return this + } + + // private methods - /** - * @param {Number} delta - * @return {AudioManager} - */ - control: function (delta) { - this.elapsed += delta - this.currentTime += delta + _shouldStartAudio() { + if (this.audio.isPlaying) return false - if (this._shouldStopAudio()) this.audio.stop() - if (this._shouldStartAudio()) this.audio.play() + while (this.currentTime >= this.duration) { + this.currentTime -= this.duration + } - return this - }, + if (this.currentTime < this.delayTime) return false - // private methods + // 'duration' can be bigger than 'audioDuration + delayTime' because of sync configuration + if (this.currentTime - this.delayTime > this.audioDuration) return false - _shouldStartAudio: function () { - if (this.audio.isPlaying) return false + return true + } - while (this.currentTime >= this.duration) { - this.currentTime -= this.duration - } + _shouldStopAudio() { + return this.audio.isPlaying && this.currentTime >= this.duration + } +} - if (this.currentTime < this.delayTime) return false +const _q = /* @__PURE__ */ new Quaternion() - // 'duration' can be bigger than 'audioDuration + delayTime' because of sync configuration - if (this.currentTime - this.delayTime > this.audioDuration) return false +/** + * Solver for Grant (Fuyo in Japanese. I just google translated because + * Fuyo may be MMD specific term and may not be common word in 3D CG terms.) + * Grant propagates a bone's transform to other bones transforms even if + * they are not children. + * @param {THREE.SkinnedMesh} mesh + * @param {Array} grants + */ +class GrantSolver { + constructor(mesh, grants = []) { + this.mesh = mesh + this.grants = grants + } - return true - }, + /** + * Solve all the grant bones + * @return {GrantSolver} + */ + update() { + const grants = this.grants - _shouldStopAudio: function () { - return this.audio.isPlaying && this.currentTime >= this.duration - }, + for (let i = 0, il = grants.length; i < il; i++) { + this.updateOne(grants[i]) + } + + return this } /** - * @param {THREE.SkinnedMesh} mesh - * @param {Array} grants + * Solve a grant bone + * @param {Object} grant - grant parameter + * @return {GrantSolver} */ - function GrantSolver(mesh, grants) { - this.mesh = mesh - this.grants = grants || [] - } - - GrantSolver.prototype = { - constructor: GrantSolver, - - /** - * @return {GrantSolver} - */ - update: (() => { - const quaternion = new Quaternion() - - return function () { - const bones = this.mesh.skeleton.bones - const grants = this.grants - - for (let i = 0, il = grants.length; i < il; i++) { - const grant = grants[i] - const bone = bones[grant.index] - const parentBone = bones[grant.parentIndex] - - if (grant.isLocal) { - // TODO: implement - if (grant.affectPosition) { - } - - // TODO: implement - if (grant.affectRotation) { - } - } else { - // TODO: implement - if (grant.affectPosition) { - } - - if (grant.affectRotation) { - quaternion.set(0, 0, 0, 1) - quaternion.slerp(parentBone.quaternion, grant.ratio) - bone.quaternion.multiply(quaternion) - } - } - } + updateOne(grant) { + const bones = this.mesh.skeleton.bones + const bone = bones[grant.index] + const parentBone = bones[grant.parentIndex] + + if (grant.isLocal) { + // TODO: implement + if (grant.affectPosition) { + } - return this + // TODO: implement + if (grant.affectRotation) { } - })(), + } else { + // TODO: implement + if (grant.affectPosition) { + } + + if (grant.affectRotation) { + this.addGrantRotation(bone, parentBone.quaternion, grant.ratio) + } + } + + return this } - return MMDAnimationHelper -})() + addGrantRotation(bone, q, ratio) { + _q.set(0, 0, 0, 1) + _q.slerp(q, ratio) + bone.quaternion.multiply(_q) + + return this + } +} export { MMDAnimationHelper } diff --git a/src/animation/MMDPhysics.d.ts b/src/animation/MMDPhysics.d.ts index 889ec776..eb3f20e2 100644 --- a/src/animation/MMDPhysics.d.ts +++ b/src/animation/MMDPhysics.d.ts @@ -110,5 +110,10 @@ export class Constraint { } export class MMDPhysicsHelper extends Object3D { - constructor() + mesh: THREE.SkinnedMesh + physics: MMDPhysics + materials: [THREE.MeshBasicMaterial, THREE.MeshBasicMaterial, THREE.MeshBasicMaterial] + + constructor(mesh: THREE.SkinnedMesh, physics: MMDPhysics) + dispose(): void } diff --git a/src/animation/MMDPhysics.js b/src/animation/MMDPhysics.js index 1d4ad33c..966b438a 100644 --- a/src/animation/MMDPhysics.js +++ b/src/animation/MMDPhysics.js @@ -1,8 +1,8 @@ import { Bone, BoxGeometry, + CapsuleGeometry, Color, - CylinderGeometry, Euler, Matrix4, Mesh, @@ -26,7 +26,7 @@ import { /* global Ammo */ -const MMDPhysics = (() => { +class MMDPhysics { /** * @param {THREE.SkinnedMesh} mesh * @param {Array} rigidBodyParams @@ -36,14 +36,11 @@ const MMDPhysics = (() => { * @param {Integer} params.maxStepNum - Default is 3. * @param {Vector3} params.gravity - Default is ( 0, - 9.8 * 10, 0 ) */ - function MMDPhysics(mesh, rigidBodyParams, constraintParams, params) { + constructor(mesh, rigidBodyParams, constraintParams = [], params = {}) { if (typeof Ammo === 'undefined') { throw new Error('THREE.MMDPhysics: Import ammo.js https://github.com/kripken/ammo.js') } - constraintParams = constraintParams || [] - params = params || {} - this.manager = new ResourceManager() this.mesh = mesh @@ -68,232 +65,230 @@ const MMDPhysics = (() => { this._init(mesh, rigidBodyParams, constraintParams) } - MMDPhysics.prototype = { - constructor: MMDPhysics, + /** + * Advances Physics calculation and updates bones. + * + * @param {Number} delta - time in second + * @return {MMDPhysics} + */ + update(delta) { + const manager = this.manager + const mesh = this.mesh - /** - * Advances Physics calculation and updates bones. - * - * @param {Number} delta - time in second - * @return {MMDPhysics} - */ - update: function (delta) { - const manager = this.manager - const mesh = this.mesh + // rigid bodies and constrains are for + // mesh's world scale (1, 1, 1). + // Convert to (1, 1, 1) if it isn't. - // rigid bodies and constrains are for - // mesh's world scale (1, 1, 1). - // Convert to (1, 1, 1) if it isn't. + let isNonDefaultScale = false - let isNonDefaultScale = false + const position = manager.allocThreeVector3() + const quaternion = manager.allocThreeQuaternion() + const scale = manager.allocThreeVector3() - const position = manager.allocThreeVector3() - const quaternion = manager.allocThreeQuaternion() - const scale = manager.allocThreeVector3() + mesh.matrixWorld.decompose(position, quaternion, scale) - mesh.matrixWorld.decompose(position, quaternion, scale) + if (scale.x !== 1 || scale.y !== 1 || scale.z !== 1) { + isNonDefaultScale = true + } - if (scale.x !== 1 || scale.y !== 1 || scale.z !== 1) { - isNonDefaultScale = true - } + let parent - let parent + if (isNonDefaultScale) { + parent = mesh.parent - if (isNonDefaultScale) { - parent = mesh.parent + if (parent !== null) mesh.parent = null - if (parent !== null) mesh.parent = null + scale.copy(this.mesh.scale) - scale.copy(this.mesh.scale) + mesh.scale.set(1, 1, 1) + mesh.updateMatrixWorld(true) + } - mesh.scale.set(1, 1, 1) - mesh.updateMatrixWorld(true) - } + // calculate physics and update bones - // calculate physics and update bones + this._updateRigidBodies() + this._stepSimulation(delta) + this._updateBones() - this._updateRigidBodies() - this._stepSimulation(delta) - this._updateBones() + // restore mesh if converted above - // restore mesh if converted above + if (isNonDefaultScale) { + if (parent !== null) mesh.parent = parent - if (isNonDefaultScale) { - if (parent !== null) mesh.parent = parent + mesh.scale.copy(scale) + } - mesh.scale.copy(scale) - } + manager.freeThreeVector3(scale) + manager.freeThreeQuaternion(quaternion) + manager.freeThreeVector3(position) - manager.freeThreeVector3(scale) - manager.freeThreeQuaternion(quaternion) - manager.freeThreeVector3(position) + return this + } - return this - }, + /** + * Resets rigid bodies transorm to current bone's. + * + * @return {MMDPhysics} + */ + reset() { + for (let i = 0, il = this.bodies.length; i < il; i++) { + this.bodies[i].reset() + } - /** - * Resets rigid bodies transorm to current bone's. - * - * @return {MMDPhysics} - */ - reset: function () { - for (let i = 0, il = this.bodies.length; i < il; i++) { - this.bodies[i].reset() - } + return this + } - return this - }, + /** + * Warm ups Rigid bodies. Calculates cycles steps. + * + * @param {Integer} cycles + * @return {MMDPhysics} + */ + warmup(cycles) { + for (let i = 0; i < cycles; i++) { + this.update(1 / 60) + } - /** - * Warm ups Rigid bodies. Calculates cycles steps. - * - * @param {Integer} cycles - * @return {MMDPhysics} - */ - warmup: function (cycles) { - for (let i = 0; i < cycles; i++) { - this.update(1 / 60) - } + return this + } - return this - }, + /** + * Sets gravity. + * + * @param {Vector3} gravity + * @return {MMDPhysicsHelper} + */ + setGravity(gravity) { + this.world.setGravity(new Ammo.btVector3(gravity.x, gravity.y, gravity.z)) + this.gravity.copy(gravity) - /** - * Sets gravity. - * - * @param {Vector3} gravity - * @return {MMDPhysicsHelper} - */ - setGravity: function (gravity) { - this.world.setGravity(new Ammo.btVector3(gravity.x, gravity.y, gravity.z)) - this.gravity.copy(gravity) + return this + } - return this - }, + /** + * Creates MMDPhysicsHelper + * + * @return {MMDPhysicsHelper} + */ + createHelper() { + return new MMDPhysicsHelper(this.mesh, this) + } - /** - * Creates MMDPhysicsHelper - * - * @return {MMDPhysicsHelper} - */ - createHelper: function () { - return new MMDPhysicsHelper(this.mesh, this) - }, + // private methods - // private methods + _init(mesh, rigidBodyParams, constraintParams) { + const manager = this.manager - _init: function (mesh, rigidBodyParams, constraintParams) { - const manager = this.manager + // rigid body/constraint parameters are for + // mesh's default world transform as position(0, 0, 0), + // quaternion(0, 0, 0, 1) and scale(0, 0, 0) - // rigid body/constraint parameters are for - // mesh's default world transform as position(0, 0, 0), - // quaternion(0, 0, 0, 1) and scale(0, 0, 0) + const parent = mesh.parent - let parent = mesh.parent + if (parent !== null) mesh.parent = null - if (parent !== null) parent = null + const currentPosition = manager.allocThreeVector3() + const currentQuaternion = manager.allocThreeQuaternion() + const currentScale = manager.allocThreeVector3() - const currentPosition = manager.allocThreeVector3() - const currentQuaternion = manager.allocThreeQuaternion() - const currentScale = manager.allocThreeVector3() + currentPosition.copy(mesh.position) + currentQuaternion.copy(mesh.quaternion) + currentScale.copy(mesh.scale) - currentPosition.copy(mesh.position) - currentQuaternion.copy(mesh.quaternion) - currentScale.copy(mesh.scale) + mesh.position.set(0, 0, 0) + mesh.quaternion.set(0, 0, 0, 1) + mesh.scale.set(1, 1, 1) - mesh.position.set(0, 0, 0) - mesh.quaternion.set(0, 0, 0, 1) - mesh.scale.set(1, 1, 1) + mesh.updateMatrixWorld(true) - mesh.updateMatrixWorld(true) + if (this.world === null) { + this.world = this._createWorld() + this.setGravity(this.gravity) + } - if (this.world === null) { - this.world = this._createWorld() - this.setGravity(this.gravity) - } + this._initRigidBodies(rigidBodyParams) + this._initConstraints(constraintParams) - this._initRigidBodies(rigidBodyParams) - this._initConstraints(constraintParams) + if (parent !== null) mesh.parent = parent - if (parent !== null) mesh.parent = parent + mesh.position.copy(currentPosition) + mesh.quaternion.copy(currentQuaternion) + mesh.scale.copy(currentScale) - mesh.position.copy(currentPosition) - mesh.quaternion.copy(currentQuaternion) - mesh.scale.copy(currentScale) + mesh.updateMatrixWorld(true) - mesh.updateMatrixWorld(true) + this.reset() - this.reset() - - manager.freeThreeVector3(currentPosition) - manager.freeThreeQuaternion(currentQuaternion) - manager.freeThreeVector3(currentScale) - }, - - _createWorld: function () { - const config = new Ammo.btDefaultCollisionConfiguration() - const dispatcher = new Ammo.btCollisionDispatcher(config) - const cache = new Ammo.btDbvtBroadphase() - const solver = new Ammo.btSequentialImpulseConstraintSolver() - const world = new Ammo.btDiscreteDynamicsWorld(dispatcher, cache, solver, config) - return world - }, - - _initRigidBodies: function (rigidBodies) { - for (let i = 0, il = rigidBodies.length; i < il; i++) { - this.bodies.push(new RigidBody(this.mesh, this.world, rigidBodies[i], this.manager)) - } - }, - - _initConstraints: function (constraints) { - for (let i = 0, il = constraints.length; i < il; i++) { - const params = constraints[i] - const bodyA = this.bodies[params.rigidBodyIndex1] - const bodyB = this.bodies[params.rigidBodyIndex2] - this.constraints.push(new Constraint(this.mesh, this.world, bodyA, bodyB, params, this.manager)) - } - }, + manager.freeThreeVector3(currentPosition) + manager.freeThreeQuaternion(currentQuaternion) + manager.freeThreeVector3(currentScale) + } - _stepSimulation: function (delta) { - const unitStep = this.unitStep - let stepTime = delta - let maxStepNum = ((delta / unitStep) | 0) + 1 + _createWorld() { + const config = new Ammo.btDefaultCollisionConfiguration() + const dispatcher = new Ammo.btCollisionDispatcher(config) + const cache = new Ammo.btDbvtBroadphase() + const solver = new Ammo.btSequentialImpulseConstraintSolver() + const world = new Ammo.btDiscreteDynamicsWorld(dispatcher, cache, solver, config) + return world + } - if (stepTime < unitStep) { - stepTime = unitStep - maxStepNum = 1 - } + _initRigidBodies(rigidBodies) { + for (let i = 0, il = rigidBodies.length; i < il; i++) { + this.bodies.push(new RigidBody(this.mesh, this.world, rigidBodies[i], this.manager)) + } + } - if (maxStepNum > this.maxStepNum) { - maxStepNum = this.maxStepNum - } + _initConstraints(constraints) { + for (let i = 0, il = constraints.length; i < il; i++) { + const params = constraints[i] + const bodyA = this.bodies[params.rigidBodyIndex1] + const bodyB = this.bodies[params.rigidBodyIndex2] + this.constraints.push(new Constraint(this.mesh, this.world, bodyA, bodyB, params, this.manager)) + } + } - this.world.stepSimulation(stepTime, maxStepNum, unitStep) - }, + _stepSimulation(delta) { + const unitStep = this.unitStep + let stepTime = delta + let maxStepNum = ((delta / unitStep) | 0) + 1 - _updateRigidBodies: function () { - for (let i = 0, il = this.bodies.length; i < il; i++) { - this.bodies[i].updateFromBone() - } - }, + if (stepTime < unitStep) { + stepTime = unitStep + maxStepNum = 1 + } - _updateBones: function () { - for (let i = 0, il = this.bodies.length; i < il; i++) { - this.bodies[i].updateBone() - } - }, + if (maxStepNum > this.maxStepNum) { + maxStepNum = this.maxStepNum + } + + this.world.stepSimulation(stepTime, maxStepNum, unitStep) } - /** - * This manager's responsibilies are - * - * 1. manage Ammo.js and Three.js object resources and - * improve the performance and the memory consumption by - * reusing objects. - * - * 2. provide simple Ammo object operations. - */ - function ResourceManager() { + _updateRigidBodies() { + for (let i = 0, il = this.bodies.length; i < il; i++) { + this.bodies[i].updateFromBone() + } + } + + _updateBones() { + for (let i = 0, il = this.bodies.length; i < il; i++) { + this.bodies[i].updateBone() + } + } +} + +/** + * This manager's responsibilies are + * + * 1. manage Ammo.js and Three.js object resources and + * improve the performance and the memory consumption by + * reusing objects. + * + * 2. provide simple Ammo object operations. + */ +class ResourceManager { + constructor() { // for Three.js this.threeVector3s = [] this.threeMatrix4s = [] @@ -306,348 +301,346 @@ const MMDPhysics = (() => { this.vector3s = [] } - ResourceManager.prototype = { - constructor: ResourceManager, + allocThreeVector3() { + return this.threeVector3s.length > 0 ? this.threeVector3s.pop() : new Vector3() + } - allocThreeVector3: function () { - return this.threeVector3s.length > 0 ? this.threeVector3s.pop() : new Vector3() - }, + freeThreeVector3(v) { + this.threeVector3s.push(v) + } - freeThreeVector3: function (v) { - this.threeVector3s.push(v) - }, - - allocThreeMatrix4: function () { - return this.threeMatrix4s.length > 0 ? this.threeMatrix4s.pop() : new Matrix4() - }, - - freeThreeMatrix4: function (m) { - this.threeMatrix4s.push(m) - }, - - allocThreeQuaternion: function () { - return this.threeQuaternions.length > 0 ? this.threeQuaternions.pop() : new Quaternion() - }, - - freeThreeQuaternion: function (q) { - this.threeQuaternions.push(q) - }, - - allocThreeEuler: function () { - return this.threeEulers.length > 0 ? this.threeEulers.pop() : new Euler() - }, - - freeThreeEuler: function (e) { - this.threeEulers.push(e) - }, - - allocTransform: function () { - return this.transforms.length > 0 ? this.transforms.pop() : new Ammo.btTransform() - }, + allocThreeMatrix4() { + return this.threeMatrix4s.length > 0 ? this.threeMatrix4s.pop() : new Matrix4() + } - freeTransform: function (t) { - this.transforms.push(t) - }, + freeThreeMatrix4(m) { + this.threeMatrix4s.push(m) + } - allocQuaternion: function () { - return this.quaternions.length > 0 ? this.quaternions.pop() : new Ammo.btQuaternion() - }, + allocThreeQuaternion() { + return this.threeQuaternions.length > 0 ? this.threeQuaternions.pop() : new Quaternion() + } - freeQuaternion: function (q) { - this.quaternions.push(q) - }, + freeThreeQuaternion(q) { + this.threeQuaternions.push(q) + } - allocVector3: function () { - return this.vector3s.length > 0 ? this.vector3s.pop() : new Ammo.btVector3() - }, + allocThreeEuler() { + return this.threeEulers.length > 0 ? this.threeEulers.pop() : new Euler() + } - freeVector3: function (v) { - this.vector3s.push(v) - }, + freeThreeEuler(e) { + this.threeEulers.push(e) + } - setIdentity: function (t) { - t.setIdentity() - }, + allocTransform() { + return this.transforms.length > 0 ? this.transforms.pop() : new Ammo.btTransform() + } - getBasis: function (t) { - const q = this.allocQuaternion() - t.getBasis().getRotation(q) - return q - }, + freeTransform(t) { + this.transforms.push(t) + } - getBasisAsMatrix3: function (t) { - const q = this.getBasis(t) - const m = this.quaternionToMatrix3(q) - this.freeQuaternion(q) - return m - }, + allocQuaternion() { + return this.quaternions.length > 0 ? this.quaternions.pop() : new Ammo.btQuaternion() + } - getOrigin: function (t) { - return t.getOrigin() - }, + freeQuaternion(q) { + this.quaternions.push(q) + } - setOrigin: function (t, v) { - t.getOrigin().setValue(v.x(), v.y(), v.z()) - }, + allocVector3() { + return this.vector3s.length > 0 ? this.vector3s.pop() : new Ammo.btVector3() + } - copyOrigin: function (t1, t2) { - const o = t2.getOrigin() - this.setOrigin(t1, o) - }, + freeVector3(v) { + this.vector3s.push(v) + } - setBasis: function (t, q) { - t.setRotation(q) - }, + setIdentity(t) { + t.setIdentity() + } - setBasisFromMatrix3: function (t, m) { - const q = this.matrix3ToQuaternion(m) - this.setBasis(t, q) - this.freeQuaternion(q) - }, + getBasis(t) { + var q = this.allocQuaternion() + t.getBasis().getRotation(q) + return q + } - setOriginFromArray3: function (t, a) { - t.getOrigin().setValue(a[0], a[1], a[2]) - }, - - setOriginFromThreeVector3: function (t, v) { - t.getOrigin().setValue(v.x, v.y, v.z) - }, - - setBasisFromArray3: function (t, a) { - const thQ = this.allocThreeQuaternion() - const thE = this.allocThreeEuler() - thE.set(a[0], a[1], a[2]) - this.setBasisFromThreeQuaternion(t, thQ.setFromEuler(thE)) - - this.freeThreeEuler(thE) - this.freeThreeQuaternion(thQ) - }, - - setBasisFromThreeQuaternion: function (t, a) { - const q = this.allocQuaternion() - - q.setX(a.x) - q.setY(a.y) - q.setZ(a.z) - q.setW(a.w) - this.setBasis(t, q) - - this.freeQuaternion(q) - }, - - multiplyTransforms: function (t1, t2) { - const t = this.allocTransform() - this.setIdentity(t) - - const m1 = this.getBasisAsMatrix3(t1) - const m2 = this.getBasisAsMatrix3(t2) - - const o1 = this.getOrigin(t1) - const o2 = this.getOrigin(t2) - - const v1 = this.multiplyMatrix3ByVector3(m1, o2) - const v2 = this.addVector3(v1, o1) - this.setOrigin(t, v2) - - const m3 = this.multiplyMatrices3(m1, m2) - this.setBasisFromMatrix3(t, m3) - - this.freeVector3(v1) - this.freeVector3(v2) - - return t - }, - - inverseTransform: function (t) { - const t2 = this.allocTransform() - - const m1 = this.getBasisAsMatrix3(t) - const o = this.getOrigin(t) - - const m2 = this.transposeMatrix3(m1) - const v1 = this.negativeVector3(o) - const v2 = this.multiplyMatrix3ByVector3(m2, v1) - - this.setOrigin(t2, v2) - this.setBasisFromMatrix3(t2, m2) - - this.freeVector3(v1) - this.freeVector3(v2) - - return t2 - }, - - multiplyMatrices3: function (m1, m2) { - const m3 = [] - - const v10 = this.rowOfMatrix3(m1, 0) - const v11 = this.rowOfMatrix3(m1, 1) - const v12 = this.rowOfMatrix3(m1, 2) - - const v20 = this.columnOfMatrix3(m2, 0) - const v21 = this.columnOfMatrix3(m2, 1) - const v22 = this.columnOfMatrix3(m2, 2) - - m3[0] = this.dotVectors3(v10, v20) - m3[1] = this.dotVectors3(v10, v21) - m3[2] = this.dotVectors3(v10, v22) - m3[3] = this.dotVectors3(v11, v20) - m3[4] = this.dotVectors3(v11, v21) - m3[5] = this.dotVectors3(v11, v22) - m3[6] = this.dotVectors3(v12, v20) - m3[7] = this.dotVectors3(v12, v21) - m3[8] = this.dotVectors3(v12, v22) - - this.freeVector3(v10) - this.freeVector3(v11) - this.freeVector3(v12) - this.freeVector3(v20) - this.freeVector3(v21) - this.freeVector3(v22) - - return m3 - }, - - addVector3: function (v1, v2) { - const v = this.allocVector3() - v.setValue(v1.x() + v2.x(), v1.y() + v2.y(), v1.z() + v2.z()) - return v - }, - - dotVectors3: function (v1, v2) { - return v1.x() * v2.x() + v1.y() * v2.y() + v1.z() * v2.z() - }, - - rowOfMatrix3: function (m, i) { - const v = this.allocVector3() - v.setValue(m[i * 3 + 0], m[i * 3 + 1], m[i * 3 + 2]) - return v - }, - - columnOfMatrix3: function (m, i) { - const v = this.allocVector3() - v.setValue(m[i + 0], m[i + 3], m[i + 6]) - return v - }, - - negativeVector3: function (v) { - const v2 = this.allocVector3() - v2.setValue(-v.x(), -v.y(), -v.z()) - return v2 - }, - - multiplyMatrix3ByVector3: function (m, v) { - const v4 = this.allocVector3() - - const v0 = this.rowOfMatrix3(m, 0) - const v1 = this.rowOfMatrix3(m, 1) - const v2 = this.rowOfMatrix3(m, 2) - const x = this.dotVectors3(v0, v) - const y = this.dotVectors3(v1, v) - const z = this.dotVectors3(v2, v) - - v4.setValue(x, y, z) - - this.freeVector3(v0) - this.freeVector3(v1) - this.freeVector3(v2) - - return v4 - }, - - transposeMatrix3: function (m) { - const m2 = [] - m2[0] = m[0] - m2[1] = m[3] - m2[2] = m[6] - m2[3] = m[1] - m2[4] = m[4] - m2[5] = m[7] - m2[6] = m[2] - m2[7] = m[5] - m2[8] = m[8] - return m2 - }, - - quaternionToMatrix3: function (q) { - const m = [] - - const x = q.x() - const y = q.y() - const z = q.z() - const w = q.w() - - const xx = x * x - const yy = y * y - const zz = z * z - - const xy = x * y - const yz = y * z - const zx = z * x - - const xw = x * w - const yw = y * w - const zw = z * w - - m[0] = 1 - 2 * (yy + zz) - m[1] = 2 * (xy - zw) - m[2] = 2 * (zx + yw) - m[3] = 2 * (xy + zw) - m[4] = 1 - 2 * (zz + xx) - m[5] = 2 * (yz - xw) - m[6] = 2 * (zx - yw) - m[7] = 2 * (yz + xw) - m[8] = 1 - 2 * (xx + yy) - - return m - }, - - matrix3ToQuaternion: function (m) { - const t = m[0] + m[4] + m[8] - let s, x, y, z, w - - if (t > 0) { - s = Math.sqrt(t + 1.0) * 2 - w = 0.25 * s - x = (m[7] - m[5]) / s - y = (m[2] - m[6]) / s - z = (m[3] - m[1]) / s - } else if (m[0] > m[4] && m[0] > m[8]) { - s = Math.sqrt(1.0 + m[0] - m[4] - m[8]) * 2 - w = (m[7] - m[5]) / s - x = 0.25 * s - y = (m[1] + m[3]) / s - z = (m[2] + m[6]) / s - } else if (m[4] > m[8]) { - s = Math.sqrt(1.0 + m[4] - m[0] - m[8]) * 2 - w = (m[2] - m[6]) / s - x = (m[1] + m[3]) / s - y = 0.25 * s - z = (m[5] + m[7]) / s - } else { - s = Math.sqrt(1.0 + m[8] - m[0] - m[4]) * 2 - w = (m[3] - m[1]) / s - x = (m[2] + m[6]) / s - y = (m[5] + m[7]) / s - z = 0.25 * s - } + getBasisAsMatrix3(t) { + var q = this.getBasis(t) + var m = this.quaternionToMatrix3(q) + this.freeQuaternion(q) + return m + } - const q = this.allocQuaternion() - q.setX(x) - q.setY(y) - q.setZ(z) - q.setW(w) - return q - }, + getOrigin(t) { + return t.getOrigin() } - /** - * @param {THREE.SkinnedMesh} mesh - * @param {Ammo.btDiscreteDynamicsWorld} world - * @param {Object} params - * @param {ResourceManager} manager - */ - function RigidBody(mesh, world, params, manager) { + setOrigin(t, v) { + t.getOrigin().setValue(v.x(), v.y(), v.z()) + } + + copyOrigin(t1, t2) { + var o = t2.getOrigin() + this.setOrigin(t1, o) + } + + setBasis(t, q) { + t.setRotation(q) + } + + setBasisFromMatrix3(t, m) { + var q = this.matrix3ToQuaternion(m) + this.setBasis(t, q) + this.freeQuaternion(q) + } + + setOriginFromArray3(t, a) { + t.getOrigin().setValue(a[0], a[1], a[2]) + } + + setOriginFromThreeVector3(t, v) { + t.getOrigin().setValue(v.x, v.y, v.z) + } + + setBasisFromArray3(t, a) { + var thQ = this.allocThreeQuaternion() + var thE = this.allocThreeEuler() + thE.set(a[0], a[1], a[2]) + this.setBasisFromThreeQuaternion(t, thQ.setFromEuler(thE)) + + this.freeThreeEuler(thE) + this.freeThreeQuaternion(thQ) + } + + setBasisFromThreeQuaternion(t, a) { + var q = this.allocQuaternion() + + q.setX(a.x) + q.setY(a.y) + q.setZ(a.z) + q.setW(a.w) + this.setBasis(t, q) + + this.freeQuaternion(q) + } + + multiplyTransforms(t1, t2) { + var t = this.allocTransform() + this.setIdentity(t) + + var m1 = this.getBasisAsMatrix3(t1) + var m2 = this.getBasisAsMatrix3(t2) + + var o1 = this.getOrigin(t1) + var o2 = this.getOrigin(t2) + + var v1 = this.multiplyMatrix3ByVector3(m1, o2) + var v2 = this.addVector3(v1, o1) + this.setOrigin(t, v2) + + var m3 = this.multiplyMatrices3(m1, m2) + this.setBasisFromMatrix3(t, m3) + + this.freeVector3(v1) + this.freeVector3(v2) + + return t + } + + inverseTransform(t) { + var t2 = this.allocTransform() + + var m1 = this.getBasisAsMatrix3(t) + var o = this.getOrigin(t) + + var m2 = this.transposeMatrix3(m1) + var v1 = this.negativeVector3(o) + var v2 = this.multiplyMatrix3ByVector3(m2, v1) + + this.setOrigin(t2, v2) + this.setBasisFromMatrix3(t2, m2) + + this.freeVector3(v1) + this.freeVector3(v2) + + return t2 + } + + multiplyMatrices3(m1, m2) { + var m3 = [] + + var v10 = this.rowOfMatrix3(m1, 0) + var v11 = this.rowOfMatrix3(m1, 1) + var v12 = this.rowOfMatrix3(m1, 2) + + var v20 = this.columnOfMatrix3(m2, 0) + var v21 = this.columnOfMatrix3(m2, 1) + var v22 = this.columnOfMatrix3(m2, 2) + + m3[0] = this.dotVectors3(v10, v20) + m3[1] = this.dotVectors3(v10, v21) + m3[2] = this.dotVectors3(v10, v22) + m3[3] = this.dotVectors3(v11, v20) + m3[4] = this.dotVectors3(v11, v21) + m3[5] = this.dotVectors3(v11, v22) + m3[6] = this.dotVectors3(v12, v20) + m3[7] = this.dotVectors3(v12, v21) + m3[8] = this.dotVectors3(v12, v22) + + this.freeVector3(v10) + this.freeVector3(v11) + this.freeVector3(v12) + this.freeVector3(v20) + this.freeVector3(v21) + this.freeVector3(v22) + + return m3 + } + + addVector3(v1, v2) { + var v = this.allocVector3() + v.setValue(v1.x() + v2.x(), v1.y() + v2.y(), v1.z() + v2.z()) + return v + } + + dotVectors3(v1, v2) { + return v1.x() * v2.x() + v1.y() * v2.y() + v1.z() * v2.z() + } + + rowOfMatrix3(m, i) { + var v = this.allocVector3() + v.setValue(m[i * 3 + 0], m[i * 3 + 1], m[i * 3 + 2]) + return v + } + + columnOfMatrix3(m, i) { + var v = this.allocVector3() + v.setValue(m[i + 0], m[i + 3], m[i + 6]) + return v + } + + negativeVector3(v) { + var v2 = this.allocVector3() + v2.setValue(-v.x(), -v.y(), -v.z()) + return v2 + } + + multiplyMatrix3ByVector3(m, v) { + var v4 = this.allocVector3() + + var v0 = this.rowOfMatrix3(m, 0) + var v1 = this.rowOfMatrix3(m, 1) + var v2 = this.rowOfMatrix3(m, 2) + var x = this.dotVectors3(v0, v) + var y = this.dotVectors3(v1, v) + var z = this.dotVectors3(v2, v) + + v4.setValue(x, y, z) + + this.freeVector3(v0) + this.freeVector3(v1) + this.freeVector3(v2) + + return v4 + } + + transposeMatrix3(m) { + var m2 = [] + m2[0] = m[0] + m2[1] = m[3] + m2[2] = m[6] + m2[3] = m[1] + m2[4] = m[4] + m2[5] = m[7] + m2[6] = m[2] + m2[7] = m[5] + m2[8] = m[8] + return m2 + } + + quaternionToMatrix3(q) { + var m = [] + + var x = q.x() + var y = q.y() + var z = q.z() + var w = q.w() + + var xx = x * x + var yy = y * y + var zz = z * z + + var xy = x * y + var yz = y * z + var zx = z * x + + var xw = x * w + var yw = y * w + var zw = z * w + + m[0] = 1 - 2 * (yy + zz) + m[1] = 2 * (xy - zw) + m[2] = 2 * (zx + yw) + m[3] = 2 * (xy + zw) + m[4] = 1 - 2 * (zz + xx) + m[5] = 2 * (yz - xw) + m[6] = 2 * (zx - yw) + m[7] = 2 * (yz + xw) + m[8] = 1 - 2 * (xx + yy) + + return m + } + + matrix3ToQuaternion(m) { + var t = m[0] + m[4] + m[8] + var s, x, y, z, w + + if (t > 0) { + s = Math.sqrt(t + 1.0) * 2 + w = 0.25 * s + x = (m[7] - m[5]) / s + y = (m[2] - m[6]) / s + z = (m[3] - m[1]) / s + } else if (m[0] > m[4] && m[0] > m[8]) { + s = Math.sqrt(1.0 + m[0] - m[4] - m[8]) * 2 + w = (m[7] - m[5]) / s + x = 0.25 * s + y = (m[1] + m[3]) / s + z = (m[2] + m[6]) / s + } else if (m[4] > m[8]) { + s = Math.sqrt(1.0 + m[4] - m[0] - m[8]) * 2 + w = (m[2] - m[6]) / s + x = (m[1] + m[3]) / s + y = 0.25 * s + z = (m[5] + m[7]) / s + } else { + s = Math.sqrt(1.0 + m[8] - m[0] - m[4]) * 2 + w = (m[3] - m[1]) / s + x = (m[2] + m[6]) / s + y = (m[5] + m[7]) / s + z = 0.25 * s + } + + var q = this.allocQuaternion() + q.setX(x) + q.setY(y) + q.setZ(z) + q.setW(w) + return q + } +} + +/** + * @param {THREE.SkinnedMesh} mesh + * @param {Ammo.btDiscreteDynamicsWorld} world + * @param {Object} params + * @param {ResourceManager} manager + */ +class RigidBody { + constructor(mesh, world, params, manager) { this.mesh = mesh this.world = world this.params = params @@ -661,247 +654,247 @@ const MMDPhysics = (() => { this._init() } - RigidBody.prototype = { - constructor: MMDPhysics.RigidBody, + /** + * Resets rigid body transform to the current bone's. + * + * @return {RigidBody} + */ + reset() { + this._setTransformFromBone() + return this + } - /** - * Resets rigid body transform to the current bone's. - * - * @return {RigidBody} - */ - reset: function () { + /** + * Updates rigid body's transform from the current bone. + * + * @return {RidigBody} + */ + updateFromBone() { + if (this.params.boneIndex !== -1 && this.params.type === 0) { this._setTransformFromBone() - return this - }, + } - /** - * Updates rigid body's transform from the current bone. - * - * @return {RidigBody} - */ - updateFromBone: function () { - if (this.params.boneIndex !== -1 && this.params.type === 0) { - this._setTransformFromBone() - } + return this + } + /** + * Updates bone from the current ridid body's transform. + * + * @return {RidigBody} + */ + updateBone() { + if (this.params.type === 0 || this.params.boneIndex === -1) { return this - }, - - /** - * Updates bone from the current ridid body's transform. - * - * @return {RidigBody} - */ - updateBone: function () { - if (this.params.type === 0 || this.params.boneIndex === -1) { - return this - } + } - this._updateBoneRotation() + this._updateBoneRotation() - if (this.params.type === 1) { - this._updateBonePosition() - } + if (this.params.type === 1) { + this._updateBonePosition() + } - this.bone.updateMatrixWorld(true) + this.bone.updateMatrixWorld(true) - if (this.params.type === 2) { - this._setPositionFromBone() - } + if (this.params.type === 2) { + this._setPositionFromBone() + } - return this - }, + return this + } - // private methods + // private methods - _init: function () { - function generateShape(p) { - switch (p.shapeType) { - case 0: - return new Ammo.btSphereShape(p.width) + _init() { + function generateShape(p) { + switch (p.shapeType) { + case 0: + return new Ammo.btSphereShape(p.width) - case 1: - return new Ammo.btBoxShape(new Ammo.btVector3(p.width, p.height, p.depth)) + case 1: + return new Ammo.btBoxShape(new Ammo.btVector3(p.width, p.height, p.depth)) - case 2: - return new Ammo.btCapsuleShape(p.width, p.height) + case 2: + return new Ammo.btCapsuleShape(p.width, p.height) - default: - throw `unknown shape type ${p.shapeType}` - } + default: + throw new Error('unknown shape type ' + p.shapeType) } + } - const manager = this.manager - const params = this.params - const bones = this.mesh.skeleton.bones - const bone = params.boneIndex === -1 ? new Bone() : bones[params.boneIndex] + const manager = this.manager + const params = this.params + const bones = this.mesh.skeleton.bones + const bone = params.boneIndex === -1 ? new Bone() : bones[params.boneIndex] - const shape = generateShape(params) - const weight = params.type === 0 ? 0 : params.weight - const localInertia = manager.allocVector3() - localInertia.setValue(0, 0, 0) + const shape = generateShape(params) + const weight = params.type === 0 ? 0 : params.weight + const localInertia = manager.allocVector3() + localInertia.setValue(0, 0, 0) - if (weight !== 0) { - shape.calculateLocalInertia(weight, localInertia) - } + if (weight !== 0) { + shape.calculateLocalInertia(weight, localInertia) + } - const boneOffsetForm = manager.allocTransform() - manager.setIdentity(boneOffsetForm) - manager.setOriginFromArray3(boneOffsetForm, params.position) - manager.setBasisFromArray3(boneOffsetForm, params.rotation) + const boneOffsetForm = manager.allocTransform() + manager.setIdentity(boneOffsetForm) + manager.setOriginFromArray3(boneOffsetForm, params.position) + manager.setBasisFromArray3(boneOffsetForm, params.rotation) - const vector = manager.allocThreeVector3() - const boneForm = manager.allocTransform() - manager.setIdentity(boneForm) - manager.setOriginFromThreeVector3(boneForm, bone.getWorldPosition(vector)) + const vector = manager.allocThreeVector3() + const boneForm = manager.allocTransform() + manager.setIdentity(boneForm) + manager.setOriginFromThreeVector3(boneForm, bone.getWorldPosition(vector)) - const form = manager.multiplyTransforms(boneForm, boneOffsetForm) - const state = new Ammo.btDefaultMotionState(form) + const form = manager.multiplyTransforms(boneForm, boneOffsetForm) + const state = new Ammo.btDefaultMotionState(form) - const info = new Ammo.btRigidBodyConstructionInfo(weight, state, shape, localInertia) - info.set_m_friction(params.friction) - info.set_m_restitution(params.restitution) + const info = new Ammo.btRigidBodyConstructionInfo(weight, state, shape, localInertia) + info.set_m_friction(params.friction) + info.set_m_restitution(params.restitution) - const body = new Ammo.btRigidBody(info) + const body = new Ammo.btRigidBody(info) - if (params.type === 0) { - body.setCollisionFlags(body.getCollisionFlags() | 2) + if (params.type === 0) { + body.setCollisionFlags(body.getCollisionFlags() | 2) - /* - * It'd be better to comment out this line though in general I should call this method - * because I'm not sure why but physics will be more like MMD's - * if I comment out. - */ - body.setActivationState(4) - } + /* + * It'd be better to comment out this line though in general I should call this method + * because I'm not sure why but physics will be more like MMD's + * if I comment out. + */ + body.setActivationState(4) + } - body.setDamping(params.positionDamping, params.rotationDamping) - body.setSleepingThresholds(0, 0) + body.setDamping(params.positionDamping, params.rotationDamping) + body.setSleepingThresholds(0, 0) - this.world.addRigidBody(body, 1 << params.groupIndex, params.groupTarget) + this.world.addRigidBody(body, 1 << params.groupIndex, params.groupTarget) - this.body = body - this.bone = bone - this.boneOffsetForm = boneOffsetForm - this.boneOffsetFormInverse = manager.inverseTransform(boneOffsetForm) + this.body = body + this.bone = bone + this.boneOffsetForm = boneOffsetForm + this.boneOffsetFormInverse = manager.inverseTransform(boneOffsetForm) - manager.freeVector3(localInertia) - manager.freeTransform(form) - manager.freeTransform(boneForm) - manager.freeThreeVector3(vector) - }, + manager.freeVector3(localInertia) + manager.freeTransform(form) + manager.freeTransform(boneForm) + manager.freeThreeVector3(vector) + } - _getBoneTransform: function () { - const manager = this.manager - const p = manager.allocThreeVector3() - const q = manager.allocThreeQuaternion() - const s = manager.allocThreeVector3() + _getBoneTransform() { + const manager = this.manager + const p = manager.allocThreeVector3() + const q = manager.allocThreeQuaternion() + const s = manager.allocThreeVector3() - this.bone.matrixWorld.decompose(p, q, s) + this.bone.matrixWorld.decompose(p, q, s) - const tr = manager.allocTransform() - manager.setOriginFromThreeVector3(tr, p) - manager.setBasisFromThreeQuaternion(tr, q) + const tr = manager.allocTransform() + manager.setOriginFromThreeVector3(tr, p) + manager.setBasisFromThreeQuaternion(tr, q) - const form = manager.multiplyTransforms(tr, this.boneOffsetForm) + const form = manager.multiplyTransforms(tr, this.boneOffsetForm) - manager.freeTransform(tr) - manager.freeThreeVector3(s) - manager.freeThreeQuaternion(q) - manager.freeThreeVector3(p) + manager.freeTransform(tr) + manager.freeThreeVector3(s) + manager.freeThreeQuaternion(q) + manager.freeThreeVector3(p) - return form - }, + return form + } - _getWorldTransformForBone: function () { - const manager = this.manager - const tr = this.body.getCenterOfMassTransform() - return manager.multiplyTransforms(tr, this.boneOffsetFormInverse) - }, + _getWorldTransformForBone() { + const manager = this.manager + const tr = this.body.getCenterOfMassTransform() + return manager.multiplyTransforms(tr, this.boneOffsetFormInverse) + } - _setTransformFromBone: function () { - const manager = this.manager - const form = this._getBoneTransform() + _setTransformFromBone() { + const manager = this.manager + const form = this._getBoneTransform() - // TODO: check the most appropriate way to set - //this.body.setWorldTransform( form ); - this.body.setCenterOfMassTransform(form) - this.body.getMotionState().setWorldTransform(form) + // TODO: check the most appropriate way to set + //this.body.setWorldTransform( form ); + this.body.setCenterOfMassTransform(form) + this.body.getMotionState().setWorldTransform(form) - manager.freeTransform(form) - }, + manager.freeTransform(form) + } - _setPositionFromBone: function () { - const manager = this.manager - const form = this._getBoneTransform() + _setPositionFromBone() { + const manager = this.manager + const form = this._getBoneTransform() - const tr = manager.allocTransform() - this.body.getMotionState().getWorldTransform(tr) - manager.copyOrigin(tr, form) + const tr = manager.allocTransform() + this.body.getMotionState().getWorldTransform(tr) + manager.copyOrigin(tr, form) - // TODO: check the most appropriate way to set - //this.body.setWorldTransform( tr ); - this.body.setCenterOfMassTransform(tr) - this.body.getMotionState().setWorldTransform(tr) + // TODO: check the most appropriate way to set + //this.body.setWorldTransform( tr ); + this.body.setCenterOfMassTransform(tr) + this.body.getMotionState().setWorldTransform(tr) - manager.freeTransform(tr) - manager.freeTransform(form) - }, + manager.freeTransform(tr) + manager.freeTransform(form) + } - _updateBoneRotation: function () { - const manager = this.manager + _updateBoneRotation() { + const manager = this.manager - const tr = this._getWorldTransformForBone() - const q = manager.getBasis(tr) + const tr = this._getWorldTransformForBone() + const q = manager.getBasis(tr) - const thQ = manager.allocThreeQuaternion() - const thQ2 = manager.allocThreeQuaternion() - const thQ3 = manager.allocThreeQuaternion() + const thQ = manager.allocThreeQuaternion() + const thQ2 = manager.allocThreeQuaternion() + const thQ3 = manager.allocThreeQuaternion() - thQ.set(q.x(), q.y(), q.z(), q.w()) - thQ2.setFromRotationMatrix(this.bone.matrixWorld) - thQ2.conjugate() - thQ2.multiply(thQ) + thQ.set(q.x(), q.y(), q.z(), q.w()) + thQ2.setFromRotationMatrix(this.bone.matrixWorld) + thQ2.conjugate() + thQ2.multiply(thQ) - //this.bone.quaternion.multiply( thQ2 ); + //this.bone.quaternion.multiply( thQ2 ); - thQ3.setFromRotationMatrix(this.bone.matrix) + thQ3.setFromRotationMatrix(this.bone.matrix) - // Renormalizing quaternion here because repeatedly transforming - // quaternion continuously accumulates floating point error and - // can end up being overflow. See #15335 - this.bone.quaternion.copy(thQ2.multiply(thQ3).normalize()) + // Renormalizing quaternion here because repeatedly transforming + // quaternion continuously accumulates floating point error and + // can end up being overflow. See #15335 + this.bone.quaternion.copy(thQ2.multiply(thQ3).normalize()) - manager.freeThreeQuaternion(thQ) - manager.freeThreeQuaternion(thQ2) - manager.freeThreeQuaternion(thQ3) + manager.freeThreeQuaternion(thQ) + manager.freeThreeQuaternion(thQ2) + manager.freeThreeQuaternion(thQ3) - manager.freeQuaternion(q) - manager.freeTransform(tr) - }, + manager.freeQuaternion(q) + manager.freeTransform(tr) + } - _updateBonePosition: function () { - const manager = this.manager + _updateBonePosition() { + const manager = this.manager - const tr = this._getWorldTransformForBone() + const tr = this._getWorldTransformForBone() - const thV = manager.allocThreeVector3() + const thV = manager.allocThreeVector3() - const o = manager.getOrigin(tr) - thV.set(o.x(), o.y(), o.z()) + const o = manager.getOrigin(tr) + thV.set(o.x(), o.y(), o.z()) - if (this.bone.parent) { - this.bone.parent.worldToLocal(thV) - } + if (this.bone.parent) { + this.bone.parent.worldToLocal(thV) + } - this.bone.position.copy(thV) + this.bone.position.copy(thV) - manager.freeThreeVector3(thV) + manager.freeThreeVector3(thV) - manager.freeTransform(tr) - }, + manager.freeTransform(tr) } +} + +// +class Constraint { /** * @param {THREE.SkinnedMesh} mesh * @param {Ammo.btDiscreteDynamicsWorld} world @@ -910,7 +903,7 @@ const MMDPhysics = (() => { * @param {Object} params * @param {ResourceManager} manager */ - function Constraint(mesh, world, bodyA, bodyB, params, manager) { + constructor(mesh, world, bodyA, bodyB, params, manager) { this.mesh = mesh this.world = world this.bodyA = bodyA @@ -923,103 +916,105 @@ const MMDPhysics = (() => { this._init() } - Constraint.prototype = { - constructor: Constraint, - - // private method + // private method - _init: function () { - const manager = this.manager - const params = this.params - const bodyA = this.bodyA - const bodyB = this.bodyB + _init() { + const manager = this.manager + const params = this.params + const bodyA = this.bodyA + const bodyB = this.bodyB - const form = manager.allocTransform() - manager.setIdentity(form) - manager.setOriginFromArray3(form, params.position) - manager.setBasisFromArray3(form, params.rotation) + const form = manager.allocTransform() + manager.setIdentity(form) + manager.setOriginFromArray3(form, params.position) + manager.setBasisFromArray3(form, params.rotation) - const formA = manager.allocTransform() - const formB = manager.allocTransform() + const formA = manager.allocTransform() + const formB = manager.allocTransform() - bodyA.body.getMotionState().getWorldTransform(formA) - bodyB.body.getMotionState().getWorldTransform(formB) + bodyA.body.getMotionState().getWorldTransform(formA) + bodyB.body.getMotionState().getWorldTransform(formB) - const formInverseA = manager.inverseTransform(formA) - const formInverseB = manager.inverseTransform(formB) + const formInverseA = manager.inverseTransform(formA) + const formInverseB = manager.inverseTransform(formB) - const formA2 = manager.multiplyTransforms(formInverseA, form) - const formB2 = manager.multiplyTransforms(formInverseB, form) + const formA2 = manager.multiplyTransforms(formInverseA, form) + const formB2 = manager.multiplyTransforms(formInverseB, form) - const constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA.body, bodyB.body, formA2, formB2, true) + const constraint = new Ammo.btGeneric6DofSpringConstraint(bodyA.body, bodyB.body, formA2, formB2, true) - const lll = manager.allocVector3() - const lul = manager.allocVector3() - const all = manager.allocVector3() - const aul = manager.allocVector3() + const lll = manager.allocVector3() + const lul = manager.allocVector3() + const all = manager.allocVector3() + const aul = manager.allocVector3() - lll.setValue(params.translationLimitation1[0], params.translationLimitation1[1], params.translationLimitation1[2]) - lul.setValue(params.translationLimitation2[0], params.translationLimitation2[1], params.translationLimitation2[2]) - all.setValue(params.rotationLimitation1[0], params.rotationLimitation1[1], params.rotationLimitation1[2]) - aul.setValue(params.rotationLimitation2[0], params.rotationLimitation2[1], params.rotationLimitation2[2]) + lll.setValue(params.translationLimitation1[0], params.translationLimitation1[1], params.translationLimitation1[2]) + lul.setValue(params.translationLimitation2[0], params.translationLimitation2[1], params.translationLimitation2[2]) + all.setValue(params.rotationLimitation1[0], params.rotationLimitation1[1], params.rotationLimitation1[2]) + aul.setValue(params.rotationLimitation2[0], params.rotationLimitation2[1], params.rotationLimitation2[2]) - constraint.setLinearLowerLimit(lll) - constraint.setLinearUpperLimit(lul) - constraint.setAngularLowerLimit(all) - constraint.setAngularUpperLimit(aul) + constraint.setLinearLowerLimit(lll) + constraint.setLinearUpperLimit(lul) + constraint.setAngularLowerLimit(all) + constraint.setAngularUpperLimit(aul) - for (let i = 0; i < 3; i++) { - if (params.springPosition[i] !== 0) { - constraint.enableSpring(i, true) - constraint.setStiffness(i, params.springPosition[i]) - } + for (let i = 0; i < 3; i++) { + if (params.springPosition[i] !== 0) { + constraint.enableSpring(i, true) + constraint.setStiffness(i, params.springPosition[i]) } + } - for (let i = 0; i < 3; i++) { - if (params.springRotation[i] !== 0) { - constraint.enableSpring(i + 3, true) - constraint.setStiffness(i + 3, params.springRotation[i]) - } + for (let i = 0; i < 3; i++) { + if (params.springRotation[i] !== 0) { + constraint.enableSpring(i + 3, true) + constraint.setStiffness(i + 3, params.springRotation[i]) } + } - /* - * Currently(10/31/2016) official ammo.js doesn't support - * btGeneric6DofSpringConstraint.setParam method. - * You need custom ammo.js (add the method into idl) if you wanna use. - * By setting this parameter, physics will be more like MMD's - */ - if (constraint.setParam !== undefined) { - for (let i = 0; i < 6; i++) { - // this parameter is from http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js - constraint.setParam(2, 0.475, i) - } + /* + * Currently(10/31/2016) official ammo.js doesn't support + * btGeneric6DofSpringConstraint.setParam method. + * You need custom ammo.js (add the method into idl) if you wanna use. + * By setting this parameter, physics will be more like MMD's + */ + if (constraint.setParam !== undefined) { + for (let i = 0; i < 6; i++) { + constraint.setParam(2, 0.475, i) } + } - this.world.addConstraint(constraint, true) - this.constraint = constraint - - manager.freeTransform(form) - manager.freeTransform(formA) - manager.freeTransform(formB) - manager.freeTransform(formInverseA) - manager.freeTransform(formInverseB) - manager.freeTransform(formA2) - manager.freeTransform(formB2) - manager.freeVector3(lll) - manager.freeVector3(lul) - manager.freeVector3(all) - manager.freeVector3(aul) - }, + this.world.addConstraint(constraint, true) + this.constraint = constraint + + manager.freeTransform(form) + manager.freeTransform(formA) + manager.freeTransform(formB) + manager.freeTransform(formInverseA) + manager.freeTransform(formInverseB) + manager.freeTransform(formA2) + manager.freeTransform(formB2) + manager.freeVector3(lll) + manager.freeVector3(lul) + manager.freeVector3(all) + manager.freeVector3(aul) } +} +const _position = /* @__PURE__ */ new Vector3() +const _quaternion = /* @__PURE__ */ new Quaternion() +const _scale = /* @__PURE__ */ new Vector3() +const _matrixWorldInv = /* @__PURE__ */ new Matrix4() + +class MMDPhysicsHelper extends Object3D { /** * Visualize Rigid bodies * * @param {THREE.SkinnedMesh} mesh * @param {Physics} physics */ - function MMDPhysicsHelper(mesh, physics) { - Object3D.call(this) + constructor(mesh, physics) { + super() this.root = mesh this.physics = physics @@ -1065,106 +1060,89 @@ const MMDPhysics = (() => { this._init() } - MMDPhysicsHelper.prototype = Object.assign(Object.create(Object3D.prototype), { - constructor: MMDPhysicsHelper, + /** + * Frees the GPU-related resources allocated by this instance. Call this method whenever this instance is no longer used in your app. + */ + dispose() { + const materials = this.materials + const children = this.children - /** - * Updates Rigid Bodies visualization. - */ - updateMatrixWorld: (() => { - const position = new Vector3() - const quaternion = new Quaternion() - const scale = new Vector3() - const matrixWorldInv = new Matrix4() - - return function updateMatrixWorld(force) { - const mesh = this.root - - if (this.visible) { - const bodies = this.physics.bodies - - matrixWorldInv - .copy(mesh.matrixWorld) - .decompose(position, quaternion, scale) - .compose(position, quaternion, scale.set(1, 1, 1)) - .invert() - - for (let i = 0, il = bodies.length; i < il; i++) { - const body = bodies[i].body - const child = this.children[i] - - const tr = body.getCenterOfMassTransform() - const origin = tr.getOrigin() - const rotation = tr.getRotation() - - child.position.set(origin.x(), origin.y(), origin.z()).applyMatrix4(matrixWorldInv) - - child.quaternion - .setFromRotationMatrix(matrixWorldInv) - .multiply(quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w())) - } - } - - this.matrix - .copy(mesh.matrixWorld) - .decompose(position, quaternion, scale) - .compose(position, quaternion, scale.set(1, 1, 1)) - - Object3D.prototype.updateMatrixWorld.call(this, force) - } - })(), + for (let i = 0; i < materials.length; i++) { + materials[i].dispose() + } - // private method + for (let i = 0; i < children.length; i++) { + const child = children[i] - _init: function () { - const bodies = this.physics.bodies + if (child.isMesh) child.geometry.dispose() + } + } + + /** + * Updates Rigid Bodies visualization. + */ + updateMatrixWorld(force) { + var mesh = this.root - function createGeometry(param) { - switch (param.shapeType) { - case 0: - return new SphereGeometry(param.width, 16, 8) + if (this.visible) { + var bodies = this.physics.bodies - case 1: - return new BoxGeometry(param.width * 2, param.height * 2, param.depth * 2, 8, 8, 8) + _matrixWorldInv + .copy(mesh.matrixWorld) + .decompose(_position, _quaternion, _scale) + .compose(_position, _quaternion, _scale.set(1, 1, 1)) + .invert() - case 2: - return new createCapsuleGeometry(param.width, param.height, 16, 8) + for (var i = 0, il = bodies.length; i < il; i++) { + var body = bodies[i].body + var child = this.children[i] - default: - return null - } + var tr = body.getCenterOfMassTransform() + var origin = tr.getOrigin() + var rotation = tr.getRotation() + + child.position.set(origin.x(), origin.y(), origin.z()).applyMatrix4(_matrixWorldInv) + + child.quaternion + .setFromRotationMatrix(_matrixWorldInv) + .multiply(_quaternion.set(rotation.x(), rotation.y(), rotation.z(), rotation.w())) } + } - // copy from http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mytest37.js?ver=20160815 - function createCapsuleGeometry(radius, cylinderHeight, segmentsRadius, segmentsHeight) { - const geometry = new CylinderGeometry(radius, radius, cylinderHeight, segmentsRadius, segmentsHeight, true) - const upperSphere = new Mesh( - new SphereGeometry(radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, 0, Math.PI / 2), - ) - const lowerSphere = new Mesh( - new SphereGeometry(radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, Math.PI / 2, Math.PI / 2), - ) + this.matrix + .copy(mesh.matrixWorld) + .decompose(_position, _quaternion, _scale) + .compose(_position, _quaternion, _scale.set(1, 1, 1)) - upperSphere.position.set(0, cylinderHeight / 2, 0) - lowerSphere.position.set(0, -cylinderHeight / 2, 0) + super.updateMatrixWorld(force) + } - upperSphere.updateMatrix() - lowerSphere.updateMatrix() + // private method - geometry.merge(upperSphere.geometry, upperSphere.matrix) - geometry.merge(lowerSphere.geometry, lowerSphere.matrix) + _init() { + var bodies = this.physics.bodies - return geometry - } + function createGeometry(param) { + switch (param.shapeType) { + case 0: + return new SphereGeometry(param.width, 16, 8) + + case 1: + return new BoxGeometry(param.width * 2, param.height * 2, param.depth * 2, 8, 8, 8) + + case 2: + return new CapsuleGeometry(param.width, param.height, 8, 16) - for (let i = 0, il = bodies.length; i < il; i++) { - const param = bodies[i].params - this.add(new Mesh(createGeometry(param), this.materials[param.type])) + default: + return null } - }, - }) + } - return MMDPhysics -})() + for (var i = 0, il = bodies.length; i < il; i++) { + var param = bodies[i].params + this.add(new Mesh(createGeometry(param), this.materials[param.type])) + } + } +} export { MMDPhysics } diff --git a/src/cameras/CinematicCamera.d.ts b/src/cameras/CinematicCamera.d.ts index 3b0eb438..770ae220 100644 --- a/src/cameras/CinematicCamera.d.ts +++ b/src/cameras/CinematicCamera.d.ts @@ -1,6 +1,6 @@ import { PerspectiveCamera, ShaderMaterial, Scene, WebGLRenderer, OrthographicCamera, WebGLRenderTarget } from 'three' -import { BokehShaderUniforms } from './../shaders/BokehShader2' +import { BokehShader2Uniforms } from './../shaders/BokehShader2' export class CinematicCamera extends PerspectiveCamera { constructor(fov: number, aspect: number, near: number, far: number) @@ -11,7 +11,7 @@ export class CinematicCamera extends PerspectiveCamera { camera: OrthographicCamera rtTextureDepth: WebGLRenderTarget rtTextureColor: WebGLRenderTarget - bokeh_uniforms: BokehShaderUniforms + bokeh_uniforms: BokehShader2Uniforms } shaderSettings: { rings: number diff --git a/src/controls/ArcballControls.ts b/src/controls/ArcballControls.ts index c9b7c5ec..f32b4eb5 100644 --- a/src/controls/ArcballControls.ts +++ b/src/controls/ArcballControls.ts @@ -67,8 +67,8 @@ const _center = { //transformation matrices for gizmos and camera const _transformation: Transformation = { - camera: new Matrix4(), - gizmos: new Matrix4(), + camera: /* @__PURE__ */ new Matrix4(), + gizmos: /* @__PURE__ */ new Matrix4(), } //events @@ -360,7 +360,7 @@ class ArcballControls extends EventDispatcher { } const newRadius = this._tbRadius / scale - // @ts-expect-error + // @ts-ignore const curve = new EllipseCurve(0, 0, newRadius, newRadius) const points = curve.getPoints(this._curvePts) const curveGeometry = new BufferGeometry().setFromPoints(points) @@ -656,14 +656,14 @@ class ArcballControls extends EventDispatcher { if (this.cursorZoom && this.enablePan) { let scalePoint - if (this.camera?.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { scalePoint = this.unprojectOnTbPlane(this.camera, event.clientX, event.clientY, this.domElement) ?.applyQuaternion(this.camera.quaternion) .multiplyScalar(1 / this.camera.zoom) .add(this._gizmos.position) } - if (this.camera?.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { scalePoint = this.unprojectOnTbPlane(this.camera, event.clientX, event.clientY, this.domElement) ?.applyQuaternion(this.camera.quaternion) .add(this._gizmos.position) @@ -687,7 +687,7 @@ class ArcballControls extends EventDispatcher { break case 'FOV': - if (this.camera?.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { this.updateTbState(STATE.FOV, true) //Vertigo effect @@ -822,7 +822,7 @@ class ArcballControls extends EventDispatcher { case 'FOV': if (!this.enableZoom) return - if (this.camera?.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { if (this._animationId != -1) { cancelAnimationFrame(this._animationId) this._animationId = -1 @@ -999,7 +999,7 @@ class ArcballControls extends EventDispatcher { break case STATE.FOV: - if (this.enableZoom && this.camera?.type === 'PerspectiveCamera') { + if (this.enableZoom && this.camera instanceof PerspectiveCamera) { if (restart) { //switch to fov operation @@ -1287,12 +1287,12 @@ class ArcballControls extends EventDispatcher { if (!this.enablePan) { scalePoint = this._gizmos.position } else { - if (this.camera?.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { scalePoint = this.unprojectOnTbPlane(this.camera, _center.x, _center.y, this.domElement) ?.applyQuaternion(this.camera.quaternion) .multiplyScalar(1 / this.camera.zoom) .add(this._gizmos.position) - } else if (this.camera?.type === 'PerspectiveCamera') { + } else if (this.camera instanceof PerspectiveCamera) { scalePoint = this.unprojectOnTbPlane(this.camera, _center.x, _center.y, this.domElement) ?.applyQuaternion(this.camera.quaternion) .add(this._gizmos.position) @@ -1699,11 +1699,11 @@ class ArcballControls extends EventDispatcher { const factor = 0.67 const distance = camera.position.distanceTo(this._gizmos.position) - if (camera.type == 'PerspectiveCamera') { + if (camera instanceof PerspectiveCamera) { const halfFovV = MathUtils.DEG2RAD * camera.fov * 0.5 //vertical fov/2 in radians const halfFovH = Math.atan(camera.aspect * Math.tan(halfFovV)) //horizontal fov/2 in radians return Math.tan(Math.min(halfFovV, halfFovH)) * distance * factor - } else if (camera.type == 'OrthographicCamera') { + } else if (camera instanceof OrthographicCamera) { return Math.min(camera.top, camera.right) * factor } } @@ -1749,7 +1749,7 @@ class ArcballControls extends EventDispatcher { const multiplier = 3 let size, divisions, maxLength, tick - if (this.camera?.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { const width = this.camera.right - this.camera.left const height = this.camera.bottom - this.camera.top @@ -1758,7 +1758,7 @@ class ArcballControls extends EventDispatcher { size = (maxLength / this.camera.zoom) * multiplier divisions = (size / tick) * this.camera.zoom - } else if (this.camera?.type === 'PerspectiveCamera') { + } else if (this.camera instanceof PerspectiveCamera) { const distance = this.camera.position.distanceTo(this._gizmos.position) const halfFovV = MathUtils.DEG2RAD * this.camera.fov * 0.5 const halfFovH = Math.atan(this.camera.aspect * Math.tan(halfFovV)) @@ -1875,7 +1875,7 @@ class ArcballControls extends EventDispatcher { */ private getCursorPosition = (cursorX: number, cursorY: number, canvas: HTMLElement): Vector2 => { this._v2_1.copy(this.getCursorNDC(cursorX, cursorY, canvas)) - if (this.camera?.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { this._v2_1.x *= (this.camera.right - this.camera.left) * 0.5 this._v2_1.y *= (this.camera.top - this.camera.bottom) * 0.5 } @@ -1892,7 +1892,7 @@ class ArcballControls extends EventDispatcher { camera.updateMatrix() //setting state - if (camera?.type == 'PerspectiveCamera') { + if (camera instanceof PerspectiveCamera) { this._fov0 = camera.fov this._fovState = camera.fov } @@ -1942,7 +1942,7 @@ class ArcballControls extends EventDispatcher { * @param {number} tbRadius The trackball radius */ private makeGizmos = (tbCenter: Vector3, tbRadius: number): void => { - // @ts-expect-error + // @ts-ignore const curve = new EllipseCurve(0, 0, tbRadius, tbRadius) const points = curve.getPoints(this._curvePts) @@ -2099,12 +2099,12 @@ class ArcballControls extends EventDispatcher { if (this.camera) { const movement = p0.clone().sub(p1) - if (this.camera.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { //adjust movement amount movement.multiplyScalar(1 / this.camera.zoom) } - if (this.camera.type === 'PerspectiveCamera' && adjust) { + if (this.camera instanceof PerspectiveCamera && adjust) { //adjust movement amount this._v3_1.setFromMatrixPosition(this._cameraMatrixState0) //camera's initial position this._v3_2.setFromMatrixPosition(this._gizmoMatrixState0) //gizmo's initial position @@ -2129,7 +2129,7 @@ class ArcballControls extends EventDispatcher { if (this.camera) { this.camera.zoom = this._zoom0 - if (this.camera.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { this.camera.fov = this._fov0 } @@ -2184,7 +2184,7 @@ class ArcballControls extends EventDispatcher { public copyState = (): void => { if (this.camera) { const state = JSON.stringify( - this.camera?.type === 'OrthographicCamera' + this.camera instanceof OrthographicCamera ? { arcballState: { cameraFar: this.camera.far, @@ -2232,7 +2232,7 @@ class ArcballControls extends EventDispatcher { this._zoom0 = this.camera.zoom this._up0.copy(this.camera.up) - if (this.camera.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { this._fov0 = this.camera.fov } } @@ -2250,7 +2250,7 @@ class ArcballControls extends EventDispatcher { const scalePoint = point.clone() let sizeInverse = 1 / size - if (this.camera.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { //camera zoom this.camera.zoom = this._zoomState this.camera.zoom *= size @@ -2288,7 +2288,7 @@ class ArcballControls extends EventDispatcher { return _transformation } - if (this.camera.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { this._v3_1.setFromMatrixPosition(this._cameraMatrixState) this._v3_2.setFromMatrixPosition(this._gizmoMatrixState) @@ -2341,7 +2341,7 @@ class ArcballControls extends EventDispatcher { * @param {Number} value fov to be setted */ private setFov = (value: number): void => { - if (this.camera?.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { this.camera.fov = MathUtils.clamp(value, this.minFov, this.maxFov) this.camera.updateProjectionMatrix() } @@ -2457,7 +2457,7 @@ class ArcballControls extends EventDispatcher { canvas: HTMLElement, tbRadius: number, ): Vector3 | undefined => { - if (camera.type == 'OrthographicCamera') { + if (camera instanceof OrthographicCamera) { this._v2_1.copy(this.getCursorPosition(cursorX, cursorY, canvas)) this._v3_1.set(this._v2_1.x, this._v2_1.y, 0) @@ -2476,7 +2476,7 @@ class ArcballControls extends EventDispatcher { return this._v3_1 } - if (camera.type == 'PerspectiveCamera') { + if (camera instanceof PerspectiveCamera) { //unproject cursor on the near plane this._v2_1.copy(this.getCursorNDC(cursorX, cursorY, canvas)) @@ -2578,14 +2578,14 @@ class ArcballControls extends EventDispatcher { canvas: HTMLElement, initialDistance = false, ): Vector3 | undefined => { - if (camera.type == 'OrthographicCamera') { + if (camera instanceof OrthographicCamera) { this._v2_1.copy(this.getCursorPosition(cursorX, cursorY, canvas)) this._v3_1.set(this._v2_1.x, this._v2_1.y, 0) return this._v3_1.clone() } - if (camera.type == 'PerspectiveCamera') { + if (camera instanceof PerspectiveCamera) { this._v2_1.copy(this.getCursorNDC(cursorX, cursorY, canvas)) //unproject cursor on the near plane @@ -2650,13 +2650,13 @@ class ArcballControls extends EventDispatcher { this._cameraMatrixState.copy(this.camera.matrix) this._gizmoMatrixState.copy(this._gizmos.matrix) - if (this.camera.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { this._cameraProjectionState.copy(this.camera.projectionMatrix) this.camera.updateProjectionMatrix() this._zoomState = this.camera.zoom } - if (this.camera.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { this._fovState = this.camera.fov } } @@ -2690,7 +2690,7 @@ class ArcballControls extends EventDispatcher { if (!this.camera) return //check min/max parameters - if (this.camera.type === 'OrthographicCamera') { + if (this.camera instanceof OrthographicCamera) { //check zoom if (this.camera.zoom > this.maxZoom || this.camera.zoom < this.minZoom) { const newZoom = MathUtils.clamp(this.camera.zoom, this.minZoom, this.maxZoom) @@ -2698,7 +2698,7 @@ class ArcballControls extends EventDispatcher { } } - if (this.camera.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { //check distance const distance = this.camera.position.distanceTo(this._gizmos.position) @@ -2723,7 +2723,7 @@ class ArcballControls extends EventDispatcher { if (oldRadius < this._tbRadius - EPS || oldRadius > this._tbRadius + EPS) { const scale = (this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z) / 3 const newRadius = this._tbRadius / scale - // @ts-expect-error + // @ts-ignore const curve = new EllipseCurve(0, 0, newRadius, newRadius) const points = curve.getPoints(this._curvePts) const curveGeometry = new BufferGeometry().setFromPoints(points) @@ -2751,7 +2751,7 @@ class ArcballControls extends EventDispatcher { this.camera.zoom = state.arcballState.cameraZoom - if (this.camera.type === 'PerspectiveCamera') { + if (this.camera instanceof PerspectiveCamera) { this.camera.fov = state.arcballState.cameraFov } diff --git a/src/controls/DeviceOrientationControls.ts b/src/controls/DeviceOrientationControls.ts index bcf96b9e..64eb1a8b 100644 --- a/src/controls/DeviceOrientationControls.ts +++ b/src/controls/DeviceOrientationControls.ts @@ -58,16 +58,18 @@ class DeviceOrientationControls extends EventDispatcher { if ( window.DeviceOrientationEvent !== undefined && + // @ts-ignore typeof window.DeviceOrientationEvent.requestPermission === 'function' ) { + // @ts-ignore window.DeviceOrientationEvent.requestPermission() - .then((response) => { + .then((response: any) => { if (response == 'granted') { window.addEventListener('orientationchange', this.onScreenOrientationChangeEvent) window.addEventListener('deviceorientation', this.onDeviceOrientationChangeEvent) } }) - .catch((error) => { + .catch((error: any) => { console.error('THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:', error) }) } else { diff --git a/src/controls/FirstPersonControls.ts b/src/controls/FirstPersonControls.ts index 79fdd5de..8ef3db73 100644 --- a/src/controls/FirstPersonControls.ts +++ b/src/controls/FirstPersonControls.ts @@ -1,6 +1,6 @@ import { MathUtils, Spherical, Vector3, EventDispatcher, Camera } from 'three' -const targetPosition = new Vector3() +const targetPosition = /* @__PURE__ */ new Vector3() export class FirstPersonControls extends EventDispatcher { public object: Camera diff --git a/src/controls/PointerLockControls.ts b/src/controls/PointerLockControls.ts index 9170daf6..5548e716 100644 --- a/src/controls/PointerLockControls.ts +++ b/src/controls/PointerLockControls.ts @@ -1,7 +1,7 @@ import { Euler, Camera, EventDispatcher, Vector3 } from 'three' -const _euler = new Euler(0, 0, 0, 'YXZ') -const _vector = new Vector3() +const _euler = /* @__PURE__ */ new Euler(0, 0, 0, 'YXZ') +const _vector = /* @__PURE__ */ new Vector3() const _changeEvent = { type: 'change' } const _lockEvent = { type: 'lock' } const _unlockEvent = { type: 'unlock' } diff --git a/src/controls/TransformControls.ts b/src/controls/TransformControls.ts index a76677e9..edfd5790 100644 --- a/src/controls/TransformControls.ts +++ b/src/controls/TransformControls.ts @@ -24,6 +24,7 @@ import { TorusGeometry, Vector3, Camera, + Vector2, } from 'three' export interface TransformControlsPointerObject { @@ -255,7 +256,7 @@ class TransformControls extends Object3D { private pointerHover = (pointer: TransformControlsPointerObject): void => { if (this.object === undefined || this.dragging === true) return - this.raycaster.setFromCamera(pointer, this.camera) + this.raycaster.setFromCamera((pointer as unknown) as Vector2, this.camera) const intersect = this.intersectObjectWithRay(this.gizmo.picker[this.mode], this.raycaster) @@ -270,7 +271,7 @@ class TransformControls extends Object3D { if (this.object === undefined || this.dragging === true || pointer.button !== 0) return if (this.axis !== null) { - this.raycaster.setFromCamera(pointer, this.camera) + this.raycaster.setFromCamera((pointer as unknown) as Vector2, this.camera) const planeIntersect = this.intersectObjectWithRay(this.plane, this.raycaster, true) @@ -326,7 +327,7 @@ class TransformControls extends Object3D { if (object === undefined || axis === null || this.dragging === false || pointer.button !== -1) return - this.raycaster.setFromCamera(pointer, this.camera) + this.raycaster.setFromCamera((pointer as unknown) as Vector2, this.camera) const planeIntersect = this.intersectObjectWithRay(this.plane, this.raycaster, true) diff --git a/src/csm/CSM.js b/src/csm/CSM.js index afa6cdee..ce5359af 100644 --- a/src/csm/CSM.js +++ b/src/csm/CSM.js @@ -2,10 +2,10 @@ import { Vector2, Vector3, DirectionalLight, MathUtils, ShaderChunk, Matrix4, Bo import { CSMFrustum } from './CSMFrustum' import { CSMShader } from './CSMShader' -const _cameraToLightMatrix = new Matrix4() -const _lightSpaceFrustum = new CSMFrustum() -const _center = new Vector3() -const _bbox = new Box3() +const _cameraToLightMatrix = /* @__PURE__ */ new Matrix4() +const _lightSpaceFrustum = /* @__PURE__ */ new CSMFrustum() +const _center = /* @__PURE__ */ new Vector3() +const _bbox = /* @__PURE__ */ new Box3() const _uniformArray = [] const _logArray = [] diff --git a/src/csm/CSMFrustum.js b/src/csm/CSMFrustum.js index ab8f7d15..3e375417 100644 --- a/src/csm/CSMFrustum.js +++ b/src/csm/CSMFrustum.js @@ -1,6 +1,6 @@ import { Vector3, Matrix4 } from 'three' -const inverseProjectionMatrix = new Matrix4() +const inverseProjectionMatrix = /* @__PURE__ */ new Matrix4() class CSMFrustum { constructor(data) { diff --git a/src/csm/CSMShader.js b/src/csm/CSMShader.js index c816792c..8afa7e48 100644 --- a/src/csm/CSMShader.js +++ b/src/csm/CSMShader.js @@ -239,14 +239,17 @@ IncidentLight directLight; #endif `, - lights_pars_begin: - /* glsl */ ` -#if defined( USE_CSM ) && defined( CSM_CASCADES ) -uniform vec2 CSM_cascades[CSM_CASCADES]; -uniform float cameraNear; -uniform float shadowFar; -#endif - ` + ShaderChunk.lights_pars_begin, + getlights_pars_begin() { + return /* glsl */ ` + #if defined( USE_CSM ) && defined( CSM_CASCADES ) + uniform vec2 CSM_cascades[CSM_CASCADES]; + uniform float cameraNear; + uniform float shadowFar; + #endif + + ${ShaderChunk.lights_pars_begin} + ` + }, } export { CSMShader } diff --git a/src/curves/CurveExtras.js b/src/curves/CurveExtras.js index 419e26b6..19e0919a 100644 --- a/src/curves/CurveExtras.js +++ b/src/curves/CurveExtras.js @@ -5,20 +5,16 @@ import { Curve, Vector3 } from 'three' * * Formulas collected from various sources * http://mathworld.wolfram.com/HeartCurve.html - * http://mathdl.maa.org/images/upload_library/23/stemkoski/knots/page6.html * http://en.wikipedia.org/wiki/Viviani%27s_curve - * http://mathdl.maa.org/images/upload_library/23/stemkoski/knots/page4.html * http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf * https://prideout.net/blog/old/blog/index.html@p=44.html */ -export class GrannyKnot extends Curve { - constructor() { - super() - } +// GrannyKnot - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() +class GrannyKnot extends Curve { + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t = 2 * Math.PI * t @@ -32,15 +28,15 @@ export class GrannyKnot extends Curve { // HeartCurve -export class HeartCurve extends Curve { - constructor(scale) { +class HeartCurve extends Curve { + constructor(scale = 5) { super() - this.scale = scale === undefined ? 5 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t *= 2 * Math.PI @@ -54,15 +50,15 @@ export class HeartCurve extends Curve { // Viviani's Curve -export class VivianiCurve extends Curve { - constructor(scale) { +class VivianiCurve extends Curve { + constructor(scale = 70) { super() - this.scale = scale === undefined ? 70 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t = t * 4 * Math.PI // normalized to 0..1 const a = this.scale / 2 @@ -77,13 +73,9 @@ export class VivianiCurve extends Curve { // KnotCurve -export class KnotCurve extends Curve { - constructor() { - super() - } - - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() +class KnotCurve extends Curve { + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t *= 2 * Math.PI @@ -100,13 +92,9 @@ export class KnotCurve extends Curve { // HelixCurve -export class HelixCurve extends Curve { - constructor() { - super() - } - - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() +class HelixCurve extends Curve { + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget const a = 30 // radius const b = 150 // height @@ -123,15 +111,15 @@ export class HelixCurve extends Curve { // TrefoilKnot -export class TrefoilKnot extends Curve { - constructor(scale) { +class TrefoilKnot extends Curve { + constructor(scale = 10) { super() - this.scale = scale === undefined ? 10 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t *= Math.PI * 2 @@ -145,15 +133,15 @@ export class TrefoilKnot extends Curve { // TorusKnot -export class TorusKnot extends Curve { - constructor(scale) { +class TorusKnot extends Curve { + constructor(scale = 10) { super() - this.scale = scale === undefined ? 10 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget const p = 3 const q = 4 @@ -170,15 +158,15 @@ export class TorusKnot extends Curve { // CinquefoilKnot -export class CinquefoilKnot extends Curve { - constructor(scale) { +class CinquefoilKnot extends Curve { + constructor(scale = 10) { super() - this.scale = scale === undefined ? 10 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget const p = 2 const q = 5 @@ -195,15 +183,15 @@ export class CinquefoilKnot extends Curve { // TrefoilPolynomialKnot -export class TrefoilPolynomialKnot extends Curve { - constructor(scale) { +class TrefoilPolynomialKnot extends Curve { + constructor(scale = 10) { super() - this.scale = scale === undefined ? 10 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t = t * 4 - 2 @@ -215,22 +203,22 @@ export class TrefoilPolynomialKnot extends Curve { } } -export const scaleTo = (x, y, t) => { +function scaleTo(x, y, t) { const r = y - x return t * r + x } // FigureEightPolynomialKnot -export class FigureEightPolynomialKnot extends Curve { - constructor(scale) { +class FigureEightPolynomialKnot extends Curve { + constructor(scale = 1) { super() - this.scale = scale === undefined ? 1 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t = scaleTo(-4, 4, t) @@ -244,15 +232,15 @@ export class FigureEightPolynomialKnot extends Curve { // DecoratedTorusKnot4a -export class DecoratedTorusKnot4a extends Curve { - constructor(scale) { +class DecoratedTorusKnot4a extends Curve { + constructor(scale = 40) { super() - this.scale = scale === undefined ? 40 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget t *= Math.PI * 2 @@ -266,15 +254,15 @@ export class DecoratedTorusKnot4a extends Curve { // DecoratedTorusKnot4b -export class DecoratedTorusKnot4b extends Curve { - constructor(scale) { +class DecoratedTorusKnot4b extends Curve { + constructor(scale = 40) { super() - this.scale = scale === undefined ? 40 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget const fi = t * Math.PI * 2 @@ -288,15 +276,15 @@ export class DecoratedTorusKnot4b extends Curve { // DecoratedTorusKnot5a -export class DecoratedTorusKnot5a extends Curve { - constructor(scale) { +class DecoratedTorusKnot5a extends Curve { + constructor(scale = 40) { super() - this.scale = scale === undefined ? 40 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget const fi = t * Math.PI * 2 @@ -310,15 +298,15 @@ export class DecoratedTorusKnot5a extends Curve { // DecoratedTorusKnot5c -export class DecoratedTorusKnot5c extends Curve { - constructor(scale) { +class DecoratedTorusKnot5c extends Curve { + constructor(scale = 40) { super() - this.scale = scale === undefined ? 40 : scale + this.scale = scale } - getPoint(t, optionalTarget) { - const point = optionalTarget || new Vector3() + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget const fi = t * Math.PI * 2 @@ -329,3 +317,20 @@ export class DecoratedTorusKnot5c extends Curve { return point.set(x, y, z).multiplyScalar(this.scale) } } + +export { + GrannyKnot, + HeartCurve, + VivianiCurve, + KnotCurve, + HelixCurve, + TrefoilKnot, + TorusKnot, + CinquefoilKnot, + TrefoilPolynomialKnot, + FigureEightPolynomialKnot, + DecoratedTorusKnot4a, + DecoratedTorusKnot4b, + DecoratedTorusKnot5a, + DecoratedTorusKnot5c, +} diff --git a/src/curves/NURBSUtils.d.ts b/src/curves/NURBSUtils.d.ts index 314c3e71..ade7c162 100644 --- a/src/curves/NURBSUtils.d.ts +++ b/src/curves/NURBSUtils.d.ts @@ -1,22 +1,20 @@ import { Vector3, Vector4 } from 'three' -export namespace NURBSUtils { - function findSpan(p: number, u: number, U: number[]): number - function calcBasisFunctions(span: number, u: number, p: number, U: number[]): number[] - function calcBSplinePoint(p: number, U: number[], P: Vector4[], u: number): Vector4 - function calcBasisFunctionDerivatives(span: number, u: number, p: number, n: number, U: number[]): number[][] - function calcBSplineDerivatives(p: number, U: number[], P: Vector4[], u: number, nd: number): Vector4[] - function calcKoverI(k: number, i: number): number - function calcRationalCurveDerivatives(Pders: Vector4[]): Vector3[] - function calcNURBSDerivatives(p: number, U: number[], P: Vector4[], u: number, nd: number): Vector3[] - function calcSurfacePoint( - p: number, - q: number, - U: number[], - V: number[], - P: Vector4[], - u: number, - v: number, - target: Vector3, - ): Vector3 -} +export function findSpan(p: number, u: number, U: number[]): number +export function calcBasisFunctions(span: number, u: number, p: number, U: number[]): number[] +export function calcBSplinePoint(p: number, U: number[], P: Vector4[], u: number): Vector4 +export function calcBasisFunctionDerivatives(span: number, u: number, p: number, n: number, U: number[]): number[][] +export function calcBSplineDerivatives(p: number, U: number[], P: Vector4[], u: number, nd: number): Vector4[] +export function calcKoverI(k: number, i: number): number +export function calcRationalCurveDerivatives(Pders: Vector4[]): Vector3[] +export function calcNURBSDerivatives(p: number, U: number[], P: Vector4[], u: number, nd: number): Vector3[] +export function calcSurfacePoint( + p: number, + q: number, + U: number[], + V: number[], + P: Vector4[], + u: number, + v: number, + target: Vector3, +): Vector3 diff --git a/src/curves/NURBSUtils.js b/src/curves/NURBSUtils.js index 05fc9681..d81d10ce 100644 --- a/src/curves/NURBSUtils.js +++ b/src/curves/NURBSUtils.js @@ -11,15 +11,15 @@ import { Vector3, Vector4 } from 'three' **************************************************************/ /* - Finds knot vector span. +Finds knot vector span. - p : degree - u : parametric value - U : knot vector +p : degree +u : parametric value +U : knot vector - returns the span - */ -export function findSpan(p, u, U) { +returns the span +*/ +function findSpan(p, u, U) { const n = U.length - p - 1 if (u >= U[n]) { @@ -48,16 +48,16 @@ export function findSpan(p, u, U) { } /* - Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2 +Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2 - span : span in which u lies - u : parametric point - p : degree - U : knot vector +span : span in which u lies +u : parametric point +p : degree +U : knot vector - returns array[p+1] with basis functions values. - */ -export function calcBasisFunctions(span, u, p, U) { +returns array[p+1] with basis functions values. +*/ +function calcBasisFunctions(span, u, p, U) { const N = [] const left = [] const right = [] @@ -84,16 +84,16 @@ export function calcBasisFunctions(span, u, p, U) { } /* - Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1. +Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1. - p : degree of B-Spline - U : knot vector - P : control points (x, y, z, w) - u : parametric point +p : degree of B-Spline +U : knot vector +P : control points (x, y, z, w) +u : parametric point - returns point for given u - */ -export function calcBSplinePoint(p, U, P, u) { +returns point for given u +*/ +function calcBSplinePoint(p, U, P, u) { const span = findSpan(p, u, U) const N = calcBasisFunctions(span, u, p, U) const C = new Vector4(0, 0, 0, 0) @@ -112,24 +112,26 @@ export function calcBSplinePoint(p, U, P, u) { } /* - Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3. +Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3. - span : span in which u lies - u : parametric point - p : degree - n : number of derivatives to calculate - U : knot vector +span : span in which u lies +u : parametric point +p : degree +n : number of derivatives to calculate +U : knot vector - returns array[n+1][p+1] with basis functions derivatives - */ -export function calcBasisFunctionDerivatives(span, u, p, n, U) { +returns array[n+1][p+1] with basis functions derivatives +*/ +function calcBasisFunctionDerivatives(span, u, p, n, U) { const zeroArr = [] for (let i = 0; i <= p; ++i) zeroArr[i] = 0.0 const ders = [] + for (let i = 0; i <= n; ++i) ders[i] = zeroArr.slice(0) const ndu = [] + for (let i = 0; i <= p; ++i) ndu[i] = zeroArr.slice(0) ndu[0][0] = 1.0 @@ -196,13 +198,13 @@ export function calcBasisFunctionDerivatives(span, u, p, n, U) { ders[k][r] = d - var j = s1 + const j = s1 s1 = s2 s2 = j } } - var r = p + let r = p for (let k = 1; k <= n; ++k) { for (let j = 0; j <= p; ++j) { @@ -216,17 +218,17 @@ export function calcBasisFunctionDerivatives(span, u, p, n, U) { } /* - Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2. + Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2. - p : degree - U : knot vector - P : control points - u : Parametric points - nd : number of derivatives + p : degree + U : knot vector + P : control points + u : Parametric points + nd : number of derivatives - returns array[d+1] with derivatives - */ -export function calcBSplineDerivatives(p, U, P, u, nd) { + returns array[d+1] with derivatives + */ +function calcBSplineDerivatives(p, U, P, u, nd) { const du = nd < p ? nd : p const CK = [] const span = findSpan(p, u, U) @@ -234,7 +236,7 @@ export function calcBSplineDerivatives(p, U, P, u, nd) { const Pw = [] for (let i = 0; i < P.length; ++i) { - var point = P[i].clone() + const point = P[i].clone() const w = point.w point.x *= w @@ -245,7 +247,7 @@ export function calcBSplineDerivatives(p, U, P, u, nd) { } for (let k = 0; k <= du; ++k) { - var point = Pw[span - p].clone().multiplyScalar(nders[k][0]) + const point = Pw[span - p].clone().multiplyScalar(nders[k][0]) for (let j = 1; j <= p; ++j) { point.add(Pw[span - p + j].clone().multiplyScalar(nders[k][j])) @@ -262,11 +264,11 @@ export function calcBSplineDerivatives(p, U, P, u, nd) { } /* - Calculate "K over I" +Calculate "K over I" - returns k!/(i!(k-i)!) - */ -export function calcKoverI(k, i) { +returns k!/(i!(k-i)!) +*/ +function calcKoverI(k, i) { let nom = 1 for (let j = 2; j <= k; ++j) { @@ -287,13 +289,13 @@ export function calcKoverI(k, i) { } /* - Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2. +Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2. - Pders : result of function calcBSplineDerivatives +Pders : result of function calcBSplineDerivatives - returns array with derivatives for rational curve. - */ -export function calcRationalCurveDerivatives(Pders) { +returns array with derivatives for rational curve. +*/ +function calcRationalCurveDerivatives(Pders) { const nd = Pders.length const Aders = [] const wders = [] @@ -320,32 +322,32 @@ export function calcRationalCurveDerivatives(Pders) { } /* - Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2. +Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2. - p : degree - U : knot vector - P : control points in homogeneous space - u : parametric points - nd : number of derivatives +p : degree +U : knot vector +P : control points in homogeneous space +u : parametric points +nd : number of derivatives - returns array with derivatives. - */ -export function calcNURBSDerivatives(p, U, P, u, nd) { +returns array with derivatives. +*/ +function calcNURBSDerivatives(p, U, P, u, nd) { const Pders = calcBSplineDerivatives(p, U, P, u, nd) return calcRationalCurveDerivatives(Pders) } /* - Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3. +Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3. - p1, p2 : degrees of B-Spline surface - U1, U2 : knot vectors - P : control points (x, y, z, w) - u, v : parametric values +p1, p2 : degrees of B-Spline surface +U1, U2 : knot vectors +P : control points (x, y, z, w) +u, v : parametric values - returns point for given (u, v) - */ -export function calcSurfacePoint(p, q, U, V, P, u, v, target) { +returns point for given (u, v) +*/ +function calcSurfacePoint(p, q, U, V, P, u, v, target) { const uspan = findSpan(p, u, U) const vspan = findSpan(q, v, V) const Nu = calcBasisFunctions(uspan, u, p, U) @@ -372,3 +374,15 @@ export function calcSurfacePoint(p, q, U, V, P, u, v, target) { Sw.divideScalar(Sw.w) target.set(Sw.x, Sw.y, Sw.z) } + +export { + findSpan, + calcBasisFunctions, + calcBSplinePoint, + calcBasisFunctionDerivatives, + calcBSplineDerivatives, + calcKoverI, + calcRationalCurveDerivatives, + calcNURBSDerivatives, + calcSurfacePoint, +} diff --git a/src/custom.d.ts b/src/custom.d.ts deleted file mode 100644 index 1dcac09d..00000000 --- a/src/custom.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module 'mmd-parser' { - export class CharsetEncoder { - public s2uTable: { [key: string]: number } - } -} diff --git a/src/deprecated/Geometry.js b/src/deprecated/Geometry.js index 9f6a8a4b..32148cd1 100644 --- a/src/deprecated/Geometry.js +++ b/src/deprecated/Geometry.js @@ -14,11 +14,43 @@ import { Vector3, } from 'three' -const _m1 = new Matrix4() -const _obj = new Object3D() -const _offset = new Vector3() +const _m1 = /* @__PURE__ */ new Matrix4() +const _obj = /* @__PURE__ */ new Object3D() +const _offset = /* @__PURE__ */ new Vector3() class Geometry extends EventDispatcher { + static createBufferGeometryFromObject(object) { + let buffergeometry = new BufferGeometry() + + const geometry = object.geometry + + if (object.isPoints || object.isLine) { + const positions = new Float32BufferAttribute(geometry.vertices.length * 3, 3) + const colors = new Float32BufferAttribute(geometry.colors.length * 3, 3) + + buffergeometry.setAttribute('position', positions.copyVector3sArray(geometry.vertices)) + buffergeometry.setAttribute('color', colors.copyColorsArray(geometry.colors)) + + if (geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length) { + const lineDistances = new Float32BufferAttribute(geometry.lineDistances.length, 1) + + buffergeometry.setAttribute('lineDistance', lineDistances.copyArray(geometry.lineDistances)) + } + + if (geometry.boundingSphere !== null) { + buffergeometry.boundingSphere = geometry.boundingSphere.clone() + } + + if (geometry.boundingBox !== null) { + buffergeometry.boundingBox = geometry.boundingBox.clone() + } + } else if (object.isMesh) { + buffergeometry = geometry.toBufferGeometry() + } + + return buffergeometry + } + constructor() { super() this.isGeometry = true @@ -1171,38 +1203,6 @@ class Geometry extends EventDispatcher { } } -Geometry.createBufferGeometryFromObject = (object) => { - let buffergeometry = new BufferGeometry() - - const geometry = object.geometry - - if (object.isPoints || object.isLine) { - const positions = new Float32BufferAttribute(geometry.vertices.length * 3, 3) - const colors = new Float32BufferAttribute(geometry.colors.length * 3, 3) - - buffergeometry.setAttribute('position', positions.copyVector3sArray(geometry.vertices)) - buffergeometry.setAttribute('color', colors.copyColorsArray(geometry.colors)) - - if (geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length) { - const lineDistances = new Float32BufferAttribute(geometry.lineDistances.length, 1) - - buffergeometry.setAttribute('lineDistance', lineDistances.copyArray(geometry.lineDistances)) - } - - if (geometry.boundingSphere !== null) { - buffergeometry.boundingSphere = geometry.boundingSphere.clone() - } - - if (geometry.boundingBox !== null) { - buffergeometry.boundingBox = geometry.boundingBox.clone() - } - } else if (object.isMesh) { - buffergeometry = geometry.toBufferGeometry() - } - - return buffergeometry -} - class DirectGeometry { constructor() { this.vertices = [] diff --git a/src/effects/AnaglyphEffect.js b/src/effects/AnaglyphEffect.js index 5674befa..097bcfc7 100644 --- a/src/effects/AnaglyphEffect.js +++ b/src/effects/AnaglyphEffect.js @@ -12,154 +12,136 @@ import { WebGLRenderTarget, } from 'three' -const AnaglyphEffect = function (renderer, width, height) { - // Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4 - - this.colorMatrixLeft = new Matrix3().fromArray([ - 0.4561, - -0.0400822, - -0.0152161, - 0.500484, - -0.0378246, - -0.0205971, - 0.176381, - -0.0157589, - -0.00546856, - ]) - - this.colorMatrixRight = new Matrix3().fromArray([ - -0.0434706, - 0.378476, - -0.0721527, - -0.0879388, - 0.73364, - -0.112961, - -0.00155529, - -0.0184503, - 1.2264, - ]) - - const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1) - - const _scene = new Scene() - - const _stereo = new StereoCamera() - - const _params = { - minFilter: LinearFilter, - magFilter: NearestFilter, - format: RGBAFormat, - } +class AnaglyphEffect { + constructor(renderer, width = 512, height = 512) { + // Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4 - if (width === undefined) width = 512 - if (height === undefined) height = 512 + this.colorMatrixLeft = new Matrix3().fromArray([ + 0.4561, + -0.0400822, + -0.0152161, + 0.500484, + -0.0378246, + -0.0205971, + 0.176381, + -0.0157589, + -0.00546856, + ]) - const _renderTargetL = new WebGLRenderTarget(width, height, _params) - const _renderTargetR = new WebGLRenderTarget(width, height, _params) + this.colorMatrixRight = new Matrix3().fromArray([ + -0.0434706, + 0.378476, + -0.0721527, + -0.0879388, + 0.73364, + -0.112961, + -0.00155529, + -0.0184503, + 1.2264, + ]) - const _material = new ShaderMaterial({ - uniforms: { - mapLeft: { value: _renderTargetL.texture }, - mapRight: { value: _renderTargetR.texture }, + const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1) - colorMatrixLeft: { value: this.colorMatrixLeft }, - colorMatrixRight: { value: this.colorMatrixRight }, - }, + const _scene = new Scene() - vertexShader: [ - 'varying vec2 vUv;', + const _stereo = new StereoCamera() - 'void main() {', + const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat } - ' vUv = vec2( uv.x, uv.y );', - ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + const _renderTargetL = new WebGLRenderTarget(width, height, _params) + const _renderTargetR = new WebGLRenderTarget(width, height, _params) - '}', - ].join('\n'), + const _material = new ShaderMaterial({ + uniforms: { + mapLeft: { value: _renderTargetL.texture }, + mapRight: { value: _renderTargetR.texture }, - fragmentShader: [ - 'uniform sampler2D mapLeft;', - 'uniform sampler2D mapRight;', - 'varying vec2 vUv;', + colorMatrixLeft: { value: this.colorMatrixLeft }, + colorMatrixRight: { value: this.colorMatrixRight }, + }, - 'uniform mat3 colorMatrixLeft;', - 'uniform mat3 colorMatrixRight;', + vertexShader: [ + 'varying vec2 vUv;', - // These functions implement sRGB linearization and gamma correction + 'void main() {', - 'float lin( float c ) {', - ' return c <= 0.04045 ? c * 0.0773993808 :', - ' pow( c * 0.9478672986 + 0.0521327014, 2.4 );', - '}', + ' vUv = vec2( uv.x, uv.y );', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', - 'vec4 lin( vec4 c ) {', - ' return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );', - '}', + '}', + ].join('\n'), - 'float dev( float c ) {', - ' return c <= 0.0031308 ? c * 12.92', - ' : pow( c, 0.41666 ) * 1.055 - 0.055;', - '}', + fragmentShader: [ + 'uniform sampler2D mapLeft;', + 'uniform sampler2D mapRight;', + 'varying vec2 vUv;', - 'void main() {', + 'uniform mat3 colorMatrixLeft;', + 'uniform mat3 colorMatrixRight;', - ' vec2 uv = vUv;', + 'void main() {', - ' vec4 colorL = lin( texture2D( mapLeft, uv ) );', - ' vec4 colorR = lin( texture2D( mapRight, uv ) );', + ' vec2 uv = vUv;', - ' vec3 color = clamp(', - ' colorMatrixLeft * colorL.rgb +', - ' colorMatrixRight * colorR.rgb, 0., 1. );', + ' vec4 colorL = texture2D( mapLeft, uv );', + ' vec4 colorR = texture2D( mapRight, uv );', - ' gl_FragColor = vec4(', - ' dev( color.r ), dev( color.g ), dev( color.b ),', - ' max( colorL.a, colorR.a ) );', + ' vec3 color = clamp(', + ' colorMatrixLeft * colorL.rgb +', + ' colorMatrixRight * colorR.rgb, 0., 1. );', - '}', - ].join('\n'), - }) + ' gl_FragColor = vec4(', + ' color.r, color.g, color.b,', + ' max( colorL.a, colorR.a ) );', - const _mesh = new Mesh(new PlaneGeometry(2, 2), _material) - _scene.add(_mesh) + ' #include ', + ' #include ', - this.setSize = (width, height) => { - renderer.setSize(width, height) + '}', + ].join('\n'), + }) - const pixelRatio = renderer.getPixelRatio() + const _mesh = new Mesh(new PlaneGeometry(2, 2), _material) + _scene.add(_mesh) - _renderTargetL.setSize(width * pixelRatio, height * pixelRatio) - _renderTargetR.setSize(width * pixelRatio, height * pixelRatio) - } + this.setSize = function (width, height) { + renderer.setSize(width, height) - this.render = (scene, camera) => { - const currentRenderTarget = renderer.getRenderTarget() + const pixelRatio = renderer.getPixelRatio() - scene.updateMatrixWorld() + _renderTargetL.setSize(width * pixelRatio, height * pixelRatio) + _renderTargetR.setSize(width * pixelRatio, height * pixelRatio) + } - if (camera.parent === null) camera.updateMatrixWorld() + this.render = function (scene, camera) { + const currentRenderTarget = renderer.getRenderTarget() - _stereo.update(camera) + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld() - renderer.setRenderTarget(_renderTargetL) - renderer.clear() - renderer.render(scene, _stereo.cameraL) + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld() - renderer.setRenderTarget(_renderTargetR) - renderer.clear() - renderer.render(scene, _stereo.cameraR) + _stereo.update(camera) - renderer.setRenderTarget(null) - renderer.render(_scene, _camera) + renderer.setRenderTarget(_renderTargetL) + renderer.clear() + renderer.render(scene, _stereo.cameraL) - renderer.setRenderTarget(currentRenderTarget) - } + renderer.setRenderTarget(_renderTargetR) + renderer.clear() + renderer.render(scene, _stereo.cameraR) + + renderer.setRenderTarget(null) + renderer.render(_scene, _camera) + + renderer.setRenderTarget(currentRenderTarget) + } - this.dispose = () => { - if (_renderTargetL) _renderTargetL.dispose() - if (_renderTargetR) _renderTargetR.dispose() - if (_mesh) _mesh.geometry.dispose() - if (_material) _material.dispose() + this.dispose = function () { + _renderTargetL.dispose() + _renderTargetR.dispose() + _mesh.geometry.dispose() + _mesh.material.dispose() + } } } diff --git a/src/effects/AsciiEffect.js b/src/effects/AsciiEffect.js index 7ee91560..b5ef5636 100644 --- a/src/effects/AsciiEffect.js +++ b/src/effects/AsciiEffect.js @@ -1,266 +1,244 @@ /** - * Ascii generation is based on http://www.nihilogic.dk/labs/jsascii/ - * Maybe more about this later with a blog post at http://lab4games.net/zz85/blog + * Ascii generation is based on https://github.com/hassadee/jsascii/blob/master/jsascii.js * * 16 April 2012 - @blurspline */ -const AsciiEffect = function (renderer, charSet, options) { - // its fun to create one your own! +class AsciiEffect { + constructor(renderer, charSet = ' .:-=+*#%@', options = {}) { + // ' .,:;=|iI+hHOE#`$'; + // darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/ + // ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split(''); - charSet = charSet === undefined ? ' .:-=+*#%@' : charSet + // Some ASCII settings - // ' .,:;=|iI+hHOE#`$'; - // darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/ - // ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split(''); + const fResolution = options['resolution'] || 0.15 // Higher for more details + const iScale = options['scale'] || 1 + const bColor = options['color'] || false // nice but slows down rendering! + const bAlpha = options['alpha'] || false // Transparency + const bBlock = options['block'] || false // blocked characters. like good O dos + const bInvert = options['invert'] || false // black is white, white is black + const strResolution = options['strResolution'] || 'low' - if (!options) options = {} + let width, height - // Some ASCII settings + const domElement = document.createElement('div') + domElement.style.cursor = 'default' - const bResolution = !options['resolution'] ? 0.15 : options['resolution'] // Higher for more details - const iScale = !options['scale'] ? 1 : options['scale'] - const bColor = !options['color'] ? false : options['color'] // nice but slows down rendering! - const bAlpha = !options['alpha'] ? false : options['alpha'] // Transparency - const bBlock = !options['block'] ? false : options['block'] // blocked characters. like good O dos - const bInvert = !options['invert'] ? false : options['invert'] // black is white, white is black + const oAscii = document.createElement('table') + domElement.appendChild(oAscii) - const strResolution = 'low' + let iWidth, iHeight + let oImg - let width, height + this.setSize = function (w, h) { + width = w + height = h - const domElement = document.createElement('div') - domElement.style.cursor = 'default' + renderer.setSize(w, h) - const oAscii = document.createElement('table') - domElement.appendChild(oAscii) - - let iWidth, iHeight - let oImg - - this.setSize = (w, h) => { - width = w - height = h - - renderer.setSize(w, h) - - initAsciiSize() - } + initAsciiSize() + } - this.render = (scene, camera) => { - renderer.render(scene, camera) - asciifyImage(renderer, oAscii) - } + this.render = function (scene, camera) { + renderer.render(scene, camera) + asciifyImage(oAscii) + } - this.domElement = domElement + this.domElement = domElement - // Throw in ascii library from http://www.nihilogic.dk/labs/jsascii/jsascii.js + // Throw in ascii library from https://github.com/hassadee/jsascii/blob/master/jsascii.js (MIT License) - /* - * jsAscii 0.1 - * Copyright (c) 2008 Jacob Seidelin, jseidelin@nihilogic.dk, http://blog.nihilogic.dk/ - * MIT License [http://www.nihilogic.dk/licenses/mit-license.txt] - */ + function initAsciiSize() { + iWidth = Math.floor(width * fResolution) + iHeight = Math.floor(height * fResolution) - function initAsciiSize() { - iWidth = Math.round(width * fResolution) - iHeight = Math.round(height * fResolution) + oCanvas.width = iWidth + oCanvas.height = iHeight + // oCanvas.style.display = "none"; + // oCanvas.style.width = iWidth; + // oCanvas.style.height = iHeight; - oCanvas.width = iWidth - oCanvas.height = iHeight - // oCanvas.style.display = "none"; - // oCanvas.style.width = iWidth; - // oCanvas.style.height = iHeight; + oImg = renderer.domElement - oImg = renderer.domElement + if (oImg.style.backgroundColor) { + oAscii.rows[0].cells[0].style.backgroundColor = oImg.style.backgroundColor + oAscii.rows[0].cells[0].style.color = oImg.style.color + } - if (oImg.style.backgroundColor) { - oAscii.rows[0].cells[0].style.backgroundColor = oImg.style.backgroundColor - oAscii.rows[0].cells[0].style.color = oImg.style.color + oAscii.cellSpacing = 0 + oAscii.cellPadding = 0 + + const oStyle = oAscii.style + oStyle.whiteSpace = 'pre' + oStyle.margin = '0px' + oStyle.padding = '0px' + oStyle.letterSpacing = fLetterSpacing + 'px' + oStyle.fontFamily = strFont + oStyle.fontSize = fFontSize + 'px' + oStyle.lineHeight = fLineHeight + 'px' + oStyle.textAlign = 'left' + oStyle.textDecoration = 'none' } - oAscii.cellSpacing = 0 - oAscii.cellPadding = 0 - - const oStyle = oAscii.style - oStyle.display = 'inline' - oStyle.width = `${Math.round((iWidth / fResolution) * iScale)}px` - oStyle.height = `${Math.round((iHeight / fResolution) * iScale)}px` - oStyle.whiteSpace = 'pre' - oStyle.margin = '0px' - oStyle.padding = '0px' - oStyle.letterSpacing = `${fLetterSpacing}px` - oStyle.fontFamily = strFont - oStyle.fontSize = `${fFontSize}px` - oStyle.lineHeight = `${fLineHeight}px` - oStyle.textAlign = 'left' - oStyle.textDecoration = 'none' - } - - const aDefaultCharList = ' .,:;i1tfLCG08@'.split('') - const aDefaultColorCharList = ' CGO08@'.split('') - var strFont = 'courier new, monospace' + const aDefaultCharList = ' .,:;i1tfLCG08@'.split('') + const aDefaultColorCharList = ' CGO08@'.split('') + const strFont = 'courier new, monospace' - const oCanvasImg = renderer.domElement + const oCanvasImg = renderer.domElement - var oCanvas = document.createElement('canvas') - if (!oCanvas.getContext) { - return - } - - const oCtx = oCanvas.getContext('2d') - if (!oCtx.getImageData) { - return - } - - let aCharList = bColor ? aDefaultColorCharList : aDefaultCharList + const oCanvas = document.createElement('canvas') + if (!oCanvas.getContext) { + return + } - if (charSet) aCharList = charSet + const oCtx = oCanvas.getContext('2d') + if (!oCtx.getImageData) { + return + } - var fResolution = 0.5 + let aCharList = bColor ? aDefaultColorCharList : aDefaultCharList - switch (strResolution) { - case 'low': - fResolution = 0.25 - break - case 'medium': - fResolution = 0.5 - break - case 'high': - fResolution = 1 - break - } + if (charSet) aCharList = charSet - if (bResolution) fResolution = bResolution + // Setup dom - // Setup dom + const fFontSize = (2 / fResolution) * iScale + const fLineHeight = (2 / fResolution) * iScale - var fFontSize = (2 / fResolution) * iScale - var fLineHeight = (2 / fResolution) * iScale + // adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width. - // adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width. + let fLetterSpacing = 0 - var fLetterSpacing = 0 - - if (strResolution == 'low') { - switch (iScale) { - case 1: - fLetterSpacing = -1 - break - case 2: - case 3: - fLetterSpacing = -2.1 - break - case 4: - fLetterSpacing = -3.1 - break - case 5: - fLetterSpacing = -4.15 - break + if (strResolution == 'low') { + switch (iScale) { + case 1: + fLetterSpacing = -1 + break + case 2: + case 3: + fLetterSpacing = -2.1 + break + case 4: + fLetterSpacing = -3.1 + break + case 5: + fLetterSpacing = -4.15 + break + } } - } - if (strResolution == 'medium') { - switch (iScale) { - case 1: - fLetterSpacing = 0 - break - case 2: - fLetterSpacing = -1 - break - case 3: - fLetterSpacing = -1.04 - break - case 4: - case 5: - fLetterSpacing = -2.1 - break + if (strResolution == 'medium') { + switch (iScale) { + case 1: + fLetterSpacing = 0 + break + case 2: + fLetterSpacing = -1 + break + case 3: + fLetterSpacing = -1.04 + break + case 4: + case 5: + fLetterSpacing = -2.1 + break + } } - } - if (strResolution == 'high') { - switch (iScale) { - case 1: - case 2: - fLetterSpacing = 0 - break - case 3: - case 4: - case 5: - fLetterSpacing = -1 - break + if (strResolution == 'high') { + switch (iScale) { + case 1: + case 2: + fLetterSpacing = 0 + break + case 3: + case 4: + case 5: + fLetterSpacing = -1 + break + } } - } - - // can't get a span or div to flow like an img element, but a table works? - - // convert img element to ascii - - function asciifyImage(canvasRenderer, oAscii) { - oCtx.clearRect(0, 0, iWidth, iHeight) - oCtx.drawImage(oCanvasImg, 0, 0, iWidth, iHeight) - const oImgData = oCtx.getImageData(0, 0, iWidth, iHeight).data - - // Coloring loop starts now - let strChars = '' - - // console.time('rendering'); - - for (let y = 0; y < iHeight; y += 2) { - for (let x = 0; x < iWidth; x++) { - const iOffset = (y * iWidth + x) * 4 - - const iRed = oImgData[iOffset] - const iGreen = oImgData[iOffset + 1] - const iBlue = oImgData[iOffset + 2] - const iAlpha = oImgData[iOffset + 3] - let iCharIdx - - let fBrightness - - fBrightness = (0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue) / 255 - // fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255; - if (iAlpha == 0) { - // should calculate alpha instead, but quick hack :) - //fBrightness *= (iAlpha / 255); - fBrightness = 1 + // can't get a span or div to flow like an img element, but a table works? + + // convert img element to ascii + + function asciifyImage(oAscii) { + oCtx.clearRect(0, 0, iWidth, iHeight) + oCtx.drawImage(oCanvasImg, 0, 0, iWidth, iHeight) + const oImgData = oCtx.getImageData(0, 0, iWidth, iHeight).data + + // Coloring loop starts now + let strChars = '' + + // console.time('rendering'); + + for (let y = 0; y < iHeight; y += 2) { + for (let x = 0; x < iWidth; x++) { + const iOffset = (y * iWidth + x) * 4 + + const iRed = oImgData[iOffset] + const iGreen = oImgData[iOffset + 1] + const iBlue = oImgData[iOffset + 2] + const iAlpha = oImgData[iOffset + 3] + let iCharIdx + + let fBrightness + + fBrightness = (0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue) / 255 + // fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255; + + if (iAlpha == 0) { + // should calculate alpha instead, but quick hack :) + //fBrightness *= (iAlpha / 255); + fBrightness = 1 + } + + iCharIdx = Math.floor((1 - fBrightness) * (aCharList.length - 1)) + + if (bInvert) { + iCharIdx = aCharList.length - iCharIdx - 1 + } + + // good for debugging + //fBrightness = Math.floor(fBrightness * 10); + //strThisChar = fBrightness; + + let strThisChar = aCharList[iCharIdx] + + if (strThisChar === undefined || strThisChar == ' ') strThisChar = ' ' + + if (bColor) { + strChars += + "" + + strThisChar + + '' + } else { + strChars += strThisChar + } } - iCharIdx = Math.floor((1 - fBrightness) * (aCharList.length - 1)) - - if (bInvert) { - iCharIdx = aCharList.length - iCharIdx - 1 - } - - // good for debugging - //fBrightness = Math.floor(fBrightness * 10); - //strThisChar = fBrightness; - - let strThisChar = aCharList[iCharIdx] - - if (strThisChar === undefined || strThisChar == ' ') strThisChar = ' ' - - if (bColor) { - strChars += `${strThisChar}` - } else { - strChars += strThisChar - } + strChars += '
' } - strChars += '
' - } - - oAscii.innerHTML = `${strChars}` + oAscii.innerHTML = `${strChars}` - // console.timeEnd('rendering'); + // console.timeEnd('rendering'); - // return oAscii; + // return oAscii; + } } - - // end modified asciifyImage block } export { AsciiEffect } diff --git a/src/effects/OutlineEffect.js b/src/effects/OutlineEffect.js index 316e20ec..ee4222ee 100644 --- a/src/effects/OutlineEffect.js +++ b/src/effects/OutlineEffect.js @@ -7,7 +7,7 @@ import { BackSide, Color, ShaderMaterial, UniformsLib, UniformsUtils } from 'thr * * 1. Traditional * - * var effect = new OutlineEffect( renderer ); + * const effect = new OutlineEffect( renderer ); * * function render() { * @@ -17,8 +17,8 @@ import { BackSide, Color, ShaderMaterial, UniformsLib, UniformsUtils } from 'thr * * 2. VR compatible * - * var effect = new OutlineEffect( renderer ); - * var renderingOutline = false; + * const effect = new OutlineEffect( renderer ); + * let renderingOutline = false; * * scene.onAfterRender = function () { * @@ -49,422 +49,395 @@ import { BackSide, Color, ShaderMaterial, UniformsLib, UniformsUtils } from 'thr * // How to set outline parameters for each material * material.userData.outlineParameters = { * thickness: 0.01, - * color: [ 0, 0, 0 ] + * color: [ 0, 0, 0 ], * alpha: 0.8, * visible: true, * keepAlive: true * }; */ -const OutlineEffect = function (renderer, parameters) { - parameters = parameters || {} - - this.enabled = true - - const defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003 - const defaultColor = new Color().fromArray( - parameters.defaultColor !== undefined ? parameters.defaultColor : [0, 0, 0], - ) - const defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0 - const defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false - - // object.material.uuid -> outlineMaterial or - // object.material[ n ].uuid -> outlineMaterial - // save at the outline material creation and release - // if it's unused removeThresholdCount frames - // unless keepAlive is true. - const cache = {} - - const removeThresholdCount = 60 - - // outlineMaterial.uuid -> object.material or - // outlineMaterial.uuid -> object.material[ n ] - // save before render and release after render. - const originalMaterials = {} - - // object.uuid -> originalOnBeforeRender - // save before render and release after render. - const originalOnBeforeRenders = {} - - //this.cache = cache; // for debug +class OutlineEffect { + constructor(renderer, parameters = {}) { + this.enabled = true + + const defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003 + const defaultColor = new Color().fromArray( + parameters.defaultColor !== undefined ? parameters.defaultColor : [0, 0, 0], + ) + const defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0 + const defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false + + // object.material.uuid -> outlineMaterial or + // object.material[ n ].uuid -> outlineMaterial + // save at the outline material creation and release + // if it's unused removeThresholdCount frames + // unless keepAlive is true. + const cache = {} + + const removeThresholdCount = 60 + + // outlineMaterial.uuid -> object.material or + // outlineMaterial.uuid -> object.material[ n ] + // save before render and release after render. + const originalMaterials = {} + + // object.uuid -> originalOnBeforeRender + // save before render and release after render. + const originalOnBeforeRenders = {} + + //this.cache = cache; // for debug + + const uniformsOutline = { + outlineThickness: { value: defaultThickness }, + outlineColor: { value: defaultColor }, + outlineAlpha: { value: defaultAlpha }, + } - const uniformsOutline = { - outlineThickness: { value: defaultThickness }, - outlineColor: { value: defaultColor }, - outlineAlpha: { value: defaultAlpha }, - } + const vertexShader = [ + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + '#include ', + + 'uniform float outlineThickness;', + + 'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {', + ' float thickness = outlineThickness;', + ' const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex + ' vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );', + // NOTE: subtract pos2 from pos because BackSide objectNormal is negative + ' vec4 norm = normalize( pos - pos2 );', + ' return pos + norm * thickness * pos.w * ratio;', + '}', + + 'void main() {', + + ' #include ', + + ' #include ', + ' #include ', + ' #include ', + ' #include ', + + ' #include ', + ' #include ', + ' #include ', + ' #include ', + ' #include ', + + ' vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide + + ' gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );', + + ' #include ', + ' #include ', + ' #include ', + + '}', + ].join('\n') + + const fragmentShader = [ + '#include ', + '#include ', + '#include ', + '#include ', + + 'uniform vec3 outlineColor;', + 'uniform float outlineAlpha;', + + 'void main() {', + + ' #include ', + ' #include ', + + ' gl_FragColor = vec4( outlineColor, outlineAlpha );', + + ' #include ', + ' #include ', + ' #include ', + ' #include ', + + '}', + ].join('\n') + + function createMaterial() { + return new ShaderMaterial({ + type: 'OutlineEffect', + uniforms: UniformsUtils.merge([UniformsLib['fog'], UniformsLib['displacementmap'], uniformsOutline]), + vertexShader: vertexShader, + fragmentShader: fragmentShader, + side: BackSide, + }) + } - const vertexShader = [ - '#include ', - '#include ', - '#include ', - '#include ', - '#include ', - '#include ', - '#include ', - '#include ', - - 'uniform float outlineThickness;', - - 'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {', - ' float thickness = outlineThickness;', - ' const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex - ' vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );', - // NOTE: subtract pos2 from pos because BackSide objectNormal is negative - ' vec4 norm = normalize( pos - pos2 );', - ' return pos + norm * thickness * pos.w * ratio;', - '}', - - 'void main() {', - - ' #include ', - - ' #include ', - ' #include ', - ' #include ', - ' #include ', - - ' #include ', - ' #include ', - ' #include ', - ' #include ', - ' #include ', - - ' vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide - - ' gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );', - - ' #include ', - ' #include ', - ' #include ', - - '}', - ].join('\n') - - const fragmentShader = [ - '#include ', - '#include ', - '#include ', - '#include ', - - 'uniform vec3 outlineColor;', - 'uniform float outlineAlpha;', - - 'void main() {', - - ' #include ', - ' #include ', - - ' gl_FragColor = vec4( outlineColor, outlineAlpha );', - - ' #include ', - ' #include ', - ' #include ', - ' #include ', - - '}', - ].join('\n') - - function createMaterial() { - return new ShaderMaterial({ - type: 'OutlineEffect', - uniforms: UniformsUtils.merge([UniformsLib['fog'], UniformsLib['displacementmap'], uniformsOutline]), - vertexShader, - fragmentShader, - side: BackSide, - }) - } + function getOutlineMaterialFromCache(originalMaterial) { + let data = cache[originalMaterial.uuid] - function getOutlineMaterialFromCache(originalMaterial) { - let data = cache[originalMaterial.uuid] + if (data === undefined) { + data = { + material: createMaterial(), + used: true, + keepAlive: defaultKeepAlive, + count: 0, + } - if (data === undefined) { - data = { - material: createMaterial(), - used: true, - keepAlive: defaultKeepAlive, - count: 0, + cache[originalMaterial.uuid] = data } - cache[originalMaterial.uuid] = data + data.used = true + + return data.material } - data.used = true + function getOutlineMaterial(originalMaterial) { + const outlineMaterial = getOutlineMaterialFromCache(originalMaterial) - return data.material - } + originalMaterials[outlineMaterial.uuid] = originalMaterial - function getOutlineMaterial(originalMaterial) { - const outlineMaterial = getOutlineMaterialFromCache(originalMaterial) + updateOutlineMaterial(outlineMaterial, originalMaterial) - originalMaterials[outlineMaterial.uuid] = originalMaterial + return outlineMaterial + } - updateOutlineMaterial(outlineMaterial, originalMaterial) + function isCompatible(object) { + const geometry = object.geometry + const hasNormals = geometry !== undefined && geometry.attributes.normal !== undefined - return outlineMaterial - } + return object.isMesh === true && object.material !== undefined && hasNormals === true + } - function isCompatible(object) { - const geometry = object.geometry - let hasNormals = false + function setOutlineMaterial(object) { + if (isCompatible(object) === false) return - if (object.geometry !== undefined) { - if (geometry.isBufferGeometry) { - hasNormals = geometry.attributes.normal !== undefined + if (Array.isArray(object.material)) { + for (let i = 0, il = object.material.length; i < il; i++) { + object.material[i] = getOutlineMaterial(object.material[i]) + } } else { - hasNormals = true // the renderer always produces a normal attribute for Geometry + object.material = getOutlineMaterial(object.material) } - } - return object.isMesh === true && object.material !== undefined && hasNormals === true - } + originalOnBeforeRenders[object.uuid] = object.onBeforeRender + object.onBeforeRender = onBeforeRender + } - function setOutlineMaterial(object) { - if (isCompatible(object) === false) return + function restoreOriginalMaterial(object) { + if (isCompatible(object) === false) return - if (Array.isArray(object.material)) { - for (let i = 0, il = object.material.length; i < il; i++) { - object.material[i] = getOutlineMaterial(object.material[i]) + if (Array.isArray(object.material)) { + for (let i = 0, il = object.material.length; i < il; i++) { + object.material[i] = originalMaterials[object.material[i].uuid] + } + } else { + object.material = originalMaterials[object.material.uuid] } - } else { - object.material = getOutlineMaterial(object.material) + + object.onBeforeRender = originalOnBeforeRenders[object.uuid] } - originalOnBeforeRenders[object.uuid] = object.onBeforeRender - object.onBeforeRender = onBeforeRender - } + function onBeforeRender(renderer, scene, camera, geometry, material) { + const originalMaterial = originalMaterials[material.uuid] - function restoreOriginalMaterial(object) { - if (isCompatible(object) === false) return + // just in case + if (originalMaterial === undefined) return - if (Array.isArray(object.material)) { - for (let i = 0, il = object.material.length; i < il; i++) { - object.material[i] = originalMaterials[object.material[i].uuid] - } - } else { - object.material = originalMaterials[object.material.uuid] + updateUniforms(material, originalMaterial) } - object.onBeforeRender = originalOnBeforeRenders[object.uuid] - } - - function onBeforeRender(renderer, scene, camera, geometry, material) { - const originalMaterial = originalMaterials[material.uuid] + function updateUniforms(material, originalMaterial) { + const outlineParameters = originalMaterial.userData.outlineParameters - // just in case - if (originalMaterial === undefined) return - - updateUniforms(material, originalMaterial) - } + material.uniforms.outlineAlpha.value = originalMaterial.opacity - function updateUniforms(material, originalMaterial) { - const outlineParameters = originalMaterial.userData.outlineParameters - - material.uniforms.outlineAlpha.value = originalMaterial.opacity + if (outlineParameters !== undefined) { + if (outlineParameters.thickness !== undefined) + material.uniforms.outlineThickness.value = outlineParameters.thickness + if (outlineParameters.color !== undefined) + material.uniforms.outlineColor.value.fromArray(outlineParameters.color) + if (outlineParameters.alpha !== undefined) material.uniforms.outlineAlpha.value = outlineParameters.alpha + } - if (outlineParameters !== undefined) { - if (outlineParameters.thickness !== undefined) { - material.uniforms.outlineThickness.value = outlineParameters.thickness + if (originalMaterial.displacementMap) { + material.uniforms.displacementMap.value = originalMaterial.displacementMap + material.uniforms.displacementScale.value = originalMaterial.displacementScale + material.uniforms.displacementBias.value = originalMaterial.displacementBias } - if (outlineParameters.color !== undefined) material.uniforms.outlineColor.value.fromArray(outlineParameters.color) - if (outlineParameters.alpha !== undefined) material.uniforms.outlineAlpha.value = outlineParameters.alpha } - if (originalMaterial.displacementMap) { - material.uniforms.displacementMap.value = originalMaterial.displacementMap - material.uniforms.displacementScale.value = originalMaterial.displacementScale - material.uniforms.displacementBias.value = originalMaterial.displacementBias - } - } + function updateOutlineMaterial(material, originalMaterial) { + if (material.name === 'invisible') return - function updateOutlineMaterial(material, originalMaterial) { - if (material.name === 'invisible') return + const outlineParameters = originalMaterial.userData.outlineParameters - const outlineParameters = originalMaterial.userData.outlineParameters + material.fog = originalMaterial.fog + material.toneMapped = originalMaterial.toneMapped + material.premultipliedAlpha = originalMaterial.premultipliedAlpha + material.displacementMap = originalMaterial.displacementMap - material.skinning = originalMaterial.skinning - material.morphTargets = originalMaterial.morphTargets - material.morphNormals = originalMaterial.morphNormals - material.fog = originalMaterial.fog - material.toneMapped = originalMaterial.toneMapped - material.premultipliedAlpha = originalMaterial.premultipliedAlpha - material.displacementMap = originalMaterial.displacementMap + if (outlineParameters !== undefined) { + if (originalMaterial.visible === false) { + material.visible = false + } else { + material.visible = outlineParameters.visible !== undefined ? outlineParameters.visible : true + } - if (outlineParameters !== undefined) { - if (originalMaterial.visible === false) { - material.visible = false + material.transparent = + outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ? true : originalMaterial.transparent + + if (outlineParameters.keepAlive !== undefined) + cache[originalMaterial.uuid].keepAlive = outlineParameters.keepAlive } else { - material.visible = outlineParameters.visible !== undefined ? outlineParameters.visible : true + material.transparent = originalMaterial.transparent + material.visible = originalMaterial.visible } - material.transparent = - outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ? true : originalMaterial.transparent - - if (outlineParameters.keepAlive !== undefined) { - cache[originalMaterial.uuid].keepAlive = outlineParameters.keepAlive - } - } else { - material.transparent = originalMaterial.transparent - material.visible = originalMaterial.visible - } + if (originalMaterial.wireframe === true || originalMaterial.depthTest === false) material.visible = false - if (originalMaterial.wireframe === true || originalMaterial.depthTest === false) material.visible = false + if (originalMaterial.clippingPlanes) { + material.clipping = true - if (originalMaterial.clippingPlanes) { - material.clipping = true + material.clippingPlanes = originalMaterial.clippingPlanes + material.clipIntersection = originalMaterial.clipIntersection + material.clipShadows = originalMaterial.clipShadows + } - material.clippingPlanes = originalMaterial.clippingPlanes - material.clipIntersection = originalMaterial.clipIntersection - material.clipShadows = originalMaterial.clipShadows + material.version = originalMaterial.version // update outline material if necessary } - material.version = originalMaterial.version // update outline material if necessary - } - - function cleanupCache() { - let keys + function cleanupCache() { + let keys - // clear originialMaterials - keys = Object.keys(originalMaterials) + // clear originialMaterials + keys = Object.keys(originalMaterials) - for (let i = 0, il = keys.length; i < il; i++) { - originalMaterials[keys[i]] = undefined - } + for (let i = 0, il = keys.length; i < il; i++) { + originalMaterials[keys[i]] = undefined + } - // clear originalOnBeforeRenders - keys = Object.keys(originalOnBeforeRenders) + // clear originalOnBeforeRenders + keys = Object.keys(originalOnBeforeRenders) - for (let i = 0, il = keys.length; i < il; i++) { - originalOnBeforeRenders[keys[i]] = undefined - } + for (let i = 0, il = keys.length; i < il; i++) { + originalOnBeforeRenders[keys[i]] = undefined + } - // remove unused outlineMaterial from cache - keys = Object.keys(cache) + // remove unused outlineMaterial from cache + keys = Object.keys(cache) - for (let i = 0, il = keys.length; i < il; i++) { - const key = keys[i] + for (let i = 0, il = keys.length; i < il; i++) { + const key = keys[i] - if (cache[key].used === false) { - cache[key].count++ + if (cache[key].used === false) { + cache[key].count++ - if (cache[key].keepAlive === false && cache[key].count > removeThresholdCount) { - delete cache[key] + if (cache[key].keepAlive === false && cache[key].count > removeThresholdCount) { + delete cache[key] + } + } else { + cache[key].used = false + cache[key].count = 0 } - } else { - cache[key].used = false - cache[key].count = 0 } } - } - - this.render = function (scene, camera) { - let renderTarget - let forceClear = false - if (arguments[2] !== undefined) { - console.warn( - 'THREE.OutlineEffect.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.', - ) - renderTarget = arguments[2] - } - - if (arguments[3] !== undefined) { - console.warn('THREE.OutlineEffect.render(): the forceClear argument has been removed. Use .clear() instead.') - forceClear = arguments[3] - } - - if (renderTarget !== undefined) renderer.setRenderTarget(renderTarget) + this.render = function (scene, camera) { + if (this.enabled === false) { + renderer.render(scene, camera) + return + } - if (forceClear) renderer.clear() + const currentAutoClear = renderer.autoClear + renderer.autoClear = this.autoClear - if (this.enabled === false) { renderer.render(scene, camera) - return - } - - const currentAutoClear = renderer.autoClear - renderer.autoClear = this.autoClear - renderer.render(scene, camera) + renderer.autoClear = currentAutoClear - renderer.autoClear = currentAutoClear - - this.renderOutline(scene, camera) - } + this.renderOutline(scene, camera) + } - this.renderOutline = (scene, camera) => { - const currentAutoClear = renderer.autoClear - const currentSceneAutoUpdate = scene.autoUpdate - const currentSceneBackground = scene.background - const currentShadowMapEnabled = renderer.shadowMap.enabled + this.renderOutline = function (scene, camera) { + const currentAutoClear = renderer.autoClear + const currentSceneAutoUpdate = scene.matrixWorldAutoUpdate + const currentSceneBackground = scene.background + const currentShadowMapEnabled = renderer.shadowMap.enabled - scene.autoUpdate = false - scene.background = null - renderer.autoClear = false - renderer.shadowMap.enabled = false + scene.matrixWorldAutoUpdate = false + scene.background = null + renderer.autoClear = false + renderer.shadowMap.enabled = false - scene.traverse(setOutlineMaterial) + scene.traverse(setOutlineMaterial) - renderer.render(scene, camera) + renderer.render(scene, camera) - scene.traverse(restoreOriginalMaterial) + scene.traverse(restoreOriginalMaterial) - cleanupCache() + cleanupCache() - scene.autoUpdate = currentSceneAutoUpdate - scene.background = currentSceneBackground - renderer.autoClear = currentAutoClear - renderer.shadowMap.enabled = currentShadowMapEnabled - } + scene.matrixWorldAutoUpdate = currentSceneAutoUpdate + scene.background = currentSceneBackground + renderer.autoClear = currentAutoClear + renderer.shadowMap.enabled = currentShadowMapEnabled + } - /* - * See #9918 - * - * The following property copies and wrapper methods enable - * OutlineEffect to be called from other *Effect, like - * - * effect = new StereoEffect( new OutlineEffect( renderer ) ); - * - * function render () { - * - * effect.render( scene, camera ); - * - * } - */ - this.autoClear = renderer.autoClear - this.domElement = renderer.domElement - this.shadowMap = renderer.shadowMap - - this.clear = (color, depth, stencil) => { - renderer.clear(color, depth, stencil) - } + /* + * See #9918 + * + * The following property copies and wrapper methods enable + * OutlineEffect to be called from other *Effect, like + * + * effect = new StereoEffect( new OutlineEffect( renderer ) ); + * + * function render () { + * + * effect.render( scene, camera ); + * + * } + */ + this.autoClear = renderer.autoClear + this.domElement = renderer.domElement + this.shadowMap = renderer.shadowMap + + this.clear = function (color, depth, stencil) { + renderer.clear(color, depth, stencil) + } - this.getPixelRatio = () => renderer.getPixelRatio() + this.getPixelRatio = function () { + return renderer.getPixelRatio() + } - this.setPixelRatio = (value) => { - renderer.setPixelRatio(value) - } + this.setPixelRatio = function (value) { + renderer.setPixelRatio(value) + } - this.getSize = (target) => renderer.getSize(target) + this.getSize = function (target) { + return renderer.getSize(target) + } - this.setSize = (width, height, updateStyle) => { - renderer.setSize(width, height, updateStyle) - } + this.setSize = function (width, height, updateStyle) { + renderer.setSize(width, height, updateStyle) + } - this.setViewport = (x, y, width, height) => { - renderer.setViewport(x, y, width, height) - } + this.setViewport = function (x, y, width, height) { + renderer.setViewport(x, y, width, height) + } - this.setScissor = (x, y, width, height) => { - renderer.setScissor(x, y, width, height) - } + this.setScissor = function (x, y, width, height) { + renderer.setScissor(x, y, width, height) + } - this.setScissorTest = (boolean) => { - renderer.setScissorTest(boolean) - } + this.setScissorTest = function (boolean) { + renderer.setScissorTest(boolean) + } - this.setRenderTarget = (renderTarget) => { - renderer.setRenderTarget(renderTarget) + this.setRenderTarget = function (renderTarget) { + renderer.setRenderTarget(renderTarget) + } } } diff --git a/src/effects/ParallaxBarrierEffect.js b/src/effects/ParallaxBarrierEffect.js index c38bc6e4..9c90aecf 100644 --- a/src/effects/ParallaxBarrierEffect.js +++ b/src/effects/ParallaxBarrierEffect.js @@ -11,91 +11,92 @@ import { WebGLRenderTarget, } from 'three' -const ParallaxBarrierEffect = function (renderer) { - const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1) +class ParallaxBarrierEffect { + constructor(renderer) { + const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1) - const _scene = new Scene() + const _scene = new Scene() - const _stereo = new StereoCamera() + const _stereo = new StereoCamera() - const _params = { - minFilter: LinearFilter, - magFilter: NearestFilter, - format: RGBAFormat, - } + const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat } - const _renderTargetL = new WebGLRenderTarget(512, 512, _params) - const _renderTargetR = new WebGLRenderTarget(512, 512, _params) + const _renderTargetL = new WebGLRenderTarget(512, 512, _params) + const _renderTargetR = new WebGLRenderTarget(512, 512, _params) - const _material = new ShaderMaterial({ - uniforms: { - mapLeft: { value: _renderTargetL.texture }, - mapRight: { value: _renderTargetR.texture }, - }, + const _material = new ShaderMaterial({ + uniforms: { + mapLeft: { value: _renderTargetL.texture }, + mapRight: { value: _renderTargetR.texture }, + }, - vertexShader: [ - 'varying vec2 vUv;', + vertexShader: [ + 'varying vec2 vUv;', - 'void main() {', + 'void main() {', - ' vUv = vec2( uv.x, uv.y );', - ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + ' vUv = vec2( uv.x, uv.y );', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', - '}', - ].join('\n'), + '}', + ].join('\n'), - fragmentShader: [ - 'uniform sampler2D mapLeft;', - 'uniform sampler2D mapRight;', - 'varying vec2 vUv;', + fragmentShader: [ + 'uniform sampler2D mapLeft;', + 'uniform sampler2D mapRight;', + 'varying vec2 vUv;', - 'void main() {', + 'void main() {', - ' vec2 uv = vUv;', + ' vec2 uv = vUv;', - ' if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {', + ' if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {', - ' gl_FragColor = texture2D( mapLeft, uv );', + ' gl_FragColor = texture2D( mapLeft, uv );', - ' } else {', + ' } else {', - ' gl_FragColor = texture2D( mapRight, uv );', + ' gl_FragColor = texture2D( mapRight, uv );', - ' }', + ' }', - '}', - ].join('\n'), - }) + ' #include ', + ' #include ', - const mesh = new Mesh(new PlaneGeometry(2, 2), _material) - _scene.add(mesh) + '}', + ].join('\n'), + }) - this.setSize = (width, height) => { - renderer.setSize(width, height) + const mesh = new Mesh(new PlaneGeometry(2, 2), _material) + _scene.add(mesh) - const pixelRatio = renderer.getPixelRatio() + this.setSize = function (width, height) { + renderer.setSize(width, height) - _renderTargetL.setSize(width * pixelRatio, height * pixelRatio) - _renderTargetR.setSize(width * pixelRatio, height * pixelRatio) - } + const pixelRatio = renderer.getPixelRatio() + + _renderTargetL.setSize(width * pixelRatio, height * pixelRatio) + _renderTargetR.setSize(width * pixelRatio, height * pixelRatio) + } - this.render = (scene, camera) => { - scene.updateMatrixWorld() + this.render = function (scene, camera) { + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld() - if (camera.parent === null) camera.updateMatrixWorld() + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld() - _stereo.update(camera) + _stereo.update(camera) - renderer.setRenderTarget(_renderTargetL) - renderer.clear() - renderer.render(scene, _stereo.cameraL) + renderer.setRenderTarget(_renderTargetL) + renderer.clear() + renderer.render(scene, _stereo.cameraL) - renderer.setRenderTarget(_renderTargetR) - renderer.clear() - renderer.render(scene, _stereo.cameraR) + renderer.setRenderTarget(_renderTargetR) + renderer.clear() + renderer.render(scene, _stereo.cameraR) - renderer.setRenderTarget(null) - renderer.render(_scene, _camera) + renderer.setRenderTarget(null) + renderer.render(_scene, _camera) + } } } diff --git a/src/effects/PeppersGhostEffect.js b/src/effects/PeppersGhostEffect.js index 46020dec..d07142c2 100644 --- a/src/effects/PeppersGhostEffect.js +++ b/src/effects/PeppersGhostEffect.js @@ -4,114 +4,116 @@ import { PerspectiveCamera, Quaternion, Vector3 } from 'three' * peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS */ -const PeppersGhostEffect = function (renderer) { - const scope = this - - scope.cameraDistance = 15 - scope.reflectFromAbove = false - - // Internals - let _halfWidth, _width, _height - - const _cameraF = new PerspectiveCamera() //front - const _cameraB = new PerspectiveCamera() //back - const _cameraL = new PerspectiveCamera() //left - const _cameraR = new PerspectiveCamera() //right - - const _position = new Vector3() - const _quaternion = new Quaternion() - const _scale = new Vector3() - - // Initialization - renderer.autoClear = false - - this.setSize = (width, height) => { - _halfWidth = width / 2 - if (width < height) { - _width = width / 3 - _height = width / 3 - } else { - _width = height / 3 - _height = height / 3 +class PeppersGhostEffect { + constructor(renderer) { + const scope = this + + scope.cameraDistance = 15 + scope.reflectFromAbove = false + + // Internals + let _halfWidth, _width, _height + + const _cameraF = new PerspectiveCamera() //front + const _cameraB = new PerspectiveCamera() //back + const _cameraL = new PerspectiveCamera() //left + const _cameraR = new PerspectiveCamera() //right + + const _position = new Vector3() + const _quaternion = new Quaternion() + const _scale = new Vector3() + + // Initialization + renderer.autoClear = false + + this.setSize = function (width, height) { + _halfWidth = width / 2 + if (width < height) { + _width = width / 3 + _height = width / 3 + } else { + _width = height / 3 + _height = height / 3 + } + + renderer.setSize(width, height) } - renderer.setSize(width, height) - } - - this.render = (scene, camera) => { - scene.updateMatrixWorld() - - if (camera.parent === null) camera.updateMatrixWorld() - - camera.matrixWorld.decompose(_position, _quaternion, _scale) - - // front - _cameraF.position.copy(_position) - _cameraF.quaternion.copy(_quaternion) - _cameraF.translateZ(scope.cameraDistance) - _cameraF.lookAt(scene.position) - - // back - _cameraB.position.copy(_position) - _cameraB.quaternion.copy(_quaternion) - _cameraB.translateZ(-scope.cameraDistance) - _cameraB.lookAt(scene.position) - _cameraB.rotation.z += 180 * (Math.PI / 180) - - // left - _cameraL.position.copy(_position) - _cameraL.quaternion.copy(_quaternion) - _cameraL.translateX(-scope.cameraDistance) - _cameraL.lookAt(scene.position) - _cameraL.rotation.x += 90 * (Math.PI / 180) - - // right - _cameraR.position.copy(_position) - _cameraR.quaternion.copy(_quaternion) - _cameraR.translateX(scope.cameraDistance) - _cameraR.lookAt(scene.position) - _cameraR.rotation.x += 90 * (Math.PI / 180) - - renderer.clear() - renderer.setScissorTest(true) - - renderer.setScissor(_halfWidth - _width / 2, _height * 2, _width, _height) - renderer.setViewport(_halfWidth - _width / 2, _height * 2, _width, _height) - - if (scope.reflectFromAbove) { - renderer.render(scene, _cameraB) - } else { - renderer.render(scene, _cameraF) - } - - renderer.setScissor(_halfWidth - _width / 2, 0, _width, _height) - renderer.setViewport(_halfWidth - _width / 2, 0, _width, _height) - - if (scope.reflectFromAbove) { - renderer.render(scene, _cameraF) - } else { - renderer.render(scene, _cameraB) - } - - renderer.setScissor(_halfWidth - _width / 2 - _width, _height, _width, _height) - renderer.setViewport(_halfWidth - _width / 2 - _width, _height, _width, _height) - - if (scope.reflectFromAbove) { - renderer.render(scene, _cameraR) - } else { - renderer.render(scene, _cameraL) - } - - renderer.setScissor(_halfWidth + _width / 2, _height, _width, _height) - renderer.setViewport(_halfWidth + _width / 2, _height, _width, _height) - - if (scope.reflectFromAbove) { - renderer.render(scene, _cameraL) - } else { - renderer.render(scene, _cameraR) + this.render = function (scene, camera) { + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld() + + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld() + + camera.matrixWorld.decompose(_position, _quaternion, _scale) + + // front + _cameraF.position.copy(_position) + _cameraF.quaternion.copy(_quaternion) + _cameraF.translateZ(scope.cameraDistance) + _cameraF.lookAt(scene.position) + + // back + _cameraB.position.copy(_position) + _cameraB.quaternion.copy(_quaternion) + _cameraB.translateZ(-scope.cameraDistance) + _cameraB.lookAt(scene.position) + _cameraB.rotation.z += 180 * (Math.PI / 180) + + // left + _cameraL.position.copy(_position) + _cameraL.quaternion.copy(_quaternion) + _cameraL.translateX(-scope.cameraDistance) + _cameraL.lookAt(scene.position) + _cameraL.rotation.x += 90 * (Math.PI / 180) + + // right + _cameraR.position.copy(_position) + _cameraR.quaternion.copy(_quaternion) + _cameraR.translateX(scope.cameraDistance) + _cameraR.lookAt(scene.position) + _cameraR.rotation.x += 90 * (Math.PI / 180) + + renderer.clear() + renderer.setScissorTest(true) + + renderer.setScissor(_halfWidth - _width / 2, _height * 2, _width, _height) + renderer.setViewport(_halfWidth - _width / 2, _height * 2, _width, _height) + + if (scope.reflectFromAbove) { + renderer.render(scene, _cameraB) + } else { + renderer.render(scene, _cameraF) + } + + renderer.setScissor(_halfWidth - _width / 2, 0, _width, _height) + renderer.setViewport(_halfWidth - _width / 2, 0, _width, _height) + + if (scope.reflectFromAbove) { + renderer.render(scene, _cameraF) + } else { + renderer.render(scene, _cameraB) + } + + renderer.setScissor(_halfWidth - _width / 2 - _width, _height, _width, _height) + renderer.setViewport(_halfWidth - _width / 2 - _width, _height, _width, _height) + + if (scope.reflectFromAbove) { + renderer.render(scene, _cameraR) + } else { + renderer.render(scene, _cameraL) + } + + renderer.setScissor(_halfWidth + _width / 2, _height, _width, _height) + renderer.setViewport(_halfWidth + _width / 2, _height, _width, _height) + + if (scope.reflectFromAbove) { + renderer.render(scene, _cameraL) + } else { + renderer.render(scene, _cameraR) + } + + renderer.setScissorTest(false) } - - renderer.setScissorTest(false) } } diff --git a/src/effects/StereoEffect.js b/src/effects/StereoEffect.js index aa6660cf..b98dbb8b 100644 --- a/src/effects/StereoEffect.js +++ b/src/effects/StereoEffect.js @@ -1,39 +1,41 @@ import { StereoCamera, Vector2 } from 'three' -const StereoEffect = function (renderer) { - const _stereo = new StereoCamera() - _stereo.aspect = 0.5 - const size = new Vector2() +class StereoEffect { + constructor(renderer) { + const _stereo = new StereoCamera() + _stereo.aspect = 0.5 + const size = new Vector2() - this.setEyeSeparation = (eyeSep) => { - _stereo.eyeSep = eyeSep - } + this.setEyeSeparation = function (eyeSep) { + _stereo.eyeSep = eyeSep + } - this.setSize = (width, height) => { - renderer.setSize(width, height) - } + this.setSize = function (width, height) { + renderer.setSize(width, height) + } - this.render = (scene, camera) => { - scene.updateMatrixWorld() + this.render = function (scene, camera) { + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld() - if (camera.parent === null) camera.updateMatrixWorld() + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld() - _stereo.update(camera) + _stereo.update(camera) - renderer.getSize(size) + renderer.getSize(size) - if (renderer.autoClear) renderer.clear() - renderer.setScissorTest(true) + if (renderer.autoClear) renderer.clear() + renderer.setScissorTest(true) - renderer.setScissor(0, 0, size.width / 2, size.height) - renderer.setViewport(0, 0, size.width / 2, size.height) - renderer.render(scene, _stereo.cameraL) + renderer.setScissor(0, 0, size.width / 2, size.height) + renderer.setViewport(0, 0, size.width / 2, size.height) + renderer.render(scene, _stereo.cameraL) - renderer.setScissor(size.width / 2, 0, size.width / 2, size.height) - renderer.setViewport(size.width / 2, 0, size.width / 2, size.height) - renderer.render(scene, _stereo.cameraR) + renderer.setScissor(size.width / 2, 0, size.width / 2, size.height) + renderer.setViewport(size.width / 2, 0, size.width / 2, size.height) + renderer.render(scene, _stereo.cameraR) - renderer.setScissorTest(false) + renderer.setScissorTest(false) + } } } diff --git a/src/environments/RoomEnvironment.d.ts b/src/environments/RoomEnvironment.d.ts deleted file mode 100644 index 3b2124bd..00000000 --- a/src/environments/RoomEnvironment.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Scene } from 'three' - -export class RoomEnvironment extends Scene { - constructor() -} diff --git a/src/environments/RoomEnvironment.js b/src/environments/RoomEnvironment.ts similarity index 98% rename from src/environments/RoomEnvironment.js rename to src/environments/RoomEnvironment.ts index 1373db19..62e1cd90 100644 --- a/src/environments/RoomEnvironment.js +++ b/src/environments/RoomEnvironment.ts @@ -94,7 +94,7 @@ function RoomEnvironment() { light6.scale.set(1.0, 0.1, 1.0) scene.add(light6) - function createAreaLightMaterial(intensity) { + function createAreaLightMaterial(intensity: number) { const material = new THREE.MeshBasicMaterial() material.color.setScalar(intensity) return material diff --git a/src/exporters/ColladaExporter.ts b/src/exporters/ColladaExporter.ts index 9d17e4f4..bcc5a567 100644 --- a/src/exporters/ColladaExporter.ts +++ b/src/exporters/ColladaExporter.ts @@ -231,7 +231,7 @@ class ColladaExporter { if (attr instanceof InterleavedBufferAttribute && attr.isInterleavedBufferAttribute) { // use the typed array constructor to save on memory const TypedArrayConstructor: TypedArrayConstructors = attr.array.constructor - // @ts-expect-error + // @ts-ignore const arr: number[] = new TypedArrayConstructor(attr.count * attr.itemSize) const size = attr.itemSize @@ -254,7 +254,7 @@ class ColladaExporter { return arr.slice(st, st + ct) } else { const TypedArrayConstructor: TypedArrayConstructors = arr.constructor - // @ts-expect-error + // @ts-ignore return new TypedArrayConstructor(arr.buffer, st * arr.BYTES_PER_ELEMENT, ct) } } diff --git a/src/exporters/DRACOExporter.ts b/src/exporters/DRACOExporter.ts index 4a1edc27..474d6097 100644 --- a/src/exporters/DRACOExporter.ts +++ b/src/exporters/DRACOExporter.ts @@ -72,7 +72,7 @@ class DRACOExporter { dracoObject = new dracoEncoder.Mesh() const vertices = geometry.getAttribute('position') - // @ts-expect-error + // @ts-ignore builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, @@ -84,8 +84,7 @@ class DRACOExporter { const faces = geometry.getIndex() if (faces !== null) { - // @ts-expect-error - builder.AddFacesToMesh(dracoObject, faces.count / 3, faces.array) + builder.AddFacesToMesh(dracoObject, faces.count / 3, faces.array as Uint16Array | Uint32Array) } else { const faces = new (vertices.count > 65535 ? Uint32Array : Uint16Array)(vertices.count) @@ -100,7 +99,7 @@ class DRACOExporter { const normals = geometry.getAttribute('normal') if (normals !== undefined) { - // @ts-expect-error + // @ts-ignore builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, @@ -115,7 +114,7 @@ class DRACOExporter { const uvs = geometry.getAttribute('uv') if (uvs !== undefined) { - // @ts-expect-error + // @ts-ignore builder.AddFloatAttributeToMesh(dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array) } } @@ -124,14 +123,14 @@ class DRACOExporter { const colors = geometry.getAttribute('color') if (colors !== undefined) { - // @ts-expect-error + // @ts-ignore builder.AddFloatAttributeToMesh(dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array) } } } else if (object instanceof Points && object.isPoints) { - // @ts-expect-error + // @ts-ignore builder = new dracoEncoder.PointCloudBuilder() - // @ts-expect-error + // @ts-ignore dracoObject = new dracoEncoder.PointCloud() const vertices = geometry.getAttribute('position') @@ -180,7 +179,7 @@ class DRACOExporter { if (object instanceof Mesh && object.isMesh) { length = encoder.EncodeMeshToDracoBuffer(dracoObject, encodedData) } else { - // @ts-expect-error + // @ts-ignore length = encoder.EncodePointCloudToDracoBuffer(dracoObject, true, encodedData) } diff --git a/src/exporters/GLTFExporter.d.ts b/src/exporters/GLTFExporter.d.ts new file mode 100644 index 00000000..8b68ef2d --- /dev/null +++ b/src/exporters/GLTFExporter.d.ts @@ -0,0 +1,114 @@ +import { Object3D, AnimationClip, Texture, Material, Mesh } from 'three' + +export interface GLTFExporterOptions { + /** + * Export position, rotation and scale instead of matrix per node. Default is false + */ + trs?: boolean + + /** + * Export only visible objects. Default is true. + */ + onlyVisible?: boolean + + /** + * Export just the attributes within the drawRange, if defined, instead of exporting the whole array. Default is true. + */ + truncateDrawRange?: boolean + + /** + * Export in binary (.glb) format, returning an ArrayBuffer. Default is false. + */ + binary?: boolean + + /** + * Export with images embedded into the glTF asset. Default is true. + */ + embedImages?: boolean + + /** + * Restricts the image maximum size (both width and height) to the given value. This option works only if embedImages is true. Default is Infinity. + */ + maxTextureSize?: number + + /** + * List of animations to be included in the export. + */ + animations?: AnimationClip[] + + /** + * Generate indices for non-index geometry and export with them. Default is false. + */ + forceIndices?: boolean + + /** + * Export custom glTF extensions defined on an object's userData.gltfExtensions property. Default is false. + */ + includeCustomExtensions?: boolean +} + +export class GLTFExporter { + constructor() + + register(callback: (writer: GLTFWriter) => GLTFExporterPlugin): this + unregister(callback: (writer: GLTFWriter) => GLTFExporterPlugin): this + + /** + * Generates a .gltf (JSON) or .glb (binary) output from the input (Scenes or Objects) + * + * @param input Scenes or objects to export. Valid options: + * - Export scenes + * ```js + * exporter.parse( scene1, ... ) + * exporter.parse( [ scene1, scene2 ], ... ) + * ``` + * - Export objects (It will create a new Scene to hold all the objects) + * ```js + * exporter.parse( object1, ... ) + * exporter.parse( [ object1, object2 ], ... ) + * ``` + * - Mix scenes and objects (It will export the scenes as usual but it will create a new scene to hold all the single objects). + * ```js + * exporter.parse( [ scene1, object1, object2, scene2 ], ... ) + * ``` + * @param onDone Will be called when the export completes. The argument will be the generated glTF JSON or binary ArrayBuffer. + * @param onError Will be called if there are any errors during the gltf generation. + * @param options Export options + */ + parse( + input: Object3D | Object3D[], + onDone: (gltf: ArrayBuffer | { [key: string]: any }) => void, + onError: (error: ErrorEvent) => void, + options?: GLTFExporterOptions, + ): void + + parseAsync(input: Object3D | Object3D[], options?: GLTFExporterOptions): Promise +} + +export class GLTFWriter { + constructor() + + setPlugins(plugins: GLTFExporterPlugin[]): void + + /** + * Parse scenes and generate GLTF output + * + * @param input Scene or Array of THREE.Scenes + * @param onDone Callback on completed + * @param options options + */ + write( + input: Object3D | Object3D[], + onDone: (gltf: ArrayBuffer | { [key: string]: any }) => void, + options?: GLTFExporterOptions, + ): Promise +} + +export interface GLTFExporterPlugin { + writeTexture?: (map: Texture, textureDef: { [key: string]: any }) => void + writeMaterial?: (material: Material, materialDef: { [key: string]: any }) => void + writeMesh?: (mesh: Mesh, meshDef: { [key: string]: any }) => void + writeNode?: (object: Object3D, nodeDef: { [key: string]: any }) => void + beforeParse?: (input: Object3D | Object3D[]) => void + afterParse?: (input: Object3D | Object3D[]) => void +} diff --git a/src/exporters/GLTFExporter.js b/src/exporters/GLTFExporter.js new file mode 100644 index 00000000..87065f89 --- /dev/null +++ b/src/exporters/GLTFExporter.js @@ -0,0 +1,2620 @@ +import { + REVISION, + BufferAttribute, + ClampToEdgeWrapping, + Color, + DoubleSide, + InterpolateDiscrete, + InterpolateLinear, + NoColorSpace, + LinearFilter, + LinearMipmapLinearFilter, + LinearMipmapNearestFilter, + MathUtils, + Matrix4, + MirroredRepeatWrapping, + NearestFilter, + NearestMipmapLinearFilter, + NearestMipmapNearestFilter, + PropertyBinding, + RGBAFormat, + RepeatWrapping, + Scene, + Source, + SRGBColorSpace, + CompressedTexture, + Vector3, + PlaneGeometry, + ShaderMaterial, + Uniform, + Mesh, + PerspectiveCamera, + WebGLRenderer, + Texture, +} from 'three' + +let _renderer +let fullscreenQuadGeometry +let fullscreenQuadMaterial +let fullscreenQuad + +function decompress(texture, maxTextureSize = Infinity, renderer = null) { + if (!fullscreenQuadGeometry) fullscreenQuadGeometry = new PlaneGeometry(2, 2, 1, 1) + if (!fullscreenQuadMaterial) + fullscreenQuadMaterial = new ShaderMaterial({ + uniforms: { blitTexture: new Uniform(texture) }, + vertexShader: /* glsl */ ` + varying vec2 vUv; + void main(){ + vUv = uv; + gl_Position = vec4(position.xy * 1.0,0.,.999999); + } + `, + fragmentShader: /* glsl */ ` + uniform sampler2D blitTexture; + varying vec2 vUv; + + void main(){ + gl_FragColor = vec4(vUv.xy, 0, 1); + + #ifdef IS_SRGB + gl_FragColor = LinearTosRGB( texture2D( blitTexture, vUv) ); + #else + gl_FragColor = texture2D( blitTexture, vUv); + #endif + } + `, + }) + + fullscreenQuadMaterial.uniforms.blitTexture.value = texture + fullscreenQuadMaterial.defines.IS_SRGB = + 'colorSpace' in texture ? texture.colorSpace === 'srgb' : texture.encoding === 3001 + fullscreenQuadMaterial.needsUpdate = true + + if (!fullscreenQuad) { + fullscreenQuad = new Mesh(fullscreenQuadGeometry, fullscreenQuadMaterial) + fullscreenQuad.frustrumCulled = false + } + + const _camera = new PerspectiveCamera() + const _scene = new Scene() + _scene.add(fullscreenQuad) + + if (!renderer) { + renderer = _renderer = new WebGLRenderer({ antialias: false }) + } + + renderer.setSize(Math.min(texture.image.width, maxTextureSize), Math.min(texture.image.height, maxTextureSize)) + renderer.clear() + renderer.render(_scene, _camera) + + const readableTexture = new Texture(renderer.domElement) + + readableTexture.minFilter = texture.minFilter + readableTexture.magFilter = texture.magFilter + readableTexture.wrapS = texture.wrapS + readableTexture.wrapT = texture.wrapT + readableTexture.name = texture.name + + if (_renderer) { + _renderer.dispose() + _renderer = null + } + + return readableTexture +} + +/** + * The KHR_mesh_quantization extension allows these extra attribute component types + * + * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes + */ +const KHR_mesh_quantization_ExtraAttrTypes = { + POSITION: [ + 'byte', + 'byte normalized', + 'unsigned byte', + 'unsigned byte normalized', + 'short', + 'short normalized', + 'unsigned short', + 'unsigned short normalized', + ], + NORMAL: ['byte normalized', 'short normalized'], + TANGENT: ['byte normalized', 'short normalized'], + TEXCOORD: ['byte', 'byte normalized', 'unsigned byte', 'short', 'short normalized', 'unsigned short'], +} + +class GLTFExporter { + constructor() { + this.pluginCallbacks = [] + + this.register(function (writer) { + return new GLTFLightExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsUnlitExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsTransmissionExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsVolumeExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsIorExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsSpecularExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsClearcoatExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsIridescenceExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsSheenExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsAnisotropyExtension(writer) + }) + + this.register(function (writer) { + return new GLTFMaterialsEmissiveStrengthExtension(writer) + }) + } + + register(callback) { + if (this.pluginCallbacks.indexOf(callback) === -1) { + this.pluginCallbacks.push(callback) + } + + return this + } + + unregister(callback) { + if (this.pluginCallbacks.indexOf(callback) !== -1) { + this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(callback), 1) + } + + return this + } + + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Function} onError Callback on errors + * @param {Object} options options + */ + parse(input, onDone, onError, options) { + const writer = new GLTFWriter() + const plugins = [] + + for (let i = 0, il = this.pluginCallbacks.length; i < il; i++) { + plugins.push(this.pluginCallbacks[i](writer)) + } + + writer.setPlugins(plugins) + writer.write(input, onDone, options).catch(onError) + } + + parseAsync(input, options) { + const scope = this + + return new Promise(function (resolve, reject) { + scope.parse(input, resolve, reject, options) + }) + } +} + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const WEBGL_CONSTANTS = { + POINTS: 0x0000, + LINES: 0x0001, + LINE_LOOP: 0x0002, + LINE_STRIP: 0x0003, + TRIANGLES: 0x0004, + TRIANGLE_STRIP: 0x0005, + TRIANGLE_FAN: 0x0006, + + BYTE: 0x1400, + UNSIGNED_BYTE: 0x1401, + SHORT: 0x1402, + UNSIGNED_SHORT: 0x1403, + INT: 0x1404, + UNSIGNED_INT: 0x1405, + FLOAT: 0x1406, + + ARRAY_BUFFER: 0x8892, + ELEMENT_ARRAY_BUFFER: 0x8893, + + NEAREST: 0x2600, + LINEAR: 0x2601, + NEAREST_MIPMAP_NEAREST: 0x2700, + LINEAR_MIPMAP_NEAREST: 0x2701, + NEAREST_MIPMAP_LINEAR: 0x2702, + LINEAR_MIPMAP_LINEAR: 0x2703, + + CLAMP_TO_EDGE: 33071, + MIRRORED_REPEAT: 33648, + REPEAT: 10497, +} + +const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization' + +const THREE_TO_WEBGL = {} + +THREE_TO_WEBGL[NearestFilter] = WEBGL_CONSTANTS.NEAREST +THREE_TO_WEBGL[NearestMipmapNearestFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST +THREE_TO_WEBGL[NearestMipmapLinearFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR +THREE_TO_WEBGL[LinearFilter] = WEBGL_CONSTANTS.LINEAR +THREE_TO_WEBGL[LinearMipmapNearestFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST +THREE_TO_WEBGL[LinearMipmapLinearFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR + +THREE_TO_WEBGL[ClampToEdgeWrapping] = WEBGL_CONSTANTS.CLAMP_TO_EDGE +THREE_TO_WEBGL[RepeatWrapping] = WEBGL_CONSTANTS.REPEAT +THREE_TO_WEBGL[MirroredRepeatWrapping] = WEBGL_CONSTANTS.MIRRORED_REPEAT + +const PATH_PROPERTIES = { + scale: 'scale', + position: 'translation', + quaternion: 'rotation', + morphTargetInfluences: 'weights', +} + +const DEFAULT_SPECULAR_COLOR = new Color() + +// GLB constants +// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + +const GLB_HEADER_BYTES = 12 +const GLB_HEADER_MAGIC = 0x46546c67 +const GLB_VERSION = 2 + +const GLB_CHUNK_PREFIX_BYTES = 8 +const GLB_CHUNK_TYPE_JSON = 0x4e4f534a +const GLB_CHUNK_TYPE_BIN = 0x004e4942 + +//------------------------------------------------------------------------------ +// Utility functions +//------------------------------------------------------------------------------ + +/** + * Compare two arrays + * @param {Array} array1 Array 1 to compare + * @param {Array} array2 Array 2 to compare + * @return {Boolean} Returns true if both arrays are equal + */ +function equalArray(array1, array2) { + return ( + array1.length === array2.length && + array1.every(function (element, index) { + return element === array2[index] + }) + ) +} + +/** + * Converts a string to an ArrayBuffer. + * @param {string} text + * @return {ArrayBuffer} + */ +function stringToArrayBuffer(text) { + return new TextEncoder().encode(text).buffer +} + +/** + * Is identity matrix + * + * @param {Matrix4} matrix + * @returns {Boolean} Returns true, if parameter is identity matrix + */ +function isIdentityMatrix(matrix) { + return equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) +} + +/** + * Get the min and max vectors from the given attribute + * @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count + * @param {Integer} start + * @param {Integer} count + * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components) + */ +function getMinMax(attribute, start, count) { + const output = { + min: new Array(attribute.itemSize).fill(Number.POSITIVE_INFINITY), + max: new Array(attribute.itemSize).fill(Number.NEGATIVE_INFINITY), + } + + for (let i = start; i < start + count; i++) { + for (let a = 0; a < attribute.itemSize; a++) { + let value + + if (attribute.itemSize > 4) { + // no support for interleaved data for itemSize > 4 + + value = attribute.array[i * attribute.itemSize + a] + } else { + if (a === 0) value = attribute.getX(i) + else if (a === 1) value = attribute.getY(i) + else if (a === 2) value = attribute.getZ(i) + else if (a === 3) value = attribute.getW(i) + + if (attribute.normalized === true) { + value = MathUtils.normalize(value, attribute.array) + } + } + + output.min[a] = Math.min(output.min[a], value) + output.max[a] = Math.max(output.max[a], value) + } + } + + return output +} + +/** + * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. + * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment + * + * @param {Integer} bufferSize The size the original buffer. + * @returns {Integer} new buffer size with required padding. + * + */ +function getPaddedBufferSize(bufferSize) { + return Math.ceil(bufferSize / 4) * 4 +} + +/** + * Returns a buffer aligned to 4-byte boundary. + * + * @param {ArrayBuffer} arrayBuffer Buffer to pad + * @param {Integer} paddingByte (Optional) + * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer + */ +function getPaddedArrayBuffer(arrayBuffer, paddingByte = 0) { + const paddedLength = getPaddedBufferSize(arrayBuffer.byteLength) + + if (paddedLength !== arrayBuffer.byteLength) { + const array = new Uint8Array(paddedLength) + array.set(new Uint8Array(arrayBuffer)) + + if (paddingByte !== 0) { + for (let i = arrayBuffer.byteLength; i < paddedLength; i++) { + array[i] = paddingByte + } + } + + return array.buffer + } + + return arrayBuffer +} + +function getCanvas() { + if (typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined') { + return new OffscreenCanvas(1, 1) + } + + return document.createElement('canvas') +} + +function getToBlobPromise(canvas, mimeType) { + if (canvas.toBlob !== undefined) { + return new Promise((resolve) => canvas.toBlob(resolve, mimeType)) + } + + let quality + + // Blink's implementation of convertToBlob seems to default to a quality level of 100% + // Use the Blink default quality levels of toBlob instead so that file sizes are comparable. + if (mimeType === 'image/jpeg') { + quality = 0.92 + } else if (mimeType === 'image/webp') { + quality = 0.8 + } + + return canvas.convertToBlob({ + type: mimeType, + quality: quality, + }) +} + +/** + * Writer + */ +class GLTFWriter { + constructor() { + this.plugins = [] + + this.options = {} + this.pending = [] + this.buffers = [] + + this.byteOffset = 0 + this.buffers = [] + this.nodeMap = new Map() + this.skins = [] + + this.extensionsUsed = {} + this.extensionsRequired = {} + + this.uids = new Map() + this.uid = 0 + + this.json = { + asset: { + version: '2.0', + generator: 'THREE.GLTFExporter', + }, + } + + this.cache = { + meshes: new Map(), + attributes: new Map(), + attributesNormalized: new Map(), + materials: new Map(), + textures: new Map(), + images: new Map(), + } + } + + setPlugins(plugins) { + this.plugins = plugins + } + + /** + * Parse scenes and generate GLTF output + * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes + * @param {Function} onDone Callback on completed + * @param {Object} options options + */ + async write(input, onDone, options = {}) { + this.options = Object.assign( + { + // default options + binary: false, + trs: false, + onlyVisible: true, + maxTextureSize: Infinity, + animations: [], + includeCustomExtensions: false, + }, + options, + ) + + if (this.options.animations.length > 0) { + // Only TRS properties, and not matrices, may be targeted by animation. + this.options.trs = true + } + + this.processInput(input) + + await Promise.all(this.pending) + + const writer = this + const buffers = writer.buffers + const json = writer.json + options = writer.options + + const extensionsUsed = writer.extensionsUsed + const extensionsRequired = writer.extensionsRequired + + // Merge buffers. + const blob = new Blob(buffers, { type: 'application/octet-stream' }) + + // Declare extensions. + const extensionsUsedList = Object.keys(extensionsUsed) + const extensionsRequiredList = Object.keys(extensionsRequired) + + if (extensionsUsedList.length > 0) json.extensionsUsed = extensionsUsedList + if (extensionsRequiredList.length > 0) json.extensionsRequired = extensionsRequiredList + + // Update bytelength of the single buffer. + if (json.buffers && json.buffers.length > 0) json.buffers[0].byteLength = blob.size + + if (options.binary === true) { + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification + + const reader = new FileReader() + reader.readAsArrayBuffer(blob) + reader.onloadend = function () { + // Binary chunk. + const binaryChunk = getPaddedArrayBuffer(reader.result) + const binaryChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES)) + binaryChunkPrefix.setUint32(0, binaryChunk.byteLength, true) + binaryChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_BIN, true) + + // JSON chunk. + const jsonChunk = getPaddedArrayBuffer(stringToArrayBuffer(JSON.stringify(json)), 0x20) + const jsonChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES)) + jsonChunkPrefix.setUint32(0, jsonChunk.byteLength, true) + jsonChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_JSON, true) + + // GLB header. + const header = new ArrayBuffer(GLB_HEADER_BYTES) + const headerView = new DataView(header) + headerView.setUint32(0, GLB_HEADER_MAGIC, true) + headerView.setUint32(4, GLB_VERSION, true) + const totalByteLength = + GLB_HEADER_BYTES + + jsonChunkPrefix.byteLength + + jsonChunk.byteLength + + binaryChunkPrefix.byteLength + + binaryChunk.byteLength + headerView.setUint32(8, totalByteLength, true) + + const glbBlob = new Blob([header, jsonChunkPrefix, jsonChunk, binaryChunkPrefix, binaryChunk], { + type: 'application/octet-stream', + }) + + const glbReader = new FileReader() + glbReader.readAsArrayBuffer(glbBlob) + glbReader.onloadend = function () { + onDone(glbReader.result) + } + } + } else { + if (json.buffers && json.buffers.length > 0) { + const reader = new FileReader() + reader.readAsDataURL(blob) + reader.onloadend = function () { + const base64data = reader.result + json.buffers[0].uri = base64data + onDone(json) + } + } else { + onDone(json) + } + } + } + + /** + * Serializes a userData. + * + * @param {THREE.Object3D|THREE.Material} object + * @param {Object} objectDef + */ + serializeUserData(object, objectDef) { + if (Object.keys(object.userData).length === 0) return + + const options = this.options + const extensionsUsed = this.extensionsUsed + + try { + const json = JSON.parse(JSON.stringify(object.userData)) + + if (options.includeCustomExtensions && json.gltfExtensions) { + if (objectDef.extensions === undefined) objectDef.extensions = {} + + for (const extensionName in json.gltfExtensions) { + objectDef.extensions[extensionName] = json.gltfExtensions[extensionName] + extensionsUsed[extensionName] = true + } + + delete json.gltfExtensions + } + + if (Object.keys(json).length > 0) objectDef.extras = json + } catch (error) { + console.warn( + "THREE.GLTFExporter: userData of '" + + object.name + + "' " + + "won't be serialized because of JSON.stringify error - " + + error.message, + ) + } + } + + /** + * Returns ids for buffer attributes. + * @param {Object} object + * @return {Integer} + */ + getUID(attribute, isRelativeCopy = false) { + if (this.uids.has(attribute) === false) { + const uids = new Map() + + uids.set(true, this.uid++) + uids.set(false, this.uid++) + + this.uids.set(attribute, uids) + } + + const uids = this.uids.get(attribute) + + return uids.get(isRelativeCopy) + } + + /** + * Checks if normal attribute values are normalized. + * + * @param {BufferAttribute} normal + * @returns {Boolean} + */ + isNormalizedNormalAttribute(normal) { + const cache = this.cache + + if (cache.attributesNormalized.has(normal)) return false + + const v = new Vector3() + + for (let i = 0, il = normal.count; i < il; i++) { + // 0.0005 is from glTF-validator + if (Math.abs(v.fromBufferAttribute(normal, i).length() - 1.0) > 0.0005) return false + } + + return true + } + + /** + * Creates normalized normal buffer attribute. + * + * @param {BufferAttribute} normal + * @returns {BufferAttribute} + * + */ + createNormalizedNormalAttribute(normal) { + const cache = this.cache + + if (cache.attributesNormalized.has(normal)) return cache.attributesNormalized.get(normal) + + const attribute = normal.clone() + const v = new Vector3() + + for (let i = 0, il = attribute.count; i < il; i++) { + v.fromBufferAttribute(attribute, i) + + if (v.x === 0 && v.y === 0 && v.z === 0) { + // if values can't be normalized set (1, 0, 0) + v.setX(1.0) + } else { + v.normalize() + } + + attribute.setXYZ(i, v.x, v.y, v.z) + } + + cache.attributesNormalized.set(normal, attribute) + + return attribute + } + + /** + * Applies a texture transform, if present, to the map definition. Requires + * the KHR_texture_transform extension. + * + * @param {Object} mapDef + * @param {THREE.Texture} texture + */ + applyTextureTransform(mapDef, texture) { + let didTransform = false + const transformDef = {} + + if (texture.offset.x !== 0 || texture.offset.y !== 0) { + transformDef.offset = texture.offset.toArray() + didTransform = true + } + + if (texture.rotation !== 0) { + transformDef.rotation = texture.rotation + didTransform = true + } + + if (texture.repeat.x !== 1 || texture.repeat.y !== 1) { + transformDef.scale = texture.repeat.toArray() + didTransform = true + } + + if (didTransform) { + mapDef.extensions = mapDef.extensions || {} + mapDef.extensions['KHR_texture_transform'] = transformDef + this.extensionsUsed['KHR_texture_transform'] = true + } + } + + buildMetalRoughTexture(metalnessMap, roughnessMap) { + if (metalnessMap === roughnessMap) return metalnessMap + + function getEncodingConversion(map) { + if (map.colorSpace === SRGBColorSpace) { + return function SRGBToLinear(c) { + return c < 0.04045 ? c * 0.0773993808 : Math.pow(c * 0.9478672986 + 0.0521327014, 2.4) + } + } + + return function LinearToLinear(c) { + return c + } + } + + console.warn('THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.') + + if (metalnessMap instanceof CompressedTexture) { + metalnessMap = decompress(metalnessMap) + } + + if (roughnessMap instanceof CompressedTexture) { + roughnessMap = decompress(roughnessMap) + } + + const metalness = metalnessMap ? metalnessMap.image : null + const roughness = roughnessMap ? roughnessMap.image : null + + const width = Math.max(metalness ? metalness.width : 0, roughness ? roughness.width : 0) + const height = Math.max(metalness ? metalness.height : 0, roughness ? roughness.height : 0) + + const canvas = getCanvas() + canvas.width = width + canvas.height = height + + const context = canvas.getContext('2d') + context.fillStyle = '#00ffff' + context.fillRect(0, 0, width, height) + + const composite = context.getImageData(0, 0, width, height) + + if (metalness) { + context.drawImage(metalness, 0, 0, width, height) + + const convert = getEncodingConversion(metalnessMap) + const data = context.getImageData(0, 0, width, height).data + + for (let i = 2; i < data.length; i += 4) { + composite.data[i] = convert(data[i] / 256) * 256 + } + } + + if (roughness) { + context.drawImage(roughness, 0, 0, width, height) + + const convert = getEncodingConversion(roughnessMap) + const data = context.getImageData(0, 0, width, height).data + + for (let i = 1; i < data.length; i += 4) { + composite.data[i] = convert(data[i] / 256) * 256 + } + } + + context.putImageData(composite, 0, 0) + + // + + const reference = metalnessMap || roughnessMap + + const texture = reference.clone() + + texture.source = new Source(canvas) + texture.colorSpace = NoColorSpace + texture.channel = (metalnessMap || roughnessMap).channel + + if (metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel) { + console.warn('THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.') + } + + return texture + } + + /** + * Process a buffer to append to the default one. + * @param {ArrayBuffer} buffer + * @return {Integer} + */ + processBuffer(buffer) { + const json = this.json + const buffers = this.buffers + + if (!json.buffers) json.buffers = [{ byteLength: 0 }] + + // All buffers are merged before export. + buffers.push(buffer) + + return 0 + } + + /** + * Process and generate a BufferView + * @param {BufferAttribute} attribute + * @param {number} componentType + * @param {number} start + * @param {number} count + * @param {number} target (Optional) Target usage of the BufferView + * @return {Object} + */ + processBufferView(attribute, componentType, start, count, target) { + const json = this.json + + if (!json.bufferViews) json.bufferViews = [] + + // Create a new dataview and dump the attribute's array into it + + let componentSize + + switch (componentType) { + case WEBGL_CONSTANTS.BYTE: + case WEBGL_CONSTANTS.UNSIGNED_BYTE: + componentSize = 1 + + break + + case WEBGL_CONSTANTS.SHORT: + case WEBGL_CONSTANTS.UNSIGNED_SHORT: + componentSize = 2 + + break + + default: + componentSize = 4 + } + + const byteLength = getPaddedBufferSize(count * attribute.itemSize * componentSize) + const dataView = new DataView(new ArrayBuffer(byteLength)) + let offset = 0 + + for (let i = start; i < start + count; i++) { + for (let a = 0; a < attribute.itemSize; a++) { + let value + + if (attribute.itemSize > 4) { + // no support for interleaved data for itemSize > 4 + + value = attribute.array[i * attribute.itemSize + a] + } else { + if (a === 0) value = attribute.getX(i) + else if (a === 1) value = attribute.getY(i) + else if (a === 2) value = attribute.getZ(i) + else if (a === 3) value = attribute.getW(i) + + if (attribute.normalized === true) { + value = MathUtils.normalize(value, attribute.array) + } + } + + if (componentType === WEBGL_CONSTANTS.FLOAT) { + dataView.setFloat32(offset, value, true) + } else if (componentType === WEBGL_CONSTANTS.INT) { + dataView.setInt32(offset, value, true) + } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_INT) { + dataView.setUint32(offset, value, true) + } else if (componentType === WEBGL_CONSTANTS.SHORT) { + dataView.setInt16(offset, value, true) + } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT) { + dataView.setUint16(offset, value, true) + } else if (componentType === WEBGL_CONSTANTS.BYTE) { + dataView.setInt8(offset, value) + } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE) { + dataView.setUint8(offset, value) + } + + offset += componentSize + } + } + + const bufferViewDef = { + buffer: this.processBuffer(dataView.buffer), + byteOffset: this.byteOffset, + byteLength: byteLength, + } + + if (target !== undefined) bufferViewDef.target = target + + if (target === WEBGL_CONSTANTS.ARRAY_BUFFER) { + // Only define byteStride for vertex attributes. + bufferViewDef.byteStride = attribute.itemSize * componentSize + } + + this.byteOffset += byteLength + + json.bufferViews.push(bufferViewDef) + + // @TODO Merge bufferViews where possible. + const output = { + id: json.bufferViews.length - 1, + byteLength: 0, + } + + return output + } + + /** + * Process and generate a BufferView from an image Blob. + * @param {Blob} blob + * @return {Promise} + */ + processBufferViewImage(blob) { + const writer = this + const json = writer.json + + if (!json.bufferViews) json.bufferViews = [] + + return new Promise(function (resolve) { + const reader = new FileReader() + reader.readAsArrayBuffer(blob) + reader.onloadend = function () { + const buffer = getPaddedArrayBuffer(reader.result) + + const bufferViewDef = { + buffer: writer.processBuffer(buffer), + byteOffset: writer.byteOffset, + byteLength: buffer.byteLength, + } + + writer.byteOffset += buffer.byteLength + resolve(json.bufferViews.push(bufferViewDef) - 1) + } + }) + } + + /** + * Process attribute to generate an accessor + * @param {BufferAttribute} attribute Attribute to process + * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range + * @param {Integer} start (Optional) + * @param {Integer} count (Optional) + * @return {Integer|null} Index of the processed accessor on the "accessors" array + */ + processAccessor(attribute, geometry, start, count) { + const json = this.json + + const types = { + 1: 'SCALAR', + 2: 'VEC2', + 3: 'VEC3', + 4: 'VEC4', + 9: 'MAT3', + 16: 'MAT4', + } + + let componentType + + // Detect the component type of the attribute array + if (attribute.array.constructor === Float32Array) { + componentType = WEBGL_CONSTANTS.FLOAT + } else if (attribute.array.constructor === Int32Array) { + componentType = WEBGL_CONSTANTS.INT + } else if (attribute.array.constructor === Uint32Array) { + componentType = WEBGL_CONSTANTS.UNSIGNED_INT + } else if (attribute.array.constructor === Int16Array) { + componentType = WEBGL_CONSTANTS.SHORT + } else if (attribute.array.constructor === Uint16Array) { + componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT + } else if (attribute.array.constructor === Int8Array) { + componentType = WEBGL_CONSTANTS.BYTE + } else if (attribute.array.constructor === Uint8Array) { + componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE + } else { + throw new Error( + 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name, + ) + } + + if (start === undefined) start = 0 + if (count === undefined) count = attribute.count + + // Skip creating an accessor if the attribute doesn't have data to export + if (count === 0) return null + + const minMax = getMinMax(attribute, start, count) + let bufferViewTarget + + // If geometry isn't provided, don't infer the target usage of the bufferView. For + // animation samplers, target must not be set. + if (geometry !== undefined) { + bufferViewTarget = + attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER + } + + const bufferView = this.processBufferView(attribute, componentType, start, count, bufferViewTarget) + + const accessorDef = { + bufferView: bufferView.id, + byteOffset: bufferView.byteOffset, + componentType: componentType, + count: count, + max: minMax.max, + min: minMax.min, + type: types[attribute.itemSize], + } + + if (attribute.normalized === true) accessorDef.normalized = true + if (!json.accessors) json.accessors = [] + + return json.accessors.push(accessorDef) - 1 + } + + /** + * Process image + * @param {Image} image to process + * @param {Integer} format of the image (RGBAFormat) + * @param {Boolean} flipY before writing out the image + * @param {String} mimeType export format + * @return {Integer} Index of the processed texture in the "images" array + */ + processImage(image, format, flipY, mimeType = 'image/png') { + if (image !== null) { + const writer = this + const cache = writer.cache + const json = writer.json + const options = writer.options + const pending = writer.pending + + if (!cache.images.has(image)) cache.images.set(image, {}) + + const cachedImages = cache.images.get(image) + + const key = mimeType + ':flipY/' + flipY.toString() + + if (cachedImages[key] !== undefined) return cachedImages[key] + + if (!json.images) json.images = [] + + const imageDef = { mimeType: mimeType } + + const canvas = getCanvas() + + canvas.width = Math.min(image.width, options.maxTextureSize) + canvas.height = Math.min(image.height, options.maxTextureSize) + + const ctx = canvas.getContext('2d') + + if (flipY === true) { + ctx.translate(0, canvas.height) + ctx.scale(1, -1) + } + + if (image.data !== undefined) { + // THREE.DataTexture + + if (format !== RGBAFormat) { + console.error('GLTFExporter: Only RGBAFormat is supported.', format) + } + + if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) { + console.warn('GLTFExporter: Image size is bigger than maxTextureSize', image) + } + + const data = new Uint8ClampedArray(image.height * image.width * 4) + + for (let i = 0; i < data.length; i += 4) { + data[i + 0] = image.data[i + 0] + data[i + 1] = image.data[i + 1] + data[i + 2] = image.data[i + 2] + data[i + 3] = image.data[i + 3] + } + + ctx.putImageData(new ImageData(data, image.width, image.height), 0, 0) + } else { + ctx.drawImage(image, 0, 0, canvas.width, canvas.height) + } + + if (options.binary === true) { + pending.push( + getToBlobPromise(canvas, mimeType) + .then((blob) => writer.processBufferViewImage(blob)) + .then((bufferViewIndex) => { + imageDef.bufferView = bufferViewIndex + }), + ) + } else { + if (canvas.toDataURL !== undefined) { + imageDef.uri = canvas.toDataURL(mimeType) + } else { + pending.push( + getToBlobPromise(canvas, mimeType) + .then((blob) => new FileReader().readAsDataURL(blob)) + .then((dataURL) => { + imageDef.uri = dataURL + }), + ) + } + } + + const index = json.images.push(imageDef) - 1 + cachedImages[key] = index + return index + } else { + throw new Error('THREE.GLTFExporter: No valid image data found. Unable to process texture.') + } + } + + /** + * Process sampler + * @param {Texture} map Texture to process + * @return {Integer} Index of the processed texture in the "samplers" array + */ + processSampler(map) { + const json = this.json + + if (!json.samplers) json.samplers = [] + + const samplerDef = { + magFilter: THREE_TO_WEBGL[map.magFilter], + minFilter: THREE_TO_WEBGL[map.minFilter], + wrapS: THREE_TO_WEBGL[map.wrapS], + wrapT: THREE_TO_WEBGL[map.wrapT], + } + + return json.samplers.push(samplerDef) - 1 + } + + /** + * Process texture + * @param {Texture} map Map to process + * @return {Integer} Index of the processed texture in the "textures" array + */ + processTexture(map) { + const writer = this + const options = writer.options + const cache = this.cache + const json = this.json + + if (cache.textures.has(map)) return cache.textures.get(map) + + if (!json.textures) json.textures = [] + + // make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture + if (map instanceof CompressedTexture) { + map = decompress(map, options.maxTextureSize) + } + + let mimeType = map.userData.mimeType + + if (mimeType === 'image/webp') mimeType = 'image/png' + + const textureDef = { + sampler: this.processSampler(map), + source: this.processImage(map.image, map.format, map.flipY, mimeType), + } + + if (map.name) textureDef.name = map.name + + this._invokeAll(function (ext) { + ext.writeTexture && ext.writeTexture(map, textureDef) + }) + + const index = json.textures.push(textureDef) - 1 + cache.textures.set(map, index) + return index + } + + /** + * Process material + * @param {THREE.Material} material Material to process + * @return {Integer|null} Index of the processed material in the "materials" array + */ + processMaterial(material) { + const cache = this.cache + const json = this.json + + if (cache.materials.has(material)) return cache.materials.get(material) + + if (material.isShaderMaterial) { + console.warn('GLTFExporter: THREE.ShaderMaterial not supported.') + return null + } + + if (!json.materials) json.materials = [] + + // @QUESTION Should we avoid including any attribute that has the default value? + const materialDef = { pbrMetallicRoughness: {} } + + if (material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true) { + console.warn('GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.') + } + + // pbrMetallicRoughness.baseColorFactor + const color = material.color.toArray().concat([material.opacity]) + + if (!equalArray(color, [1, 1, 1, 1])) { + materialDef.pbrMetallicRoughness.baseColorFactor = color + } + + if (material.isMeshStandardMaterial) { + materialDef.pbrMetallicRoughness.metallicFactor = material.metalness + materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness + } else { + materialDef.pbrMetallicRoughness.metallicFactor = 0.5 + materialDef.pbrMetallicRoughness.roughnessFactor = 0.5 + } + + // pbrMetallicRoughness.metallicRoughnessTexture + if (material.metalnessMap || material.roughnessMap) { + const metalRoughTexture = this.buildMetalRoughTexture(material.metalnessMap, material.roughnessMap) + + const metalRoughMapDef = { + index: this.processTexture(metalRoughTexture), + channel: metalRoughTexture.channel, + } + this.applyTextureTransform(metalRoughMapDef, metalRoughTexture) + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef + } + + // pbrMetallicRoughness.baseColorTexture + if (material.map) { + const baseColorMapDef = { + index: this.processTexture(material.map), + texCoord: material.map.channel, + } + this.applyTextureTransform(baseColorMapDef, material.map) + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef + } + + if (material.emissive) { + const emissive = material.emissive + const maxEmissiveComponent = Math.max(emissive.r, emissive.g, emissive.b) + + if (maxEmissiveComponent > 0) { + materialDef.emissiveFactor = material.emissive.toArray() + } + + // emissiveTexture + if (material.emissiveMap) { + const emissiveMapDef = { + index: this.processTexture(material.emissiveMap), + texCoord: material.emissiveMap.channel, + } + this.applyTextureTransform(emissiveMapDef, material.emissiveMap) + materialDef.emissiveTexture = emissiveMapDef + } + } + + // normalTexture + if (material.normalMap) { + const normalMapDef = { + index: this.processTexture(material.normalMap), + texCoord: material.normalMap.channel, + } + + if (material.normalScale && material.normalScale.x !== 1) { + // glTF normal scale is univariate. Ignore `y`, which may be flipped. + // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + normalMapDef.scale = material.normalScale.x + } + + this.applyTextureTransform(normalMapDef, material.normalMap) + materialDef.normalTexture = normalMapDef + } + + // occlusionTexture + if (material.aoMap) { + const occlusionMapDef = { + index: this.processTexture(material.aoMap), + texCoord: material.aoMap.channel, + } + + if (material.aoMapIntensity !== 1.0) { + occlusionMapDef.strength = material.aoMapIntensity + } + + this.applyTextureTransform(occlusionMapDef, material.aoMap) + materialDef.occlusionTexture = occlusionMapDef + } + + // alphaMode + if (material.transparent) { + materialDef.alphaMode = 'BLEND' + } else { + if (material.alphaTest > 0.0) { + materialDef.alphaMode = 'MASK' + materialDef.alphaCutoff = material.alphaTest + } + } + + // doubleSided + if (material.side === DoubleSide) materialDef.doubleSided = true + if (material.name !== '') materialDef.name = material.name + + this.serializeUserData(material, materialDef) + + this._invokeAll(function (ext) { + ext.writeMaterial && ext.writeMaterial(material, materialDef) + }) + + const index = json.materials.push(materialDef) - 1 + cache.materials.set(material, index) + return index + } + + /** + * Process mesh + * @param {THREE.Mesh} mesh Mesh to process + * @return {Integer|null} Index of the processed mesh in the "meshes" array + */ + processMesh(mesh) { + const cache = this.cache + const json = this.json + + const meshCacheKeyParts = [mesh.geometry.uuid] + + if (Array.isArray(mesh.material)) { + for (let i = 0, l = mesh.material.length; i < l; i++) { + meshCacheKeyParts.push(mesh.material[i].uuid) + } + } else { + meshCacheKeyParts.push(mesh.material.uuid) + } + + const meshCacheKey = meshCacheKeyParts.join(':') + + if (cache.meshes.has(meshCacheKey)) return cache.meshes.get(meshCacheKey) + + const geometry = mesh.geometry + + let mode + + // Use the correct mode + if (mesh.isLineSegments) { + mode = WEBGL_CONSTANTS.LINES + } else if (mesh.isLineLoop) { + mode = WEBGL_CONSTANTS.LINE_LOOP + } else if (mesh.isLine) { + mode = WEBGL_CONSTANTS.LINE_STRIP + } else if (mesh.isPoints) { + mode = WEBGL_CONSTANTS.POINTS + } else { + mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES + } + + const meshDef = {} + const attributes = {} + const primitives = [] + const targets = [] + + // Conversion between attributes names in threejs and gltf spec + const nameConversion = { + uv: 'TEXCOORD_0', + [REVISION.replace(/\D+/g, '') >= 152 ? 'uv1' : 'uv2']: 'TEXCOORD_1', + color: 'COLOR_0', + skinWeight: 'WEIGHTS_0', + skinIndex: 'JOINTS_0', + } + + const originalNormal = geometry.getAttribute('normal') + + if (originalNormal !== undefined && !this.isNormalizedNormalAttribute(originalNormal)) { + console.warn('THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.') + + geometry.setAttribute('normal', this.createNormalizedNormalAttribute(originalNormal)) + } + + // @QUESTION Detect if .vertexColors = true? + // For every attribute create an accessor + let modifiedAttribute = null + + for (let attributeName in geometry.attributes) { + // Ignore morph target attributes, which are exported later. + if (attributeName.slice(0, 5) === 'morph') continue + + const attribute = geometry.attributes[attributeName] + attributeName = nameConversion[attributeName] || attributeName.toUpperCase() + + // Prefix all geometry attributes except the ones specifically + // listed in the spec; non-spec attributes are considered custom. + const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/ + + if (!validVertexAttributes.test(attributeName)) attributeName = '_' + attributeName + + if (cache.attributes.has(this.getUID(attribute))) { + attributes[attributeName] = cache.attributes.get(this.getUID(attribute)) + continue + } + + // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. + modifiedAttribute = null + const array = attribute.array + + if (attributeName === 'JOINTS_0' && !(array instanceof Uint16Array) && !(array instanceof Uint8Array)) { + console.warn('GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.') + modifiedAttribute = new BufferAttribute(new Uint16Array(array), attribute.itemSize, attribute.normalized) + } + + const accessor = this.processAccessor(modifiedAttribute || attribute, geometry) + + if (accessor !== null) { + if (!attributeName.startsWith('_')) { + this.detectMeshQuantization(attributeName, attribute) + } + + attributes[attributeName] = accessor + cache.attributes.set(this.getUID(attribute), accessor) + } + } + + if (originalNormal !== undefined) geometry.setAttribute('normal', originalNormal) + + // Skip if no exportable attributes found + if (Object.keys(attributes).length === 0) return null + + // Morph targets + if (mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0) { + const weights = [] + const targetNames = [] + const reverseDictionary = {} + + if (mesh.morphTargetDictionary !== undefined) { + for (const key in mesh.morphTargetDictionary) { + reverseDictionary[mesh.morphTargetDictionary[key]] = key + } + } + + for (let i = 0; i < mesh.morphTargetInfluences.length; ++i) { + const target = {} + let warned = false + + for (const attributeName in geometry.morphAttributes) { + // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. + // Three.js doesn't support TANGENT yet. + + if (attributeName !== 'position' && attributeName !== 'normal') { + if (!warned) { + console.warn('GLTFExporter: Only POSITION and NORMAL morph are supported.') + warned = true + } + + continue + } + + const attribute = geometry.morphAttributes[attributeName][i] + const gltfAttributeName = attributeName.toUpperCase() + + // Three.js morph attribute has absolute values while the one of glTF has relative values. + // + // glTF 2.0 Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets + + const baseAttribute = geometry.attributes[attributeName] + + if (cache.attributes.has(this.getUID(attribute, true))) { + target[gltfAttributeName] = cache.attributes.get(this.getUID(attribute, true)) + continue + } + + // Clones attribute not to override + const relativeAttribute = attribute.clone() + + if (!geometry.morphTargetsRelative) { + for (let j = 0, jl = attribute.count; j < jl; j++) { + for (let a = 0; a < attribute.itemSize; a++) { + if (a === 0) relativeAttribute.setX(j, attribute.getX(j) - baseAttribute.getX(j)) + if (a === 1) relativeAttribute.setY(j, attribute.getY(j) - baseAttribute.getY(j)) + if (a === 2) relativeAttribute.setZ(j, attribute.getZ(j) - baseAttribute.getZ(j)) + if (a === 3) relativeAttribute.setW(j, attribute.getW(j) - baseAttribute.getW(j)) + } + } + } + + target[gltfAttributeName] = this.processAccessor(relativeAttribute, geometry) + cache.attributes.set(this.getUID(baseAttribute, true), target[gltfAttributeName]) + } + + targets.push(target) + + weights.push(mesh.morphTargetInfluences[i]) + + if (mesh.morphTargetDictionary !== undefined) targetNames.push(reverseDictionary[i]) + } + + meshDef.weights = weights + + if (targetNames.length > 0) { + meshDef.extras = {} + meshDef.extras.targetNames = targetNames + } + } + + const isMultiMaterial = Array.isArray(mesh.material) + + if (isMultiMaterial && geometry.groups.length === 0) return null + + const materials = isMultiMaterial ? mesh.material : [mesh.material] + const groups = isMultiMaterial ? geometry.groups : [{ materialIndex: 0, start: undefined, count: undefined }] + + for (let i = 0, il = groups.length; i < il; i++) { + const primitive = { + mode: mode, + attributes: attributes, + } + + this.serializeUserData(geometry, primitive) + + if (targets.length > 0) primitive.targets = targets + + if (geometry.index !== null) { + let cacheKey = this.getUID(geometry.index) + + if (groups[i].start !== undefined || groups[i].count !== undefined) { + cacheKey += ':' + groups[i].start + ':' + groups[i].count + } + + if (cache.attributes.has(cacheKey)) { + primitive.indices = cache.attributes.get(cacheKey) + } else { + primitive.indices = this.processAccessor(geometry.index, geometry, groups[i].start, groups[i].count) + cache.attributes.set(cacheKey, primitive.indices) + } + + if (primitive.indices === null) delete primitive.indices + } + + const material = this.processMaterial(materials[groups[i].materialIndex]) + + if (material !== null) primitive.material = material + + primitives.push(primitive) + } + + meshDef.primitives = primitives + + if (!json.meshes) json.meshes = [] + + this._invokeAll(function (ext) { + ext.writeMesh && ext.writeMesh(mesh, meshDef) + }) + + const index = json.meshes.push(meshDef) - 1 + cache.meshes.set(meshCacheKey, index) + return index + } + + /** + * If a vertex attribute with a + * [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) + * is used, it is checked whether it is a valid data type according to the + * [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md) + * extension. + * In this case the extension is automatically added to the list of used extensions. + * + * @param {string} attributeName + * @param {THREE.BufferAttribute} attribute + */ + detectMeshQuantization(attributeName, attribute) { + if (this.extensionsUsed[KHR_MESH_QUANTIZATION]) return + + let attrType = undefined + + switch (attribute.array.constructor) { + case Int8Array: + attrType = 'byte' + + break + + case Uint8Array: + attrType = 'unsigned byte' + + break + + case Int16Array: + attrType = 'short' + + break + + case Uint16Array: + attrType = 'unsigned short' + + break + + default: + return + } + + if (attribute.normalized) attrType += ' normalized' + + const attrNamePrefix = attributeName.split('_', 1)[0] + + if ( + KHR_mesh_quantization_ExtraAttrTypes[attrNamePrefix] && + KHR_mesh_quantization_ExtraAttrTypes[attrNamePrefix].includes(attrType) + ) { + this.extensionsUsed[KHR_MESH_QUANTIZATION] = true + this.extensionsRequired[KHR_MESH_QUANTIZATION] = true + } + } + + /** + * Process camera + * @param {THREE.Camera} camera Camera to process + * @return {Integer} Index of the processed mesh in the "camera" array + */ + processCamera(camera) { + const json = this.json + + if (!json.cameras) json.cameras = [] + + const isOrtho = camera.isOrthographicCamera + + const cameraDef = { + type: isOrtho ? 'orthographic' : 'perspective', + } + + if (isOrtho) { + cameraDef.orthographic = { + xmag: camera.right * 2, + ymag: camera.top * 2, + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near, + } + } else { + cameraDef.perspective = { + aspectRatio: camera.aspect, + yfov: MathUtils.degToRad(camera.fov), + zfar: camera.far <= 0 ? 0.001 : camera.far, + znear: camera.near < 0 ? 0 : camera.near, + } + } + + // Question: Is saving "type" as name intentional? + if (camera.name !== '') cameraDef.name = camera.type + + return json.cameras.push(cameraDef) - 1 + } + + /** + * Creates glTF animation entry from AnimationClip object. + * + * Status: + * - Only properties listed in PATH_PROPERTIES may be animated. + * + * @param {THREE.AnimationClip} clip + * @param {THREE.Object3D} root + * @return {number|null} + */ + processAnimation(clip, root) { + const json = this.json + const nodeMap = this.nodeMap + + if (!json.animations) json.animations = [] + + clip = GLTFExporter.Utils.mergeMorphTargetTracks(clip.clone(), root) + + const tracks = clip.tracks + const channels = [] + const samplers = [] + + for (let i = 0; i < tracks.length; ++i) { + const track = tracks[i] + const trackBinding = PropertyBinding.parseTrackName(track.name) + let trackNode = PropertyBinding.findNode(root, trackBinding.nodeName) + const trackProperty = PATH_PROPERTIES[trackBinding.propertyName] + + if (trackBinding.objectName === 'bones') { + if (trackNode.isSkinnedMesh === true) { + trackNode = trackNode.skeleton.getBoneByName(trackBinding.objectIndex) + } else { + trackNode = undefined + } + } + + if (!trackNode || !trackProperty) { + console.warn('THREE.GLTFExporter: Could not export animation track "%s".', track.name) + return null + } + + const inputItemSize = 1 + let outputItemSize = track.values.length / track.times.length + + if (trackProperty === PATH_PROPERTIES.morphTargetInfluences) { + outputItemSize /= trackNode.morphTargetInfluences.length + } + + let interpolation + + // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE + + // Detecting glTF cubic spline interpolant by checking factory method's special property + // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return + // valid value from .getInterpolation(). + if (track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true) { + interpolation = 'CUBICSPLINE' + + // itemSize of CUBICSPLINE keyframe is 9 + // (VEC3 * 3: inTangent, splineVertex, and outTangent) + // but needs to be stored as VEC3 so dividing by 3 here. + outputItemSize /= 3 + } else if (track.getInterpolation() === InterpolateDiscrete) { + interpolation = 'STEP' + } else { + interpolation = 'LINEAR' + } + + samplers.push({ + input: this.processAccessor(new BufferAttribute(track.times, inputItemSize)), + output: this.processAccessor(new BufferAttribute(track.values, outputItemSize)), + interpolation: interpolation, + }) + + channels.push({ + sampler: samplers.length - 1, + target: { + node: nodeMap.get(trackNode), + path: trackProperty, + }, + }) + } + + json.animations.push({ + name: clip.name || 'clip_' + json.animations.length, + samplers: samplers, + channels: channels, + }) + + return json.animations.length - 1 + } + + /** + * @param {THREE.Object3D} object + * @return {number|null} + */ + processSkin(object) { + const json = this.json + const nodeMap = this.nodeMap + + const node = json.nodes[nodeMap.get(object)] + + const skeleton = object.skeleton + + if (skeleton === undefined) return null + + const rootJoint = object.skeleton.bones[0] + + if (rootJoint === undefined) return null + + const joints = [] + const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16) + const temporaryBoneInverse = new Matrix4() + + for (let i = 0; i < skeleton.bones.length; ++i) { + joints.push(nodeMap.get(skeleton.bones[i])) + temporaryBoneInverse.copy(skeleton.boneInverses[i]) + temporaryBoneInverse.multiply(object.bindMatrix).toArray(inverseBindMatrices, i * 16) + } + + if (json.skins === undefined) json.skins = [] + + json.skins.push({ + inverseBindMatrices: this.processAccessor(new BufferAttribute(inverseBindMatrices, 16)), + joints: joints, + skeleton: nodeMap.get(rootJoint), + }) + + const skinIndex = (node.skin = json.skins.length - 1) + + return skinIndex + } + + /** + * Process Object3D node + * @param {THREE.Object3D} node Object3D to processNode + * @return {Integer} Index of the node in the nodes list + */ + processNode(object) { + const json = this.json + const options = this.options + const nodeMap = this.nodeMap + + if (!json.nodes) json.nodes = [] + + const nodeDef = {} + + if (options.trs) { + const rotation = object.quaternion.toArray() + const position = object.position.toArray() + const scale = object.scale.toArray() + + if (!equalArray(rotation, [0, 0, 0, 1])) { + nodeDef.rotation = rotation + } + + if (!equalArray(position, [0, 0, 0])) { + nodeDef.translation = position + } + + if (!equalArray(scale, [1, 1, 1])) { + nodeDef.scale = scale + } + } else { + if (object.matrixAutoUpdate) { + object.updateMatrix() + } + + if (isIdentityMatrix(object.matrix) === false) { + nodeDef.matrix = object.matrix.elements + } + } + + // We don't export empty strings name because it represents no-name in Three.js. + if (object.name !== '') nodeDef.name = String(object.name) + + this.serializeUserData(object, nodeDef) + + if (object.isMesh || object.isLine || object.isPoints) { + const meshIndex = this.processMesh(object) + + if (meshIndex !== null) nodeDef.mesh = meshIndex + } else if (object.isCamera) { + nodeDef.camera = this.processCamera(object) + } + + if (object.isSkinnedMesh) this.skins.push(object) + + if (object.children.length > 0) { + const children = [] + + for (let i = 0, l = object.children.length; i < l; i++) { + const child = object.children[i] + + if (child.visible || options.onlyVisible === false) { + const nodeIndex = this.processNode(child) + + if (nodeIndex !== null) children.push(nodeIndex) + } + } + + if (children.length > 0) nodeDef.children = children + } + + this._invokeAll(function (ext) { + ext.writeNode && ext.writeNode(object, nodeDef) + }) + + const nodeIndex = json.nodes.push(nodeDef) - 1 + nodeMap.set(object, nodeIndex) + return nodeIndex + } + + /** + * Process Scene + * @param {Scene} node Scene to process + */ + processScene(scene) { + const json = this.json + const options = this.options + + if (!json.scenes) { + json.scenes = [] + json.scene = 0 + } + + const sceneDef = {} + + if (scene.name !== '') sceneDef.name = scene.name + + json.scenes.push(sceneDef) + + const nodes = [] + + for (let i = 0, l = scene.children.length; i < l; i++) { + const child = scene.children[i] + + if (child.visible || options.onlyVisible === false) { + const nodeIndex = this.processNode(child) + + if (nodeIndex !== null) nodes.push(nodeIndex) + } + } + + if (nodes.length > 0) sceneDef.nodes = nodes + + this.serializeUserData(scene, sceneDef) + } + + /** + * Creates a Scene to hold a list of objects and parse it + * @param {Array} objects List of objects to process + */ + processObjects(objects) { + const scene = new Scene() + scene.name = 'AuxScene' + + for (let i = 0; i < objects.length; i++) { + // We push directly to children instead of calling `add` to prevent + // modify the .parent and break its original scene and hierarchy + scene.children.push(objects[i]) + } + + this.processScene(scene) + } + + /** + * @param {THREE.Object3D|Array} input + */ + processInput(input) { + const options = this.options + + input = input instanceof Array ? input : [input] + + this._invokeAll(function (ext) { + ext.beforeParse && ext.beforeParse(input) + }) + + const objectsWithoutScene = [] + + for (let i = 0; i < input.length; i++) { + if (input[i] instanceof Scene) { + this.processScene(input[i]) + } else { + objectsWithoutScene.push(input[i]) + } + } + + if (objectsWithoutScene.length > 0) this.processObjects(objectsWithoutScene) + + for (let i = 0; i < this.skins.length; ++i) { + this.processSkin(this.skins[i]) + } + + for (let i = 0; i < options.animations.length; ++i) { + this.processAnimation(options.animations[i], input[0]) + } + + this._invokeAll(function (ext) { + ext.afterParse && ext.afterParse(input) + }) + } + + _invokeAll(func) { + for (let i = 0, il = this.plugins.length; i < il; i++) { + func(this.plugins[i]) + } + } +} + +/** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ +class GLTFLightExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_lights_punctual' + } + + writeNode(light, nodeDef) { + if (!light.isLight) return + + if (!light.isDirectionalLight && !light.isPointLight && !light.isSpotLight) { + console.warn('THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light) + return + } + + const writer = this.writer + const json = writer.json + const extensionsUsed = writer.extensionsUsed + + const lightDef = {} + + if (light.name) lightDef.name = light.name + + lightDef.color = light.color.toArray() + + lightDef.intensity = light.intensity + + if (light.isDirectionalLight) { + lightDef.type = 'directional' + } else if (light.isPointLight) { + lightDef.type = 'point' + + if (light.distance > 0) lightDef.range = light.distance + } else if (light.isSpotLight) { + lightDef.type = 'spot' + + if (light.distance > 0) lightDef.range = light.distance + + lightDef.spot = {} + lightDef.spot.innerConeAngle = (light.penumbra - 1.0) * light.angle * -1.0 + lightDef.spot.outerConeAngle = light.angle + } + + if (light.decay !== undefined && light.decay !== 2) { + console.warn( + 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.', + ) + } + + if ( + light.target && + (light.target.parent !== light || + light.target.position.x !== 0 || + light.target.position.y !== 0 || + light.target.position.z !== -1) + ) { + console.warn( + 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + + 'make light.target a child of the light with position 0,0,-1.', + ) + } + + if (!extensionsUsed[this.name]) { + json.extensions = json.extensions || {} + json.extensions[this.name] = { lights: [] } + extensionsUsed[this.name] = true + } + + const lights = json.extensions[this.name].lights + lights.push(lightDef) + + nodeDef.extensions = nodeDef.extensions || {} + nodeDef.extensions[this.name] = { light: lights.length - 1 } + } +} + +/** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ +class GLTFMaterialsUnlitExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_unlit' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshBasicMaterial) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = {} + + extensionsUsed[this.name] = true + + materialDef.pbrMetallicRoughness.metallicFactor = 0.0 + materialDef.pbrMetallicRoughness.roughnessFactor = 0.9 + } +} + +/** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ +class GLTFMaterialsClearcoatExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_clearcoat' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshPhysicalMaterial || material.clearcoat === 0) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + extensionDef.clearcoatFactor = material.clearcoat + + if (material.clearcoatMap) { + const clearcoatMapDef = { + index: writer.processTexture(material.clearcoatMap), + texCoord: material.clearcoatMap.channel, + } + writer.applyTextureTransform(clearcoatMapDef, material.clearcoatMap) + extensionDef.clearcoatTexture = clearcoatMapDef + } + + extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness + + if (material.clearcoatRoughnessMap) { + const clearcoatRoughnessMapDef = { + index: writer.processTexture(material.clearcoatRoughnessMap), + texCoord: material.clearcoatRoughnessMap.channel, + } + writer.applyTextureTransform(clearcoatRoughnessMapDef, material.clearcoatRoughnessMap) + extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef + } + + if (material.clearcoatNormalMap) { + const clearcoatNormalMapDef = { + index: writer.processTexture(material.clearcoatNormalMap), + texCoord: material.clearcoatNormalMap.channel, + } + writer.applyTextureTransform(clearcoatNormalMapDef, material.clearcoatNormalMap) + extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef + } + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Iridescence Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence + */ +class GLTFMaterialsIridescenceExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_iridescence' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshPhysicalMaterial || material.iridescence === 0) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + extensionDef.iridescenceFactor = material.iridescence + + if (material.iridescenceMap) { + const iridescenceMapDef = { + index: writer.processTexture(material.iridescenceMap), + texCoord: material.iridescenceMap.channel, + } + writer.applyTextureTransform(iridescenceMapDef, material.iridescenceMap) + extensionDef.iridescenceTexture = iridescenceMapDef + } + + extensionDef.iridescenceIor = material.iridescenceIOR + extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[0] + extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[1] + + if (material.iridescenceThicknessMap) { + const iridescenceThicknessMapDef = { + index: writer.processTexture(material.iridescenceThicknessMap), + texCoord: material.iridescenceThicknessMap.channel, + } + writer.applyTextureTransform(iridescenceThicknessMapDef, material.iridescenceThicknessMap) + extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef + } + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + */ +class GLTFMaterialsTransmissionExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_transmission' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshPhysicalMaterial || material.transmission === 0) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + extensionDef.transmissionFactor = material.transmission + + if (material.transmissionMap) { + const transmissionMapDef = { + index: writer.processTexture(material.transmissionMap), + texCoord: material.transmissionMap.channel, + } + writer.applyTextureTransform(transmissionMapDef, material.transmissionMap) + extensionDef.transmissionTexture = transmissionMapDef + } + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Materials Volume Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + */ +class GLTFMaterialsVolumeExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_volume' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshPhysicalMaterial || material.transmission === 0) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + extensionDef.thicknessFactor = material.thickness + + if (material.thicknessMap) { + const thicknessMapDef = { + index: writer.processTexture(material.thicknessMap), + texCoord: material.thicknessMap.channel, + } + writer.applyTextureTransform(thicknessMapDef, material.thicknessMap) + extensionDef.thicknessTexture = thicknessMapDef + } + + extensionDef.attenuationDistance = material.attenuationDistance + extensionDef.attenuationColor = material.attenuationColor.toArray() + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Materials ior Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + */ +class GLTFMaterialsIorExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_ior' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshPhysicalMaterial || material.ior === 1.5) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + extensionDef.ior = material.ior + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Materials specular Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + */ +class GLTFMaterialsSpecularExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_specular' + } + + writeMaterial(material, materialDef) { + if ( + !material.isMeshPhysicalMaterial || + (material.specularIntensity === 1.0 && + material.specularColor.equals(DEFAULT_SPECULAR_COLOR) && + !material.specularIntensityMap && + !material.specularColorTexture) + ) + return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + if (material.specularIntensityMap) { + const specularIntensityMapDef = { + index: writer.processTexture(material.specularIntensityMap), + texCoord: material.specularIntensityMap.channel, + } + writer.applyTextureTransform(specularIntensityMapDef, material.specularIntensityMap) + extensionDef.specularTexture = specularIntensityMapDef + } + + if (material.specularColorMap) { + const specularColorMapDef = { + index: writer.processTexture(material.specularColorMap), + texCoord: material.specularColorMap.channel, + } + writer.applyTextureTransform(specularColorMapDef, material.specularColorMap) + extensionDef.specularColorTexture = specularColorMapDef + } + + extensionDef.specularFactor = material.specularIntensity + extensionDef.specularColorFactor = material.specularColor.toArray() + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Sheen Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + */ +class GLTFMaterialsSheenExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_sheen' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshPhysicalMaterial || material.sheen == 0.0) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + if (material.sheenRoughnessMap) { + const sheenRoughnessMapDef = { + index: writer.processTexture(material.sheenRoughnessMap), + texCoord: material.sheenRoughnessMap.channel, + } + writer.applyTextureTransform(sheenRoughnessMapDef, material.sheenRoughnessMap) + extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef + } + + if (material.sheenColorMap) { + const sheenColorMapDef = { + index: writer.processTexture(material.sheenColorMap), + texCoord: material.sheenColorMap.channel, + } + writer.applyTextureTransform(sheenColorMapDef, material.sheenColorMap) + extensionDef.sheenColorTexture = sheenColorMapDef + } + + extensionDef.sheenRoughnessFactor = material.sheenRoughness + extensionDef.sheenColorFactor = material.sheenColor.toArray() + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Anisotropy Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy + */ +class GLTFMaterialsAnisotropyExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_anisotropy' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshPhysicalMaterial || material.anisotropy == 0.0) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + if (material.anisotropyMap) { + const anisotropyMapDef = { index: writer.processTexture(material.anisotropyMap) } + writer.applyTextureTransform(anisotropyMapDef, material.anisotropyMap) + extensionDef.anisotropyTexture = anisotropyMapDef + } + + extensionDef.anisotropyStrength = material.anisotropy + extensionDef.anisotropyRotation = material.anisotropyRotation + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Materials Emissive Strength Extension + * + * Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md + */ +class GLTFMaterialsEmissiveStrengthExtension { + constructor(writer) { + this.writer = writer + this.name = 'KHR_materials_emissive_strength' + } + + writeMaterial(material, materialDef) { + if (!material.isMeshStandardMaterial || material.emissiveIntensity === 1.0) return + + const writer = this.writer + const extensionsUsed = writer.extensionsUsed + + const extensionDef = {} + + extensionDef.emissiveStrength = material.emissiveIntensity + + materialDef.extensions = materialDef.extensions || {} + materialDef.extensions[this.name] = extensionDef + + extensionsUsed[this.name] = true + } +} + +/** + * Static utility functions + */ +GLTFExporter.Utils = { + insertKeyframe: function (track, time) { + const tolerance = 0.001 // 1ms + const valueSize = track.getValueSize() + + const times = new track.TimeBufferType(track.times.length + 1) + const values = new track.ValueBufferType(track.values.length + valueSize) + const interpolant = track.createInterpolant(new track.ValueBufferType(valueSize)) + + let index + + if (track.times.length === 0) { + times[0] = time + + for (let i = 0; i < valueSize; i++) { + values[i] = 0 + } + + index = 0 + } else if (time < track.times[0]) { + if (Math.abs(track.times[0] - time) < tolerance) return 0 + + times[0] = time + times.set(track.times, 1) + + values.set(interpolant.evaluate(time), 0) + values.set(track.values, valueSize) + + index = 0 + } else if (time > track.times[track.times.length - 1]) { + if (Math.abs(track.times[track.times.length - 1] - time) < tolerance) { + return track.times.length - 1 + } + + times[times.length - 1] = time + times.set(track.times, 0) + + values.set(track.values, 0) + values.set(interpolant.evaluate(time), track.values.length) + + index = times.length - 1 + } else { + for (let i = 0; i < track.times.length; i++) { + if (Math.abs(track.times[i] - time) < tolerance) return i + + if (track.times[i] < time && track.times[i + 1] > time) { + times.set(track.times.slice(0, i + 1), 0) + times[i + 1] = time + times.set(track.times.slice(i + 1), i + 2) + + values.set(track.values.slice(0, (i + 1) * valueSize), 0) + values.set(interpolant.evaluate(time), (i + 1) * valueSize) + values.set(track.values.slice((i + 1) * valueSize), (i + 2) * valueSize) + + index = i + 1 + + break + } + } + } + + track.times = times + track.values = values + + return index + }, + + mergeMorphTargetTracks: function (clip, root) { + const tracks = [] + const mergedTracks = {} + const sourceTracks = clip.tracks + + for (let i = 0; i < sourceTracks.length; ++i) { + let sourceTrack = sourceTracks[i] + const sourceTrackBinding = PropertyBinding.parseTrackName(sourceTrack.name) + const sourceTrackNode = PropertyBinding.findNode(root, sourceTrackBinding.nodeName) + + if ( + sourceTrackBinding.propertyName !== 'morphTargetInfluences' || + sourceTrackBinding.propertyIndex === undefined + ) { + // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. + tracks.push(sourceTrack) + continue + } + + if ( + sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && + sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear + ) { + if (sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) { + // This should never happen, because glTF morph target animations + // affect all targets already. + throw new Error('THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.') + } + + console.warn('THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.') + + sourceTrack = sourceTrack.clone() + sourceTrack.setInterpolation(InterpolateLinear) + } + + const targetCount = sourceTrackNode.morphTargetInfluences.length + const targetIndex = sourceTrackNode.morphTargetDictionary[sourceTrackBinding.propertyIndex] + + if (targetIndex === undefined) { + throw new Error('THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex) + } + + let mergedTrack + + // If this is the first time we've seen this object, create a new + // track to store merged keyframe data for each morph target. + if (mergedTracks[sourceTrackNode.uuid] === undefined) { + mergedTrack = sourceTrack.clone() + + const values = new mergedTrack.ValueBufferType(targetCount * mergedTrack.times.length) + + for (let j = 0; j < mergedTrack.times.length; j++) { + values[j * targetCount + targetIndex] = mergedTrack.values[j] + } + + // We need to take into consideration the intended target node + // of our original un-merged morphTarget animation. + mergedTrack.name = (sourceTrackBinding.nodeName || '') + '.morphTargetInfluences' + mergedTrack.values = values + + mergedTracks[sourceTrackNode.uuid] = mergedTrack + tracks.push(mergedTrack) + + continue + } + + const sourceInterpolant = sourceTrack.createInterpolant(new sourceTrack.ValueBufferType(1)) + + mergedTrack = mergedTracks[sourceTrackNode.uuid] + + // For every existing keyframe of the merged track, write a (possibly + // interpolated) value from the source track. + for (let j = 0; j < mergedTrack.times.length; j++) { + mergedTrack.values[j * targetCount + targetIndex] = sourceInterpolant.evaluate(mergedTrack.times[j]) + } + + // For every existing keyframe of the source track, write a (possibly + // new) keyframe to the merged track. Values from the previous loop may + // be written again, but keyframes are de-duplicated. + for (let j = 0; j < sourceTrack.times.length; j++) { + const keyframeIndex = this.insertKeyframe(mergedTrack, sourceTrack.times[j]) + mergedTrack.values[keyframeIndex * targetCount + targetIndex] = sourceTrack.values[j] + } + } + + clip.tracks = tracks + + return clip + }, +} + +export { GLTFExporter } diff --git a/src/exporters/GLTFExporter.ts b/src/exporters/GLTFExporter.ts deleted file mode 100644 index 518f8bc1..00000000 --- a/src/exporters/GLTFExporter.ts +++ /dev/null @@ -1,2481 +0,0 @@ -import { - BufferAttribute, - ClampToEdgeWrapping, - DoubleSide, - InterpolateDiscrete, - InterpolateLinear, - LinearFilter, - LinearMipmapLinearFilter, - LinearMipmapNearestFilter, - MathUtils, - Matrix4, - MirroredRepeatWrapping, - NearestFilter, - NearestMipmapLinearFilter, - NearestMipmapNearestFilter, - PropertyBinding, - RGBAFormat, - RepeatWrapping, - Scene, - Vector3, - Object3D, - AnimationClip, - Material, - Texture, - BufferGeometry, - Mesh, - Camera, - KeyframeTrack, - Light, - Vector2Tuple, - ShaderMaterial, - MeshStandardMaterial, - MeshBasicMaterial, - MeshPhysicalMaterial, - MeshMatcapMaterial, - MeshNormalMaterial, - MeshPhongMaterial, - MeshToonMaterial, - MeshLambertMaterial, - LineSegments, - LineLoop, - Line, - Points, - MeshDepthMaterial, - InterleavedBufferAttribute, - OrthographicCamera, - PerspectiveCamera, - SkinnedMesh, - Vector3Tuple, - DirectionalLight, - PointLight, - SpotLight, -} from 'three' - -export interface GLTFExporterOptions { - binary?: boolean - trs?: boolean - onlyVisible?: boolean - truncateDrawRange?: boolean - embedImages?: boolean - animations?: AnimationClip[] - forceIndices?: boolean - forcePowerOfTwoTextures?: boolean - includeCustomExtensions?: boolean -} - -type PluginCallback = ( - writer: GLTFWriter, -) => - | GLTFLightExtension - | GLTFMaterialsUnlitExtension - | GLTFMaterialsPBRSpecularGlossiness - | GLTFMaterialsTransmissionExtension - | GLTFMaterialsVolumeExtension - -type TransformDef = { - offset?: Vector2Tuple - rotation?: number - scale?: Vector2Tuple -} - -type BufferViewDef = { - buffer: number - byteOffset: number - byteLength: number - target?: number - byteStride?: number -} - -type AccessorDef = { - bufferView: unknown - byteOffset: unknown - componentType: unknown - count: unknown - max: unknown - min: unknown - type: unknown - normalized?: boolean -} - -type ImageDef = { - mimeType: string - bufferView?: number - uri?: string -} - -type ImageRepresentation = HTMLImageElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap - -type SamplerDef = { - magFilter: typeof WEBGL_CONSTANTS[keyof typeof WEBGL_CONSTANTS] - minFilter: typeof WEBGL_CONSTANTS[keyof typeof WEBGL_CONSTANTS] - wrapS: typeof WEBGL_CONSTANTS[keyof typeof WEBGL_CONSTANTS] - wrapT: typeof WEBGL_CONSTANTS[keyof typeof WEBGL_CONSTANTS] -} - -type TextureDef = { - sampler: number - source: number - name?: string -} - -type MaterialDef = { - pbrMetallicRoughness: { - baseColorFactor?: number[] - metallicFactor?: number - roughnessFactor?: number - metallicRoughnessTexture?: { - index: number - } - baseColorTexture?: { - index: number - } - } - emissiveFactor?: number[] - emissiveTexture?: { - index: number - } - normalTexture?: { - index: number - scale?: number | undefined - } - occlusionTexture?: OcclusionMapDef - alphaMode?: string - alphaCutoff?: number - doubleSided?: boolean - name?: string - extensions?: { - [key: string]: unknown - } -} - -type OcclusionMapDef = { - index: number - texCoord: number - strength?: number -} - -type Primitive = { - mode: number - attributes: { - [key: string]: BufferAttribute | InterleavedBufferAttribute | number - } - targets?: { - [key: string]: BufferAttribute | InterleavedBufferAttribute | number - }[] - indices?: BufferAttribute | InterleavedBufferAttribute | number - material?: number -} - -type MeshDef = { - weights?: number[] - extras?: { - [key: string]: string[] - } - primitives?: Primitive[] -} - -type CameraDef = { - type: string - orthographic?: { - xmag: number - ymag: number - zfar: number - znear: number - } - perspective?: { - aspectRatio: number - yfov: number - zfar: number - znear: number - } - name?: string -} - -type NodeDef = { - rotation?: number[] - translation?: Vector3Tuple - scale?: Vector3Tuple - matrix?: number[] - name?: string - mesh?: number - camera?: number - children?: number[] - extensions?: { [key: string]: number | { light?: number } } -} - -type SceneDef = { - name?: string - nodes?: number[] -} - -type LightDef = { - name?: string - color?: number[] - intensity?: number - type?: string - spot?: { - innerConeAngle?: number - outerConeAngle?: number - } - range?: number -} - -type ExtensionDef = { - diffuseFactor?: number[] - specularFactor?: number[] - glossinessFactor?: unknown - diffuseTexture?: { index: number } - specularGlossinessTexture?: { - index: number - } - transmissionFactor?: number - transmissionTexture?: { - index: number - } - thickness?: unknown - thicknessTexture?: { - index: number - } - attenuationDistance?: number - attenuationColor?: number[] -} - -class GLTFExporter { - private pluginCallbacks: PluginCallback[] - - constructor() { - this.pluginCallbacks = [] - - this.register(function (writer: GLTFWriter) { - return new GLTFLightExtension(writer) - }) - - this.register(function (writer: GLTFWriter) { - return new GLTFMaterialsUnlitExtension(writer) - }) - - this.register(function (writer: GLTFWriter) { - return new GLTFMaterialsPBRSpecularGlossiness(writer) - }) - - this.register(function (writer: GLTFWriter) { - return new GLTFMaterialsTransmissionExtension(writer) - }) - - this.register(function (writer: GLTFWriter) { - return new GLTFMaterialsVolumeExtension(writer) - }) - } - - private register(callback: PluginCallback): this { - if (this.pluginCallbacks.indexOf(callback) === -1) { - this.pluginCallbacks.push(callback) - } - - return this - } - - public unregister(callback: PluginCallback): this { - if (this.pluginCallbacks.indexOf(callback) !== -1) { - this.pluginCallbacks.splice(this.pluginCallbacks.indexOf(callback), 1) - } - - return this - } - - public parse(input: Object3D, onDone: (gltf: object) => void, options: GLTFExporterOptions): void { - const writer = new GLTFWriter() - const plugins: ReturnType[] = [] - - for (let i = 0, il = this.pluginCallbacks.length; i < il; i++) { - plugins.push(this.pluginCallbacks[i](writer)) - } - - writer.setPlugins(plugins) - writer.write(input, onDone, options) - } - - /** - * Static utility functions - */ - public static Utils = { - insertKeyframe: function (track: KeyframeTrack, time: number): number | undefined { - const tolerance = 0.001 // 1ms - const valueSize = track.getValueSize() - - // @ts-expect-error - const times = new track.TimeBufferType(track.times.length + 1) - // @ts-expect-error - const values = new track.ValueBufferType(track.values.length + valueSize) - /** - * NOTE: createInterpolant does not exist in the type, but it does exist as a property of the class - * https://github.com/mrdoob/three.js/blob/77480d339d737b7505b335101ffd3cf29a30738d/src/animation/KeyframeTrack.js#L117 - */ - // @ts-expect-error - const interpolant = track.createInterpolant(new track.ValueBufferType(valueSize)) - - let index - - if (track.times.length === 0) { - times[0] = time - - for (let i = 0; i < valueSize; i++) { - values[i] = 0 - } - - index = 0 - } else if (time < track.times[0]) { - if (Math.abs(track.times[0] - time) < tolerance) return 0 - - times[0] = time - times.set(track.times, 1) - - values.set(interpolant.evaluate(time), 0) - values.set(track.values, valueSize) - - index = 0 - } else if (time > track.times[track.times.length - 1]) { - if (Math.abs(track.times[track.times.length - 1] - time) < tolerance) { - return track.times.length - 1 - } - - times[times.length - 1] = time - times.set(track.times, 0) - - values.set(track.values, 0) - values.set(interpolant.evaluate(time), track.values.length) - - index = times.length - 1 - } else { - for (let i = 0; i < track.times.length; i++) { - if (Math.abs(track.times[i] - time) < tolerance) return i - - if (track.times[i] < time && track.times[i + 1] > time) { - times.set(track.times.slice(0, i + 1), 0) - times[i + 1] = time - times.set(track.times.slice(i + 1), i + 2) - - values.set(track.values.slice(0, (i + 1) * valueSize), 0) - values.set(interpolant.evaluate(time), (i + 1) * valueSize) - values.set(track.values.slice((i + 1) * valueSize), (i + 2) * valueSize) - - index = i + 1 - - break - } - } - } - - track.times = times - track.values = values - - return index - }, - - mergeMorphTargetTracks: function (clip: AnimationClip, root: any): AnimationClip { - const tracks = [] - const mergedTracks: { [key: string]: KeyframeTrack } = {} - const sourceTracks = clip.tracks - - for (let i = 0; i < sourceTracks.length; ++i) { - let sourceTrack = sourceTracks[i] - const sourceTrackBinding = PropertyBinding.parseTrackName(sourceTrack.name) - const sourceTrackNode = PropertyBinding.findNode(root, sourceTrackBinding.nodeName) - - if ( - sourceTrackBinding.propertyName !== 'morphTargetInfluences' || - sourceTrackBinding.propertyIndex === undefined - ) { - // Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is. - tracks.push(sourceTrack) - continue - } - - if ( - // @ts-expect-error - sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete && - // @ts-expect-error - sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear - ) { - // @ts-expect-error - if (sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) { - // This should never happen, because glTF morph target animations - // affect all targets already. - throw new Error('THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.') - } - - console.warn('THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.') - - sourceTrack = sourceTrack.clone() - sourceTrack.setInterpolation(InterpolateLinear) - } - - const targetCount = sourceTrackNode.morphTargetInfluences.length - const targetIndex = sourceTrackNode.morphTargetDictionary[sourceTrackBinding.propertyIndex] - - if (targetIndex === undefined) { - throw new Error('THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex) - } - - let mergedTrack - - // If this is the first time we've seen this object, create a new - // track to store merged keyframe data for each morph target. - if (mergedTracks[sourceTrackNode.uuid] === undefined) { - mergedTrack = sourceTrack.clone() - - // @ts-expect-error - const values = new mergedTrack.ValueBufferType(targetCount * mergedTrack.times.length) - - for (let j = 0; j < mergedTrack.times.length; j++) { - values[j * targetCount + targetIndex] = mergedTrack.values[j] - } - - // We need to take into consideration the intended target node - // of our original un-merged morphTarget animation. - mergedTrack.name = (sourceTrackBinding.nodeName || '') + '.morphTargetInfluences' - mergedTrack.values = values - - mergedTracks[sourceTrackNode.uuid] = mergedTrack - tracks.push(mergedTrack) - - continue - } - - // @ts-expect-error - const sourceInterpolant = sourceTrack.createInterpolant(new sourceTrack.ValueBufferType(1)) - - mergedTrack = mergedTracks[sourceTrackNode.uuid] - - // For every existing keyframe of the merged track, write a (possibly - // interpolated) value from the source track. - for (let j = 0; j < mergedTrack.times.length; j++) { - mergedTrack.values[j * targetCount + targetIndex] = sourceInterpolant.evaluate(mergedTrack.times[j]) - } - - // For every existing keyframe of the source track, write a (possibly - // new) keyframe to the merged track. Values from the previous loop may - // be written again, but keyframes are de-duplicated. - for (let j = 0; j < sourceTrack.times.length; j++) { - const keyframeIndex = this.insertKeyframe(mergedTrack, sourceTrack.times[j])! - mergedTrack.values[keyframeIndex * targetCount + targetIndex] = sourceTrack.values[j] - } - } - - clip.tracks = tracks - - return clip - }, - } -} - -//------------------------------------------------------------------------------ -// Constants -//------------------------------------------------------------------------------ - -const WEBGL_CONSTANTS = { - POINTS: 0x0000, - LINES: 0x0001, - LINE_LOOP: 0x0002, - LINE_STRIP: 0x0003, - TRIANGLES: 0x0004, - TRIANGLE_STRIP: 0x0005, - TRIANGLE_FAN: 0x0006, - - UNSIGNED_BYTE: 0x1401, - UNSIGNED_SHORT: 0x1403, - FLOAT: 0x1406, - UNSIGNED_INT: 0x1405, - ARRAY_BUFFER: 0x8892, - ELEMENT_ARRAY_BUFFER: 0x8893, - - NEAREST: 0x2600, - LINEAR: 0x2601, - NEAREST_MIPMAP_NEAREST: 0x2700, - LINEAR_MIPMAP_NEAREST: 0x2701, - NEAREST_MIPMAP_LINEAR: 0x2702, - LINEAR_MIPMAP_LINEAR: 0x2703, - - CLAMP_TO_EDGE: 33071, - MIRRORED_REPEAT: 33648, - REPEAT: 10497, -} as const - -const THREE_TO_WEBGL: { [key: number]: typeof WEBGL_CONSTANTS[keyof typeof WEBGL_CONSTANTS] } = {} - -THREE_TO_WEBGL[NearestFilter] = WEBGL_CONSTANTS.NEAREST -THREE_TO_WEBGL[NearestMipmapNearestFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST -THREE_TO_WEBGL[NearestMipmapLinearFilter] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR -THREE_TO_WEBGL[LinearFilter] = WEBGL_CONSTANTS.LINEAR -THREE_TO_WEBGL[LinearMipmapNearestFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST -THREE_TO_WEBGL[LinearMipmapLinearFilter] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR - -THREE_TO_WEBGL[ClampToEdgeWrapping] = WEBGL_CONSTANTS.CLAMP_TO_EDGE -THREE_TO_WEBGL[RepeatWrapping] = WEBGL_CONSTANTS.REPEAT -THREE_TO_WEBGL[MirroredRepeatWrapping] = WEBGL_CONSTANTS.MIRRORED_REPEAT - -const PATH_PROPERTIES = { - scale: 'scale', - position: 'translation', - quaternion: 'rotation', - morphTargetInfluences: 'weights', -} - -// GLB constants -// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - -const GLB_HEADER_BYTES = 12 -const GLB_HEADER_MAGIC = 0x46546c67 -const GLB_VERSION = 2 - -const GLB_CHUNK_PREFIX_BYTES = 8 -const GLB_CHUNK_TYPE_JSON = 0x4e4f534a -const GLB_CHUNK_TYPE_BIN = 0x004e4942 - -/** - * Writer - */ -class GLTFWriter { - private plugins: ReturnType[] - - private options: { - binary?: boolean - trs?: boolean - onlyVisible?: boolean - truncateDrawRange?: boolean - embedImages?: boolean - maxTextureSize?: number - animations?: AnimationClip[] - includeCustomExtensions?: boolean - } & GLTFExporterOptions - private pending: Promise[] - private buffers: ArrayBuffer[] - - private byteOffset: number - private nodeMap: Map - private skins: Object3D[] - public extensionsUsed: { - [key: string]: boolean - } - - private uids: Map<{ [key: string]: any }, number> - private uid: number - - public json: { - asset: { - version: string - generator: string - } - buffers?: { - uri?: ArrayBuffer | string - byteLength: number - }[] - extensionsUsed?: string[] - bufferViews?: BufferViewDef[] - images?: ImageRepresentation[] & ImageDef[] - accessors?: AccessorDef[] - samplers?: SamplerDef[] - textures?: Texture[] & TextureDef[] - materials?: Material[] & MaterialDef[] - meshes?: unknown[] - cameras?: (Camera | CameraDef)[] - animations?: unknown[] - nodes?: { - [key: string]: unknown - }[] - skins?: {}[] - scenes?: (Scene | SceneDef)[] - scene?: number - extensions?: { - [key: string]: { - lights: unknown[] - } - } - } - - private cache: { - meshes: Map - attributes: Map - attributesNormalized: Map - materials: Map - textures: Map - images: Map - } - - private cachedCanvas: HTMLCanvasElement | null - - constructor() { - this.plugins = [] - - this.options = {} - this.pending = [] - this.buffers = [] - - this.byteOffset = 0 - this.nodeMap = new Map() - this.skins = [] - this.extensionsUsed = {} - - this.uids = new Map() - this.uid = 0 - - this.json = { - asset: { - version: '2.0', - generator: 'THREE.GLTFExporter', - }, - } - - this.cache = { - meshes: new Map(), - attributes: new Map(), - attributesNormalized: new Map(), - materials: new Map(), - textures: new Map(), - images: new Map(), - } - - this.cachedCanvas = null - } - - public setPlugins(plugins: ReturnType[]): void { - this.plugins = plugins - } - - /** - * Parse scenes and generate GLTF output - * @param {Scene or [THREE.Scenes]} input Scene or Array of THREE.Scenes - * @param {Function} onDone Callback on completed - * @param {Object} options options - */ - public write(input: Object3D, onDone: (gltf: object) => void, options: GLTFExporterOptions): void { - this.options = Object.assign( - {}, - { - // default options - binary: false, - trs: false, - onlyVisible: true, - truncateDrawRange: true, - embedImages: true, - maxTextureSize: Infinity, - animations: [], - includeCustomExtensions: false, - }, - options, - ) - - if (this.options.animations !== undefined && this.options.animations.length > 0) { - // Only TRS properties, and not matrices, may be targeted by animation. - this.options.trs = true - } - - this.processInput(input) - - const writer = this - - Promise.all(this.pending).then(() => { - const buffers = writer.buffers - const json = writer.json - const options = writer.options - const extensionsUsed = writer.extensionsUsed - - // Merge buffers. - const blob = new Blob(buffers, { type: 'application/octet-stream' }) - - // Declare extensions. - const extensionsUsedList = Object.keys(extensionsUsed) - - if (extensionsUsedList.length > 0) json.extensionsUsed = extensionsUsedList - - // Update bytelength of the single buffer. - if (json.buffers && json.buffers.length > 0) json.buffers[0].byteLength = blob.size - - if (options.binary) { - // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification - - const reader = new window.FileReader() - reader.readAsArrayBuffer(blob) - reader.onloadend = (): void => { - if (reader.result !== null && typeof reader.result !== 'string') { - // Binary chunk. - const binaryChunk = this.getPaddedArrayBuffer(reader.result) - const binaryChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES)) - binaryChunkPrefix.setUint32(0, binaryChunk.byteLength, true) - binaryChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_BIN, true) - - // JSON chunk. - const jsonChunk = this.getPaddedArrayBuffer(this.stringToArrayBuffer(JSON.stringify(json)), 0x20) - const jsonChunkPrefix = new DataView(new ArrayBuffer(GLB_CHUNK_PREFIX_BYTES)) - jsonChunkPrefix.setUint32(0, jsonChunk.byteLength, true) - jsonChunkPrefix.setUint32(4, GLB_CHUNK_TYPE_JSON, true) - - // GLB header. - const header = new ArrayBuffer(GLB_HEADER_BYTES) - const headerView = new DataView(header) - headerView.setUint32(0, GLB_HEADER_MAGIC, true) - headerView.setUint32(4, GLB_VERSION, true) - const totalByteLength = - GLB_HEADER_BYTES + - jsonChunkPrefix.byteLength + - jsonChunk.byteLength + - binaryChunkPrefix.byteLength + - binaryChunk.byteLength - headerView.setUint32(8, totalByteLength, true) - - const glbBlob = new Blob([header, jsonChunkPrefix, jsonChunk, binaryChunkPrefix, binaryChunk], { - type: 'application/octet-stream', - }) - - const glbReader = new window.FileReader() - glbReader.readAsArrayBuffer(glbBlob) - glbReader.onloadend = function (): void { - if (glbReader.result !== null && typeof glbReader.result !== 'string') { - onDone(glbReader.result) - } - } - } - } - } else { - if (json.buffers && json.buffers.length > 0) { - const reader = new window.FileReader() - reader.readAsDataURL(blob) - reader.onloadend = function (): void { - const base64data = reader.result - if (json.buffers !== undefined && base64data !== null) { - json.buffers[0].uri = base64data - onDone(json) - } - } - } else { - onDone(json) - } - } - }) - } - - /** - * Serializes a userData. - * - * @param {THREE.Object3D|THREE.Material} object - * @param {Object} objectDef - */ - private serializeUserData(object: Object3D | Material, objectDef: { [key: string]: any }): void { - if (Object.keys(object.userData).length === 0) return - - const options = this.options - const extensionsUsed = this.extensionsUsed - - try { - const json = JSON.parse(JSON.stringify(object.userData)) - - if (options.includeCustomExtensions && json.gltfExtensions) { - if (objectDef.extensions === undefined) objectDef.extensions = {} - - for (const extensionName in json.gltfExtensions) { - objectDef.extensions[extensionName] = json.gltfExtensions[extensionName] - extensionsUsed[extensionName] = true - } - - delete json.gltfExtensions - } - - if (Object.keys(json).length > 0) objectDef.extras = json - } catch (error) { - if (error instanceof Error) { - console.warn( - "THREE.GLTFExporter: userData of '" + - object.name + - "' " + - "won't be serialized because of JSON.stringify error - " + - error.message, - ) - } - } - } - - /** - * Assign and return a temporal unique id for an object - * especially which doesn't have .uuid - * @param {Object} object - * @return {Integer} - */ - private getUID(object: { [key: string]: any }): number { - if (!this.uids.has(object)) this.uids.set(object, this.uid++) - - return this.uids.get(object)! - } - - /** - * Checks if normal attribute values are normalized. - * - * @param {BufferAttribute} normal - * @returns {Boolean} - */ - private isNormalizedNormalAttribute(normal: BufferAttribute): boolean { - const cache = this.cache - - if (cache.attributesNormalized.has(normal)) return false - - const v = new Vector3() - - for (let i = 0, il = normal.count; i < il; i++) { - // 0.0005 is from glTF-validator - if (Math.abs(v.fromBufferAttribute(normal, i).length() - 1.0) > 0.0005) return false - } - - return true - } - - /** - * Creates normalized normal buffer attribute. - * - * @param {BufferAttribute} normal - * @returns {BufferAttribute} - * - */ - private createNormalizedNormalAttribute(normal: BufferAttribute): BufferAttribute { - const cache = this.cache - - if (cache.attributesNormalized.has(normal)) return cache.attributesNormalized.get(normal)! - - const attribute = normal.clone() - const v = new Vector3() - - for (let i = 0, il = attribute.count; i < il; i++) { - v.fromBufferAttribute(attribute, i) - - if (v.x === 0 && v.y === 0 && v.z === 0) { - // if values can't be normalized set (1, 0, 0) - v.setX(1.0) - } else { - v.normalize() - } - - attribute.setXYZ(i, v.x, v.y, v.z) - } - - cache.attributesNormalized.set(normal, attribute) - - return attribute - } - - /** - * Applies a texture transform, if present, to the map definition. Requires - * the KHR_texture_transform extension. - * - * @param {Object} mapDef - * @param {THREE.Texture} texture - */ - public applyTextureTransform( - mapDef: { - extensions?: { - [key: string]: TransformDef - } - index?: number - }, - texture: Texture, - ): void { - let didTransform = false - const transformDef: TransformDef = {} - - if (texture.offset.x !== 0 || texture.offset.y !== 0) { - transformDef.offset = texture.offset.toArray() - didTransform = true - } - - if (texture.rotation !== 0) { - transformDef.rotation = texture.rotation - didTransform = true - } - - if (texture.repeat.x !== 1 || texture.repeat.y !== 1) { - transformDef.scale = texture.repeat.toArray() - didTransform = true - } - - if (didTransform) { - mapDef.extensions = mapDef.extensions || {} - mapDef.extensions['KHR_texture_transform'] = transformDef - this.extensionsUsed['KHR_texture_transform'] = true - } - } - - /** - * Process a buffer to append to the default one. - * @param {ArrayBuffer} buffer - * @return {Integer} - */ - public processBuffer(buffer: ArrayBuffer): number { - const json = this.json - const buffers = this.buffers - - if (!json.buffers) json.buffers = [{ byteLength: 0 }] - - // All buffers are merged before export. - buffers.push(buffer) - - return 0 - } - - /** - * Process and generate a BufferView - * @param {BufferAttribute} attribute - * @param {number} componentType - * @param {number} start - * @param {number} count - * @param {number} target (Optional) Target usage of the BufferView - * @return {Object} - */ - private processBufferView( - attribute: BufferAttribute, - componentType: number, - start: number, - count: number, - target: number, - ): { - id: number - byteLength: number - byteOffset?: number - } { - const json = this.json - - if (!json.bufferViews) json.bufferViews = [] - - // Create a new dataview and dump the attribute's array into it - - let componentSize - - if (componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE) { - componentSize = 1 - } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT) { - componentSize = 2 - } else { - componentSize = 4 - } - - const byteLength = this.getPaddedBufferSize(count * attribute.itemSize * componentSize) - const dataView = new DataView(new ArrayBuffer(byteLength)) - let offset = 0 - - for (let i = start; i < start + count; i++) { - for (let a = 0; a < attribute.itemSize; a++) { - let value - - if (attribute.itemSize > 4) { - // no support for interleaved data for itemSize > 4 - - value = attribute.array[i * attribute.itemSize + a] - } else { - if (a === 0) value = attribute.getX(i) - else if (a === 1) value = attribute.getY(i) - else if (a === 2) value = attribute.getZ(i) - else if (a === 3) value = attribute.getW(i) - } - - if (value !== undefined) { - if (componentType === WEBGL_CONSTANTS.FLOAT) { - dataView.setFloat32(offset, value, true) - } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_INT) { - dataView.setUint32(offset, value, true) - } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT) { - dataView.setUint16(offset, value, true) - } else if (componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE) { - dataView.setUint8(offset, value) - } - } - - offset += componentSize - } - } - - const bufferViewDef: BufferViewDef = { - buffer: this.processBuffer(dataView.buffer), - byteOffset: this.byteOffset, - byteLength: byteLength, - } - - if (target !== undefined) bufferViewDef.target = target - - if (target === WEBGL_CONSTANTS.ARRAY_BUFFER) { - // Only define byteStride for vertex attributes. - bufferViewDef.byteStride = attribute.itemSize * componentSize - } - - this.byteOffset += byteLength - - json.bufferViews.push(bufferViewDef) - - // @TODO Merge bufferViews where possible. - const output = { - id: json.bufferViews.length - 1, - byteLength: 0, - } - - return output - } - - /** - * Process and generate a BufferView from an image Blob. - * @param {Blob} blob - * @return {Promise} - */ - public processBufferViewImage(blob: Blob): Promise { - const writer = this - const json = writer.json - - if (!json.bufferViews) json.bufferViews = [] - - return new Promise((resolve) => { - const reader = new window.FileReader() - reader.readAsArrayBuffer(blob) - reader.onloadend = (): void => { - if (reader.result !== null && typeof reader.result !== 'string' && json.bufferViews !== undefined) { - const buffer = this.getPaddedArrayBuffer(reader.result) - - const bufferViewDef = { - buffer: writer.processBuffer(buffer), - byteOffset: writer.byteOffset, - byteLength: buffer.byteLength, - } - - writer.byteOffset += buffer.byteLength - resolve(json.bufferViews.push(bufferViewDef) - 1) - } - } - }) - } - - /** - * Process attribute to generate an accessor - * @param {BufferAttribute} attribute Attribute to process - * @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range - * @param {Integer} start (Optional) - * @param {Integer} count (Optional) - * @return {Integer|null} Index of the processed accessor on the "accessors" array - */ - private processAccessor( - attribute: BufferAttribute, - geometry?: BufferGeometry, - start?: number, - count?: number, - ): number | null | undefined { - const options = this.options - const json = this.json - - const types: { [key: number]: string } = { - 1: 'SCALAR', - 2: 'VEC2', - 3: 'VEC3', - 4: 'VEC4', - 16: 'MAT4', - } - - let componentType - - // Detect the component type of the attribute array (float, uint or ushort) - if (attribute.array.constructor === Float32Array) { - componentType = WEBGL_CONSTANTS.FLOAT - } else if (attribute.array.constructor === Uint32Array) { - componentType = WEBGL_CONSTANTS.UNSIGNED_INT - } else if (attribute.array.constructor === Uint16Array) { - componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT - } else if (attribute.array.constructor === Uint8Array) { - componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE - } else { - throw new Error('THREE.GLTFExporter: Unsupported bufferAttribute component type.') - } - - if (start === undefined) start = 0 - if (count === undefined) count = attribute.count - - // @TODO Indexed buffer geometry with drawRange not supported yet - if (options.truncateDrawRange && geometry !== undefined && geometry.index === null) { - const end = start + count - const end2 = - geometry.drawRange.count === Infinity ? attribute.count : geometry.drawRange.start + geometry.drawRange.count - - start = Math.max(start, geometry.drawRange.start) - count = Math.min(end, end2) - start - - if (count < 0) count = 0 - } - - // Skip creating an accessor if the attribute doesn't have data to export - if (count === 0) return null - - const minMax = this.getMinMax(attribute, start, count) - let bufferViewTarget - - // If geometry isn't provided, don't infer the target usage of the bufferView. For - // animation samplers, target must not be set. - if (geometry !== undefined) { - bufferViewTarget = - attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER - } - - if (bufferViewTarget !== undefined) { - const bufferView = this.processBufferView(attribute, componentType, start, count, bufferViewTarget) - - const accessorDef: AccessorDef = { - bufferView: bufferView.id, - byteOffset: bufferView.byteOffset, - componentType: componentType, - count: count, - max: minMax.max, - min: minMax.min, - type: types[attribute.itemSize], - } - - if (attribute.normalized) accessorDef.normalized = true - if (!json.accessors) json.accessors = [] - - return json.accessors.push(accessorDef) - 1 - } - } - - /** - * Process image - * @param {Image} image to process - * @param {Integer} format of the image (RGBAFormat) - * @param {Boolean} flipY before writing out the image - * @return {Integer} Index of the processed texture in the "images" array - */ - private processImage(image: ImageRepresentation, format: number, flipY: boolean): number { - const writer = this - const cache = writer.cache - const json = writer.json - const options = writer.options - const pending = writer.pending - - if (!cache.images.has(image)) cache.images.set(image, {}) - - const cachedImages = cache.images.get(image) - const mimeType = format === RGBAFormat ? 'image/png' : 'image/jpeg' - const key = mimeType + ':flipY/' + flipY.toString() - - if (cachedImages !== undefined && cachedImages[key] !== undefined) return cachedImages[key] - - if (!json.images) json.images = [] - - const imageDef: ImageDef = { mimeType: mimeType } - - if (options.embedImages && options.maxTextureSize !== undefined) { - const canvas = (this.cachedCanvas = this.cachedCanvas || document.createElement('canvas')) - - canvas.width = Math.min(image.width, options.maxTextureSize) - canvas.height = Math.min(image.height, options.maxTextureSize) - - const ctx = canvas.getContext('2d') - - if (flipY) { - ctx?.translate(0, canvas.height) - ctx?.scale(1, -1) - } - - if ( - (typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement) || - (typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement) || - (typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas) || - (typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap) - ) { - ctx?.drawImage(image, 0, 0, canvas.width, canvas.height) - } else { - if (format !== RGBAFormat) { - console.error('GLTFExporter: Only RGBA format is supported.') - } - - if (image.width > options.maxTextureSize || image.height > options.maxTextureSize) { - console.warn('GLTFExporter: Image size is bigger than maxTextureSize', image) - } - - const data = new Uint8ClampedArray(image.height * image.width * 4) - - if (image instanceof ImageData) { - for (let i = 0; i < data.length; i += 4) { - data[i + 0] = image.data[i + 0] - data[i + 1] = image.data[i + 1] - data[i + 2] = image.data[i + 2] - data[i + 3] = image.data[i + 3] - } - } - - ctx?.putImageData(new ImageData(data, image.width, image.height), 0, 0) - } - - if (options.binary) { - pending.push( - new Promise(function (resolve) { - canvas.toBlob(function (blob) { - if (blob !== null) { - writer.processBufferViewImage(blob).then(function (bufferViewIndex) { - imageDef.bufferView = bufferViewIndex - // @ts-expect-error - resolve() - }) - } - }, mimeType) - }), - ) - } else { - imageDef.uri = canvas.toDataURL(mimeType) - } - } else if (image instanceof Image) { - imageDef.uri = image.src - } - - const index = json.images.push(imageDef) - 1 - if (cachedImages !== undefined) cachedImages[key] = index - return index - } - - /** - * Process sampler - * @param {Texture} map Texture to process - * @return {Integer} Index of the processed texture in the "samplers" array - */ - private processSampler(map: Texture): number { - const json = this.json - - if (!json.samplers) json.samplers = [] - - const samplerDef: SamplerDef = { - magFilter: THREE_TO_WEBGL[map.magFilter], - minFilter: THREE_TO_WEBGL[map.minFilter], - wrapS: THREE_TO_WEBGL[map.wrapS], - wrapT: THREE_TO_WEBGL[map.wrapT], - } - - return json.samplers.push(samplerDef) - 1 - } - - /** - * Process texture - * @param {Texture} map Map to process - * @return {Integer} Index of the processed texture in the "textures" array - */ - public processTexture(map: Texture): number { - const cache = this.cache - const json = this.json - - if (cache.textures.has(map)) return cache.textures.get(map)! - - if (!json.textures) json.textures = [] - - const textureDef: TextureDef = { - sampler: this.processSampler(map), - source: this.processImage(map.image, map.format, map.flipY), - } - - if (map.name) textureDef.name = map.name - - this._invokeAll(function (ext: { writeTexture: ((map: Texture, textureDef: TextureDef) => void) | undefined }) { - ext.writeTexture && ext.writeTexture(map, textureDef) - }) - - const index = json.textures.push(textureDef) - 1 - cache.textures.set(map, index) - return index - } - - /** - * Process material - * @param {THREE.Material} material Material to process - * @return {Integer|null} Index of the processed material in the "materials" array - */ - private processMaterial(material: Material): number | null { - const cache = this.cache - const json = this.json - - if (cache.materials.has(material)) return cache.materials.get(material)! - - if (material instanceof ShaderMaterial && material.isShaderMaterial) { - console.warn('GLTFExporter: THREE.ShaderMaterial not supported.') - return null - } - - if (!json.materials) json.materials = [] - - // @QUESTION Should we avoid including any attribute that has the default value? - const materialDef: MaterialDef = { pbrMetallicRoughness: {} } - - if ( - !( - material instanceof MeshStandardMaterial && - material.isMeshStandardMaterial && - material instanceof MeshBasicMaterial && - // @ts-expect-error - material.isMeshBasicMaterial - ) - ) { - console.warn('GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.') - } - - if (material instanceof MeshStandardMaterial || material instanceof MeshPhysicalMaterial) { - // pbrMetallicRoughness.baseColorFactor - const color = material.color.toArray().concat([material.opacity]) - - if (!this.equalArray(color, [1, 1, 1, 1])) { - materialDef.pbrMetallicRoughness.baseColorFactor = color - } - } - - if (material instanceof MeshStandardMaterial && material.isMeshStandardMaterial) { - materialDef.pbrMetallicRoughness.metallicFactor = material.metalness - materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness - } else { - materialDef.pbrMetallicRoughness.metallicFactor = 0.5 - materialDef.pbrMetallicRoughness.roughnessFactor = 0.5 - } - - // pbrMetallicRoughness.metallicRoughnessTexture - if ( - (material instanceof MeshStandardMaterial && material.metalnessMap) || - (material instanceof MeshStandardMaterial && material.roughnessMap) - ) { - if (material.metalnessMap === material.roughnessMap && material.metalnessMap !== null) { - const metalRoughMapDef = { index: this.processTexture(material.metalnessMap) } - this.applyTextureTransform(metalRoughMapDef, material.metalnessMap) - materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef - } else { - console.warn( - 'THREE.GLTFExporter: Ignoring metalnessMap and roughnessMap because they are not the same Texture.', - ) - } - } - - // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture - if ((material instanceof MeshStandardMaterial || material instanceof MeshPhysicalMaterial) && material.map) { - const baseColorMapDef = { index: this.processTexture(material.map) } - this.applyTextureTransform(baseColorMapDef, material.map) - materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef - } - - if ((material instanceof MeshStandardMaterial || material instanceof MeshPhysicalMaterial) && material.emissive) { - // note: emissive components are limited to stay within the 0 - 1 range to accommodate glTF spec. see #21849 and #22000. - const emissive = material.emissive.clone().multiplyScalar(material.emissiveIntensity) - const maxEmissiveComponent = Math.max(emissive.r, emissive.g, emissive.b) - - if (maxEmissiveComponent > 1) { - emissive.multiplyScalar(1 / maxEmissiveComponent) - - console.warn('THREE.GLTFExporter: Some emissive components exceed 1; emissive has been limited') - } - - if (maxEmissiveComponent > 0) { - materialDef.emissiveFactor = emissive.toArray() - } - - // emissiveTexture - if (material.emissiveMap) { - const emissiveMapDef = { index: this.processTexture(material.emissiveMap) } - this.applyTextureTransform(emissiveMapDef, material.emissiveMap) - materialDef.emissiveTexture = emissiveMapDef - } - } - - // normalTexture - if ( - (material instanceof MeshMatcapMaterial || - material instanceof MeshNormalMaterial || - material instanceof MeshPhongMaterial || - material instanceof MeshStandardMaterial || - material instanceof MeshToonMaterial) && - material.normalMap - ) { - const normalMapDef: { - index: number - scale?: number - } = { index: this.processTexture(material.normalMap) } - - if (material.normalScale && material.normalScale.x !== 1) { - // glTF normal scale is univariate. Ignore `y`, which may be flipped. - // Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 - normalMapDef.scale = material.normalScale.x - } - - this.applyTextureTransform(normalMapDef, material.normalMap) - materialDef.normalTexture = normalMapDef - } - - // occlusionTexture - if ( - (material instanceof MeshBasicMaterial || - material instanceof MeshLambertMaterial || - material instanceof MeshPhongMaterial || - material instanceof MeshStandardMaterial || - material instanceof MeshToonMaterial) && - material.aoMap - ) { - const occlusionMapDef: OcclusionMapDef = { - index: this.processTexture(material.aoMap), - texCoord: 1, - } - - if (material.aoMapIntensity !== 1.0) { - occlusionMapDef.strength = material.aoMapIntensity - } - - this.applyTextureTransform(occlusionMapDef, material.aoMap) - materialDef.occlusionTexture = occlusionMapDef - } - - // alphaMode - if (material.transparent) { - materialDef.alphaMode = 'BLEND' - } else { - if (material.alphaTest > 0.0) { - materialDef.alphaMode = 'MASK' - materialDef.alphaCutoff = material.alphaTest - } - } - - // doubleSided - if (material.side === DoubleSide) materialDef.doubleSided = true - if (material.name !== '') materialDef.name = material.name - - this.serializeUserData(material, materialDef) - - this._invokeAll(function (ext: { - writeMaterial: ((material: Material, materialDef: MaterialDef) => void) | undefined - }) { - ext.writeMaterial && ext.writeMaterial(material, materialDef) - }) - - const index = json.materials.push(materialDef) - 1 - cache.materials.set(material, index) - return index - } - - /** - * Process mesh - * @param {THREE.Mesh} mesh Mesh to process - * @return {Integer|null} Index of the processed mesh in the "meshes" array - */ - private processMesh(mesh: Mesh): number | null { - const cache = this.cache - const json = this.json - - const meshCacheKeyParts = [mesh.geometry.uuid] - - if (Array.isArray(mesh.material)) { - for (let i = 0, l = mesh.material.length; i < l; i++) { - meshCacheKeyParts.push(mesh.material[i].uuid) - } - } else { - meshCacheKeyParts.push(mesh.material.uuid) - } - - const meshCacheKey = meshCacheKeyParts.join(':') - - if (cache.meshes.has(meshCacheKey)) return cache.meshes.get(meshCacheKey)! - - const geometry = mesh.geometry - let mode - - // Use the correct mode - if (mesh instanceof LineSegments && mesh.isLineSegments) { - mode = WEBGL_CONSTANTS.LINES - } else if (mesh instanceof LineLoop && mesh.isLineLoop) { - mode = WEBGL_CONSTANTS.LINE_LOOP - } else if (mesh instanceof Line && mesh.isLine) { - mode = WEBGL_CONSTANTS.LINE_STRIP - } else if (mesh instanceof Points && mesh.isPoints) { - mode = WEBGL_CONSTANTS.POINTS - } else { - mode = - (mesh.material instanceof MeshBasicMaterial || - mesh.material instanceof MeshDepthMaterial || - mesh.material instanceof MeshLambertMaterial || - mesh.material instanceof MeshNormalMaterial || - mesh.material instanceof MeshPhongMaterial || - mesh.material instanceof MeshStandardMaterial || - mesh.material instanceof MeshToonMaterial || - mesh.material instanceof ShaderMaterial) && - mesh.material.wireframe - ? WEBGL_CONSTANTS.LINES - : WEBGL_CONSTANTS.TRIANGLES - } - - if (!geometry.isBufferGeometry) { - throw new Error('THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.') - } - - const meshDef: MeshDef = {} - const attributes: { - [key: string]: BufferAttribute | InterleavedBufferAttribute | number - } = {} - const primitives = [] - const targets = [] - - // Conversion between attributes names in threejs and gltf spec - const nameConversion = { - uv: 'TEXCOORD_0', - uv2: 'TEXCOORD_1', - color: 'COLOR_0', - skinWeight: 'WEIGHTS_0', - skinIndex: 'JOINTS_0', - } - - const originalNormal = geometry.getAttribute('normal') - - if ( - originalNormal !== undefined && - !(originalNormal instanceof InterleavedBufferAttribute) && - !this.isNormalizedNormalAttribute(originalNormal) - ) { - console.warn('THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.') - - geometry.setAttribute('normal', this.createNormalizedNormalAttribute(originalNormal)) - } - - // @QUESTION Detect if .vertexColors = true? - // For every attribute create an accessor - let modifiedAttribute = null - - for (let attributeName in geometry.attributes) { - // Ignore morph target attributes, which are exported later. - if (attributeName.substr(0, 5) === 'morph') continue - - const attribute = geometry.attributes[attributeName] - attributeName = nameConversion[attributeName as keyof typeof nameConversion] || attributeName.toUpperCase() - - // Prefix all geometry attributes except the ones specifically - // listed in the spec; non-spec attributes are considered custom. - const validVertexAttributes = /^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/ - - if (!validVertexAttributes.test(attributeName)) attributeName = '_' + attributeName - - if (cache.attributes.has(this.getUID(attribute))) { - attributes[attributeName] = cache.attributes.get(this.getUID(attribute))! - continue - } - - // JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT. - modifiedAttribute = null - const array = attribute.array - - if (attributeName === 'JOINTS_0' && !(array instanceof Uint16Array) && !(array instanceof Uint8Array)) { - console.warn('GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.') - modifiedAttribute = new BufferAttribute(new Uint16Array(array), attribute.itemSize, attribute.normalized) - } - - const accessor = modifiedAttribute !== null && this.processAccessor(modifiedAttribute || attribute, geometry) - - if (accessor) { - attributes[attributeName] = accessor - cache.attributes.set(this.getUID(attribute), accessor) - } - } - - if (originalNormal !== undefined) geometry.setAttribute('normal', originalNormal) - - // Skip if no exportable attributes found - if (Object.keys(attributes).length === 0) return null - - // Morph targets - if (mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0) { - const weights = [] - const targetNames = [] - const reverseDictionary: { - [key: number]: string - } = {} - - if (mesh.morphTargetDictionary !== undefined) { - for (const key in mesh.morphTargetDictionary) { - reverseDictionary[mesh.morphTargetDictionary[key]] = key - } - } - - for (let i = 0; i < mesh.morphTargetInfluences.length; ++i) { - const target: { - [key: string]: BufferAttribute | InterleavedBufferAttribute | number - } = {} - let warned = false - - for (const attributeName in geometry.morphAttributes) { - // glTF 2.0 morph supports only POSITION/NORMAL/TANGENT. - // Three.js doesn't support TANGENT yet. - - if (attributeName !== 'position' && attributeName !== 'normal') { - if (!warned) { - console.warn('GLTFExporter: Only POSITION and NORMAL morph are supported.') - warned = true - } - - continue - } - - const attribute = geometry.morphAttributes[attributeName][i] - const gltfAttributeName = attributeName.toUpperCase() - - // Three.js morph attribute has absolute values while the one of glTF has relative values. - // - // glTF 2.0 Specification: - // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets - - const baseAttribute = geometry.attributes[attributeName] - - if (cache.attributes.has(this.getUID(attribute))) { - target[gltfAttributeName] = cache.attributes.get(this.getUID(attribute))! - continue - } - - // Clones attribute not to override - const relativeAttribute = attribute.clone() - - if (!geometry.morphTargetsRelative) { - for (let j = 0, jl = attribute.count; j < jl; j++) { - relativeAttribute.setXYZ( - j, - attribute.getX(j) - baseAttribute.getX(j), - attribute.getY(j) - baseAttribute.getY(j), - attribute.getZ(j) - baseAttribute.getZ(j), - ) - } - } - - const accessor = this.processAccessor(relativeAttribute, geometry) - if (accessor != undefined) { - target[gltfAttributeName] = accessor - } - cache.attributes.set(this.getUID(baseAttribute), target[gltfAttributeName]) - } - - targets.push(target) - - weights.push(mesh.morphTargetInfluences[i]) - - if (mesh.morphTargetDictionary !== undefined) targetNames.push(reverseDictionary[i]) - } - - meshDef.weights = weights - - if (targetNames.length > 0) { - meshDef.extras = {} - meshDef.extras.targetNames = targetNames - } - } - - const isMultiMaterial = Array.isArray(mesh.material) - - if (isMultiMaterial && geometry.groups.length === 0) return null - - const materials = isMultiMaterial ? mesh.material : [mesh.material] - const groups = isMultiMaterial ? geometry.groups : [{ materialIndex: 0, start: undefined, count: undefined }] - - for (let i = 0, il = groups.length; i < il; i++) { - const primitive: Primitive = { - mode: mode, - attributes: attributes, - } - - if (geometry instanceof Object3D || geometry instanceof Material) { - this.serializeUserData(geometry, primitive) - } - - if (targets.length > 0) primitive.targets = targets - - if (geometry.index !== null) { - let cacheKey = this.getUID(geometry.index) - - if (groups[i].start !== undefined || groups[i].count !== undefined) { - // @ts-expect-error - cacheKey += `:${groups[i].start}:${groups[i].count}` - } - - if (cache.attributes.has(cacheKey)) { - primitive.indices = cache.attributes.get(cacheKey)! - } else { - primitive.indices = this.processAccessor(geometry.index, geometry, groups[i].start, groups[i].count)! - cache.attributes.set(cacheKey, primitive.indices!) - } - - if (primitive.indices === null) delete primitive.indices - } - - const materialIndex = groups[i].materialIndex - if (materialIndex !== undefined && Array.isArray(materials)) { - const targetMaterials = materials[materialIndex] - if (!Array.isArray(targetMaterials)) { - const material = this.processMaterial(targetMaterials) - - if (material !== null) primitive.material = material - - primitives.push(primitive) - } - } - } - - meshDef.primitives = primitives - - if (!json.meshes) json.meshes = [] - - this._invokeAll(function (ext: { writeMesh: (mesh: Mesh, meshDef: MeshDef) => void }) { - ext.writeMesh && ext.writeMesh(mesh, meshDef) - }) - - const index = json.meshes.push(meshDef) - 1 - cache.meshes.set(meshCacheKey, index) - return index - } - - /** - * Process camera - * @param {THREE.Camera} camera Camera to process - * @return {Integer} Index of the processed mesh in the "camera" array - */ - private processCamera(camera: Camera): number { - const json = this.json - - if (!json.cameras) json.cameras = [] - - const isOrtho = camera instanceof OrthographicCamera && camera.isOrthographicCamera - - const cameraDef: CameraDef = { - type: isOrtho ? 'orthographic' : 'perspective', - } - - if (camera instanceof OrthographicCamera && isOrtho) { - cameraDef.orthographic = { - xmag: camera.right * 2, - ymag: camera.top * 2, - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near, - } - } else if (camera instanceof PerspectiveCamera) { - cameraDef.perspective = { - aspectRatio: camera.aspect, - yfov: MathUtils.degToRad(camera.fov), - zfar: camera.far <= 0 ? 0.001 : camera.far, - znear: camera.near < 0 ? 0 : camera.near, - } - } - - // Question: Is saving "type" as name intentional? - if (camera.name !== '') cameraDef.name = camera.type - - return json.cameras.push(cameraDef) - 1 - } - - /** - * Creates glTF animation entry from AnimationClip object. - * - * Status: - * - Only properties listed in PATH_PROPERTIES may be animated. - * - * @param {THREE.AnimationClip} clip - * @param {THREE.Object3D} root - * @return {number|null} - */ - private processAnimation(clip: AnimationClip, root: Object3D): number | null { - const json = this.json - const nodeMap = this.nodeMap - - if (!json.animations) json.animations = [] - - clip = GLTFExporter.Utils.mergeMorphTargetTracks(clip.clone(), root) - - const tracks = clip.tracks - const channels = [] - const samplers = [] - - for (let i = 0; i < tracks.length; ++i) { - const track = tracks[i] - const trackBinding = PropertyBinding.parseTrackName(track.name) - let trackNode = PropertyBinding.findNode(root, trackBinding.nodeName) - const trackProperty = PATH_PROPERTIES[trackBinding.propertyName as keyof typeof PATH_PROPERTIES] - - if (trackBinding.objectName === 'bones') { - if (trackNode.isSkinnedMesh) { - trackNode = trackNode.skeleton.getBoneByName(trackBinding.objectIndex) - } else { - trackNode = undefined - } - } - - if (!trackNode || !trackProperty) { - console.warn('THREE.GLTFExporter: Could not export animation track "%s".', track.name) - return null - } - - const inputItemSize = 1 - let outputItemSize = track.values.length / track.times.length - - if (trackProperty === PATH_PROPERTIES.morphTargetInfluences) { - outputItemSize /= trackNode.morphTargetInfluences.length - } - - let interpolation - - // @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE - - // Detecting glTF cubic spline interpolant by checking factory method's special property - // GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return - // valid value from .getInterpolation(). - // @ts-expect-error - if (track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline) { - interpolation = 'CUBICSPLINE' - - // itemSize of CUBICSPLINE keyframe is 9 - // (VEC3 * 3: inTangent, splineVertex, and outTangent) - // but needs to be stored as VEC3 so dividing by 3 here. - outputItemSize /= 3 - } else if (track.getInterpolation() === InterpolateDiscrete) { - interpolation = 'STEP' - } else { - interpolation = 'LINEAR' - } - - samplers.push({ - input: this.processAccessor(new BufferAttribute(track.times, inputItemSize)), - output: this.processAccessor(new BufferAttribute(track.values, outputItemSize)), - interpolation: interpolation, - }) - - channels.push({ - sampler: samplers.length - 1, - target: { - node: nodeMap.get(trackNode), - path: trackProperty, - }, - }) - } - - json.animations.push({ - name: clip.name || 'clip_' + json.animations.length, - samplers: samplers, - channels: channels, - }) - - return json.animations.length - 1 - } - - /** - * @param {THREE.Object3D} object - * @return {number|null} - */ - private processSkin(object: Object3D): number | null { - const json = this.json - const nodeMap = this.nodeMap - - if (json.nodes !== undefined && object instanceof SkinnedMesh) { - const node = json.nodes[nodeMap.get(object)!] - - const skeleton = object.skeleton - - if (skeleton === undefined) return null - - const rootJoint = object.skeleton.bones[0] - - if (rootJoint === undefined) return null - - const joints = [] - const inverseBindMatrices = new Float32Array(skeleton.bones.length * 16) - const temporaryBoneInverse = new Matrix4() - - for (let i = 0; i < skeleton.bones.length; ++i) { - joints.push(nodeMap.get(skeleton.bones[i])) - temporaryBoneInverse.copy(skeleton.boneInverses[i]) - temporaryBoneInverse.multiply(object.bindMatrix).toArray(inverseBindMatrices, i * 16) - } - - if (json.skins === undefined) json.skins = [] - - json.skins.push({ - inverseBindMatrices: this.processAccessor(new BufferAttribute(inverseBindMatrices, 16)), - joints: joints, - skeleton: nodeMap.get(rootJoint), - }) - - const skinIndex = (node.skin = json.skins.length - 1) - - return skinIndex - } else { - return null - } - } - - /** - * Process Object3D node - * @param {THREE.Object3D} node Object3D to processNode - * @return {Integer} Index of the node in the nodes list - */ - private processNode(object: Object3D): number { - const json = this.json - const options = this.options - const nodeMap = this.nodeMap - - if (!json.nodes) json.nodes = [] - - const nodeDef: NodeDef = {} - - if (options.trs) { - const rotation = object.quaternion.toArray() - const position = object.position.toArray() - const scale = object.scale.toArray() - - if (!this.equalArray(rotation, [0, 0, 0, 1])) { - nodeDef.rotation = rotation - } - - if (!this.equalArray(position, [0, 0, 0])) { - nodeDef.translation = position - } - - if (!this.equalArray(scale, [1, 1, 1])) { - nodeDef.scale = scale - } - } else { - if (object.matrixAutoUpdate) { - object.updateMatrix() - } - - if (!this.isIdentityMatrix(object.matrix)) { - nodeDef.matrix = object.matrix.elements - } - } - - // We don't export empty strings name because it represents no-name in Three.js. - if (object.name !== '') nodeDef.name = String(object.name) - - this.serializeUserData(object, nodeDef) - - if ( - ((object instanceof Mesh && object.isMesh) || - (object instanceof Line && object.isLine) || - (object instanceof Points && object.isPoints)) && - object instanceof Mesh - ) { - const meshIndex = this.processMesh(object) - - if (meshIndex !== null) nodeDef.mesh = meshIndex - } else if (object instanceof Camera && object.isCamera) { - nodeDef.camera = this.processCamera(object) - } - - if (object instanceof SkinnedMesh && object.isSkinnedMesh) this.skins.push(object) - - if (object.children.length > 0) { - const children = [] - - for (let i = 0, l = object.children.length; i < l; i++) { - const child = object.children[i] - - if (child.visible || !options.onlyVisible) { - const nodeIndex = this.processNode(child) - - if (nodeIndex !== null) children.push(nodeIndex) - } - } - - if (children.length > 0) nodeDef.children = children - } - - this._invokeAll(function (ext: { writeNode: (object: Object3D, nodeDef: NodeDef) => void }) { - ext.writeNode && ext.writeNode(object, nodeDef) - }) - - const nodeIndex = json.nodes.push(nodeDef) - 1 - nodeMap.set(object, nodeIndex) - return nodeIndex - } - - /** - * Process Scene - * @param {Scene} node Scene to process - */ - private processScene(scene: Scene): void { - const json = this.json - const options = this.options - - if (!json.scenes) { - json.scenes = [] - json.scene = 0 - } - - const sceneDef: SceneDef = {} - - if (scene.name !== '') sceneDef.name = scene.name - - json.scenes.push(sceneDef) - - const nodes = [] - - for (let i = 0, l = scene.children.length; i < l; i++) { - const child = scene.children[i] - - if (child.visible || !options.onlyVisible) { - const nodeIndex = this.processNode(child) - - if (nodeIndex !== null) nodes.push(nodeIndex) - } - } - - if (nodes.length > 0) sceneDef.nodes = nodes - - this.serializeUserData(scene, sceneDef) - } - - private processObjects(objects: Object3D[]): void { - const scene = new Scene() - scene.name = 'AuxScene' - - for (let i = 0; i < objects.length; i++) { - // We push directly to children instead of calling `add` to prevent - // modify the .parent and break its original scene and hierarchy - scene.children.push(objects[i]) - } - - this.processScene(scene) - } - - /** - * @param {THREE.Object3D|Array} input - */ - private processInput(input: Object3D | Object3D[]): void { - const options = this.options - - input = input instanceof Array ? input : [input] - - this._invokeAll(function (ext: { beforeParse: (input: Object3D | Object3D[]) => void }) { - ext.beforeParse && ext.beforeParse(input) - }) - - const objectsWithoutScene = [] - - for (let i = 0; i < input.length; i++) { - const inputScene = input[i] - if (inputScene instanceof Scene) { - this.processScene(inputScene) - } else { - objectsWithoutScene.push(input[i]) - } - } - - if (objectsWithoutScene.length > 0) this.processObjects(objectsWithoutScene) - - for (let i = 0; i < this.skins.length; ++i) { - this.processSkin(this.skins[i]) - } - - for (let i = 0; options.animations !== undefined && i < options.animations.length; ++i) { - this.processAnimation(options.animations[i], input[0]) - } - - this._invokeAll(function (ext: { afterParse: (input: Object3D | Object3D[]) => void }) { - ext.afterParse && ext.afterParse(input) - }) - } - - private _invokeAll(func: (ext: any) => void): void { - for (let i = 0, il = this.plugins.length; i < il; i++) { - func(this.plugins[i]) - } - } - - //------------------------------------------------------------------------------ - // Utility functions - //------------------------------------------------------------------------------ - - /** - * Compare two arrays - * @param {Array} array1 Array 1 to compare - * @param {Array} array2 Array 2 to compare - * @return {Boolean} Returns true if both arrays are equal - */ - private equalArray(array1: any[], array2: any[]): boolean { - return ( - array1.length === array2.length && - array1.every(function (element, index) { - return element === array2[index] - }) - ) - } - - /** - * Converts a string to an ArrayBuffer. - * @param {string} text - * @return {ArrayBuffer} - */ - private stringToArrayBuffer(text: string): ArrayBuffer { - if (window.TextEncoder !== undefined) { - return new TextEncoder().encode(text).buffer - } - - const array = new Uint8Array(new ArrayBuffer(text.length)) - - for (let i = 0, il = text.length; i < il; i++) { - const value = text.charCodeAt(i) - - // Replacing multi-byte character with space(0x20). - array[i] = value > 0xff ? 0x20 : value - } - - return array.buffer - } - - private isIdentityMatrix(matrix: Matrix4): boolean { - return this.equalArray(matrix.elements, [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]) - } - - private getMinMax(attribute: BufferAttribute, start: number, count: number): { min: number[]; max: number[] } { - const output: { - min: number[] - max: number[] - } = { - min: new Array(attribute.itemSize).fill(Number.POSITIVE_INFINITY), - max: new Array(attribute.itemSize).fill(Number.NEGATIVE_INFINITY), - } - - for (let i = start; i < start + count; i++) { - for (let a = 0; a < attribute.itemSize; a++) { - let value - - if (attribute.itemSize > 4) { - // no support for interleaved data for itemSize > 4 - - value = attribute.array[i * attribute.itemSize + a] - } else { - if (a === 0) value = attribute.getX(i) - else if (a === 1) value = attribute.getY(i) - else if (a === 2) value = attribute.getZ(i) - else if (a === 3) value = attribute.getW(i) - } - - if (value !== undefined) { - output.min[a] = Math.min(output.min[a], value) - output.max[a] = Math.max(output.max[a], value) - } - } - } - - return output - } - - /** - * Get the required size + padding for a buffer, rounded to the next 4-byte boundary. - * https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment - */ - private getPaddedBufferSize(bufferSize: number): number { - return Math.ceil(bufferSize / 4) * 4 - } - - /** - * Returns a buffer aligned to 4-byte boundary. - * - * @param {ArrayBuffer} arrayBuffer Buffer to pad - * @param {Integer} paddingByte (Optional) - * @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer - */ - private getPaddedArrayBuffer(arrayBuffer: ArrayBuffer, paddingByte = 0): ArrayBuffer { - const paddedLength = this.getPaddedBufferSize(arrayBuffer.byteLength) - - if (paddedLength !== arrayBuffer.byteLength) { - const array = new Uint8Array(paddedLength) - array.set(new Uint8Array(arrayBuffer)) - - if (paddingByte !== 0) { - for (let i = arrayBuffer.byteLength; i < paddedLength; i++) { - array[i] = paddingByte - } - } - - return array.buffer - } - - return arrayBuffer - } -} - -/** - * Punctual Lights Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual - */ -class GLTFLightExtension { - private writer: GLTFWriter - private name: string - - constructor(writer: GLTFWriter) { - this.writer = writer - this.name = 'KHR_lights_punctual' - } - - public writeNode(light: Light, nodeDef: NodeDef): void { - if (!light.isLight) return - - if ( - !(light instanceof DirectionalLight && light.isDirectionalLight) && - // @ts-expect-error - !(light instanceof PointLight && light.isPointLight) && - !(light instanceof SpotLight && light.isSpotLight) - ) { - console.warn('THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light) - return - } - - const writer = this.writer - const json = writer.json - const extensionsUsed = writer.extensionsUsed - - const lightDef: LightDef = {} - - if (light.name) lightDef.name = light.name - - lightDef.color = light.color.toArray() - - lightDef.intensity = light.intensity - - if (light instanceof DirectionalLight && light.isDirectionalLight) { - lightDef.type = 'directional' - } else if ( - light instanceof PointLight && - // @ts-expect-error - light.isPointLight - ) { - lightDef.type = 'point' - - if (light.distance > 0) lightDef.range = light.distance - } else if (light instanceof SpotLight && light.isSpotLight) { - lightDef.type = 'spot' - - if (light.distance > 0) lightDef.range = light.distance - - lightDef.spot = {} - lightDef.spot.innerConeAngle = (light.penumbra - 1.0) * light.angle * -1.0 - lightDef.spot.outerConeAngle = light.angle - } - - if (!(light instanceof DirectionalLight) && light.decay !== undefined && light.decay !== 2) { - console.warn( - 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, ' + 'and expects light.decay=2.', - ) - } - - if ( - !(light instanceof PointLight) && - light.target && - (light.target.parent !== light || - light.target.position.x !== 0 || - light.target.position.y !== 0 || - light.target.position.z !== -1) - ) { - console.warn( - 'THREE.GLTFExporter: Light direction may be lost. For best results, ' + - 'make light.target a child of the light with position 0,0,-1.', - ) - } - - if (!extensionsUsed[this.name]) { - json.extensions = json.extensions || {} - json.extensions[this.name] = { lights: [] } - extensionsUsed[this.name] = true - } - - if (json.extensions !== undefined) { - const lights = json.extensions[this.name].lights - lights.push(lightDef) - - nodeDef.extensions = nodeDef.extensions || {} - nodeDef.extensions[this.name] = { light: lights.length - 1 } - } - } -} - -/** - * Unlit Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit - */ -class GLTFMaterialsUnlitExtension { - private writer: GLTFWriter - private name: string - - constructor(writer: GLTFWriter) { - this.writer = writer - this.name = 'KHR_materials_unlit' - } - - public writeMaterial(material: Material, materialDef: MaterialDef): void { - if ( - !( - material instanceof MeshBasicMaterial && - // @ts-expect-error - material.isMeshBasicMaterial - ) - ) { - return - } - - const writer = this.writer - const extensionsUsed = writer.extensionsUsed - - materialDef.extensions = materialDef.extensions || {} - materialDef.extensions[this.name] = {} - - extensionsUsed[this.name] = true - - materialDef.pbrMetallicRoughness.metallicFactor = 0.0 - materialDef.pbrMetallicRoughness.roughnessFactor = 0.9 - } -} - -/** - * Specular-Glossiness Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness - */ -class GLTFMaterialsPBRSpecularGlossiness { - private writer: GLTFWriter - private name: string - - constructor(writer: GLTFWriter) { - this.writer = writer - this.name = 'KHR_materials_pbrSpecularGlossiness' - } - - public writeMaterial(material: Material, materialDef: MaterialDef): void { - // @ts-expect-error - if (!material.isGLTFSpecularGlossinessMaterial) return - - const writer = this.writer - const extensionsUsed = writer.extensionsUsed - - const extensionDef: ExtensionDef = {} - - if (materialDef.pbrMetallicRoughness.baseColorFactor) { - extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor - } - - if (material instanceof MeshPhongMaterial) { - const specularFactor = [1, 1, 1] - material.specular.toArray(specularFactor, 0) - extensionDef.specularFactor = specularFactor - extensionDef.glossinessFactor = - // @ts-expect-error - material.glossiness - } - - if (materialDef.pbrMetallicRoughness.baseColorTexture) { - extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture - } - - if ( - (material instanceof MeshBasicMaterial || - material instanceof MeshLambertMaterial || - material instanceof MeshPhongMaterial) && - material.specularMap - ) { - const specularMapDef = { index: writer.processTexture(material.specularMap) } - writer.applyTextureTransform(specularMapDef, material.specularMap) - extensionDef.specularGlossinessTexture = specularMapDef - } - - materialDef.extensions = materialDef.extensions || {} - materialDef.extensions[this.name] = extensionDef - extensionsUsed[this.name] = true - } -} - -/** - * Transmission Materials Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission - */ -class GLTFMaterialsTransmissionExtension { - private writer: GLTFWriter - private name: string - - constructor(writer: GLTFWriter) { - this.writer = writer - this.name = 'KHR_materials_transmission' - } - - public writeMaterial(material: Material, materialDef: MaterialDef): void { - if ( - !( - material instanceof MeshPhysicalMaterial && - // @ts-expect-error - material.isMeshPhysicalMaterial - ) || - material.transmission === 0 - ) { - return - } - - const writer = this.writer - const extensionsUsed = writer.extensionsUsed - - const extensionDef: ExtensionDef = {} - - extensionDef.transmissionFactor = material.transmission - - if (material.transmissionMap) { - const transmissionMapDef = { index: writer.processTexture(material.transmissionMap) } - writer.applyTextureTransform(transmissionMapDef, material.transmissionMap) - extensionDef.transmissionTexture = transmissionMapDef - } - - materialDef.extensions = materialDef.extensions || {} - materialDef.extensions[this.name] = extensionDef - - extensionsUsed[this.name] = true - } -} - -/** - * Materials Volume Extension - * - * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume - */ -class GLTFMaterialsVolumeExtension { - private writer: GLTFWriter - private name: string - - constructor(writer: GLTFWriter) { - this.writer = writer - this.name = 'KHR_materials_volume' - } - - public writeMaterial(material: Material, materialDef: MaterialDef): void { - if ( - !( - material instanceof MeshPhysicalMaterial && - // @ts-expect-error - material.isMeshPhysicalMaterial - ) || - material.thickness === 0 - ) { - return - } - - const writer = this.writer - const extensionsUsed = writer.extensionsUsed - - const extensionDef: ExtensionDef = {} - - extensionDef.thickness = material.thickness - - if (material.thicknessMap) { - const thicknessMapDef = { - index: writer.processTexture(material.thicknessMap), - } - writer.applyTextureTransform(thicknessMapDef, material.thicknessMap) - extensionDef.thicknessTexture = thicknessMapDef - } - - extensionDef.attenuationDistance = material.attenuationDistance - extensionDef.attenuationColor = - //@ts-expect-error - material.attenuationTint.toArray() - - materialDef.extensions = materialDef.extensions || {} - materialDef.extensions[this.name] = extensionDef - - extensionsUsed[this.name] = true - } -} - -export { GLTFExporter } diff --git a/src/exporters/MMDExporter.ts b/src/exporters/MMDExporter.ts index fabdb263..25d28f68 100644 --- a/src/exporters/MMDExporter.ts +++ b/src/exporters/MMDExporter.ts @@ -1,4 +1,5 @@ import { Matrix4, Quaternion, Vector3, Bone, SkinnedMesh } from 'three' +// @ts-ignore import { CharsetEncoder } from 'mmd-parser' /** diff --git a/src/exporters/USDZExporter.ts b/src/exporters/USDZExporter.ts index a1dea19a..eb8227c7 100644 --- a/src/exporters/USDZExporter.ts +++ b/src/exporters/USDZExporter.ts @@ -243,7 +243,7 @@ def "Geometry" private buildMeshVertexIndices(geometry: BufferGeometry): string { if (geometry.index !== null) { - // @ts-expect-error + // @ts-ignore return geometry.index.array.join(', ') } @@ -287,7 +287,7 @@ def "Geometry" const data = attribute.array for (let i = 0; i < data.length; i += 2) { - // @ts-expect-error + // @ts-ignore array.push(`(${data[i + 0].toPrecision(this.PRECISION)}, ${1 - data[i + 1].toPrecision(this.PRECISION)})`) } diff --git a/src/geometries/ConvexGeometry.js b/src/geometries/ConvexGeometry.js index aab42c27..9919d09a 100644 --- a/src/geometries/ConvexGeometry.js +++ b/src/geometries/ConvexGeometry.js @@ -10,10 +10,6 @@ class ConvexGeometry extends BufferGeometry { const vertices = [] const normals = [] - if (ConvexHull === undefined) { - console.error('THREE.ConvexGeometry: ConvexGeometry relies on ConvexHull') - } - const convexHull = new ConvexHull().setFromPoints(points) // generate vertices and normals diff --git a/src/geometries/LightningStrike.d.ts b/src/geometries/LightningStrike.d.ts index 7a71d057..d96ce79f 100644 --- a/src/geometries/LightningStrike.d.ts +++ b/src/geometries/LightningStrike.d.ts @@ -1,4 +1,4 @@ -import { Vector3 } from 'three' +import { BufferGeometry, Vector3 } from 'three' export interface RandomGenerator { random(): number @@ -87,7 +87,7 @@ export interface RayParameters { ) => void } -export class LightningStrike { +export class LightningStrike extends BufferGeometry { constructor(rayParameters?: RayParameters) copyParameters(dest?: RayParameters, source?: RayParameters): RayParameters @@ -103,6 +103,6 @@ export class LightningStrike { update(time: number): void - copy(source: LightningStrike): LightningStrike + copy(source: LightningStrike): this clone(): this } diff --git a/src/geometries/LightningStrike.js b/src/geometries/LightningStrike.js index b7e55f46..36cb6bc0 100644 --- a/src/geometries/LightningStrike.js +++ b/src/geometries/LightningStrike.js @@ -110,19 +110,112 @@ import { SimplexNoise } from '../math/SimplexNoise' */ class LightningStrike extends BufferGeometry { - constructor(rayParameters) { + // Ray states + static RAY_INITIALIZED = 0 + static RAY_UNBORN = 1 + static RAY_PROPAGATING = 2 + static RAY_STEADY = 3 + static RAY_VANISHING = 4 + static RAY_EXTINGUISHED = 5 + + static COS30DEG = Math.cos((30 * Math.PI) / 180) + static SIN30DEG = Math.sin((30 * Math.PI) / 180) + + constructor(rayParameters = {}) { super() + this.isLightningStrike = true + this.type = 'LightningStrike' // Set parameters, and set undefined parameters to default values - rayParameters = rayParameters || {} this.init(LightningStrike.copyParameters(rayParameters, rayParameters)) // Creates and populates the mesh this.createMesh() } + static createRandomGenerator() { + const numSeeds = 2053 + const seeds = [] + + for (let i = 0; i < numSeeds; i++) { + seeds.push(Math.random()) + } + + const generator = { + currentSeed: 0, + + random: function () { + const value = seeds[generator.currentSeed] + + generator.currentSeed = (generator.currentSeed + 1) % numSeeds + + return value + }, + + getSeed: function () { + return generator.currentSeed / numSeeds + }, + + setSeed: function (seed) { + generator.currentSeed = Math.floor(seed * numSeeds) % numSeeds + }, + } + + return generator + } + + static copyParameters(dest = {}, source = {}) { + const vecCopy = function (v) { + if (source === dest) { + return v + } else { + return v.clone() + } + } + + ;(dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy(source.sourceOffset) : new Vector3(0, 100, 0)), + (dest.destOffset = source.destOffset !== undefined ? vecCopy(source.destOffset) : new Vector3(0, 0, 0)), + (dest.timeScale = source.timeScale !== undefined ? source.timeScale : 1), + (dest.roughness = source.roughness !== undefined ? source.roughness : 0.9), + (dest.straightness = source.straightness !== undefined ? source.straightness : 0.7), + (dest.up0 = source.up0 !== undefined ? vecCopy(source.up0) : new Vector3(0, 0, 1)) + ;(dest.up1 = source.up1 !== undefined ? vecCopy(source.up1) : new Vector3(0, 0, 1)), + (dest.radius0 = source.radius0 !== undefined ? source.radius0 : 1), + (dest.radius1 = source.radius1 !== undefined ? source.radius1 : 1), + (dest.radius0Factor = source.radius0Factor !== undefined ? source.radius0Factor : 0.5), + (dest.radius1Factor = source.radius1Factor !== undefined ? source.radius1Factor : 0.2), + (dest.minRadius = source.minRadius !== undefined ? source.minRadius : 0.2), + // These parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: + + (dest.isEternal = + source.isEternal !== undefined + ? source.isEternal + : source.birthTime === undefined || source.deathTime === undefined), + (dest.birthTime = source.birthTime), + (dest.deathTime = source.deathTime), + (dest.propagationTimeFactor = source.propagationTimeFactor !== undefined ? source.propagationTimeFactor : 0.1), + (dest.vanishingTimeFactor = source.vanishingTimeFactor !== undefined ? source.vanishingTimeFactor : 0.9), + (dest.subrayPeriod = source.subrayPeriod !== undefined ? source.subrayPeriod : 4), + (dest.subrayDutyCycle = source.subrayDutyCycle !== undefined ? source.subrayDutyCycle : 0.6) + + // These parameters cannot change after lightning creation: + + dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9 + dest.isStatic = source.isStatic !== undefined ? source.isStatic : false + dest.ramification = source.ramification !== undefined ? source.ramification : 5 + dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3 + dest.recursionProbability = source.recursionProbability !== undefined ? source.recursionProbability : 0.6 + dest.generateUVs = source.generateUVs !== undefined ? source.generateUVs : false + ;(dest.randomGenerator = source.randomGenerator), + (dest.noiseSeed = source.noiseSeed), + (dest.onDecideSubrayCreation = source.onDecideSubrayCreation), + (dest.onSubrayCreation = source.onSubrayCreation) + + return dest + } + update(time) { if (this.isStatic) return @@ -265,6 +358,7 @@ class LightningStrike extends BufferGeometry { this.vertices = new Float32Array(maxVerts * 3) this.indices = new Uint32Array(maxIndices) + if (this.generateUVs) { this.uvs = new Float32Array(maxVerts * 2) } @@ -285,6 +379,7 @@ class LightningStrike extends BufferGeometry { if (!this.isStatic) { this.index.usage = DynamicDrawUsage this.positionAttribute.usage = DynamicDrawUsage + if (this.generateUVs) { this.uvsAttribute.usage = DynamicDrawUsage } @@ -293,6 +388,7 @@ class LightningStrike extends BufferGeometry { // Store buffers for later modification this.vertices = this.positionAttribute.array this.indices = this.index.array + if (this.generateUVs) { this.uvs = this.uvsAttribute.array } @@ -359,7 +455,7 @@ class LightningStrike extends BufferGeometry { }) } - addNewSubray() /*rayParameters*/ { + addNewSubray(/*rayParameters*/) { return this.subrays[this.numSubrays++] } @@ -594,7 +690,7 @@ class LightningStrike extends BufferGeometry { createPrismFaces(vertex /*, index*/) { const indices = this.indices - var vertex = this.currentVertex - 6 + vertex = this.currentVertex - 6 indices[this.currentIndex++] = vertex + 1 indices[this.currentIndex++] = vertex + 2 @@ -619,7 +715,7 @@ class LightningStrike extends BufferGeometry { createDefaultSubrayCreationCallbacks() { const random1 = this.randomGenerator.random - this.onDecideSubrayCreation = (segment, lightningStrike) => { + this.onDecideSubrayCreation = function (segment, lightningStrike) { // Decide subrays creation at parent (sub)ray segment const subray = lightningStrike.currentSubray @@ -695,21 +791,21 @@ class LightningStrike extends BufferGeometry { const vec3Side = new Vector3() const vec4Up = new Vector3() - this.onSubrayCreation = (segment, parentSubray, childSubray, lightningStrike) => { + this.onSubrayCreation = function (segment, parentSubray, childSubray, lightningStrike) { // Decide childSubray origin and destination positions (pos0 and pos1) and possibly other properties of childSubray // Just use the default cone position generator lightningStrike.subrayCylinderPosition(segment, parentSubray, childSubray, 0.5, 0.6, 0.2) } - this.subrayConePosition = ( + this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor, - ) => { + ) { // Sets childSubray pos0 and pos1 in a cone childSubray.pos0.copy(segment.pos0) @@ -731,14 +827,14 @@ class LightningStrike extends BufferGeometry { .add(parentSubray.pos0) } - this.subrayCylinderPosition = ( + this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor, - ) => { + ) { // Sets childSubray pos0 and pos1 in a cylinder childSubray.pos0.copy(segment.pos0) @@ -820,100 +916,4 @@ class LightningStrike extends BufferGeometry { } } -LightningStrike.prototype.isLightningStrike = true - -// Ray states -LightningStrike.RAY_INITIALIZED = 0 -LightningStrike.RAY_UNBORN = 1 -LightningStrike.RAY_PROPAGATING = 2 -LightningStrike.RAY_STEADY = 3 -LightningStrike.RAY_VANISHING = 4 -LightningStrike.RAY_EXTINGUISHED = 5 - -LightningStrike.COS30DEG = Math.cos((30 * Math.PI) / 180) -LightningStrike.SIN30DEG = Math.sin((30 * Math.PI) / 180) - -LightningStrike.createRandomGenerator = () => { - const numSeeds = 2053 - const seeds = [] - - for (let i = 0; i < numSeeds; i++) { - seeds.push(Math.random()) - } - - const generator = { - currentSeed: 0, - - random: function () { - const value = seeds[generator.currentSeed] - - generator.currentSeed = (generator.currentSeed + 1) % numSeeds - - return value - }, - - getSeed: function () { - return generator.currentSeed / numSeeds - }, - - setSeed: function (seed) { - generator.currentSeed = Math.floor(seed * numSeeds) % numSeeds - }, - } - - return generator -} - -LightningStrike.copyParameters = (dest, source) => { - source = source || {} - dest = dest || {} - - const vecCopy = (v) => { - if (source === dest) { - return v - } else { - return v.clone() - } - } - ;(dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy(source.sourceOffset) : new Vector3(0, 100, 0)), - (dest.destOffset = source.destOffset !== undefined ? vecCopy(source.destOffset) : new Vector3(0, 0, 0)), - (dest.timeScale = source.timeScale !== undefined ? source.timeScale : 1), - (dest.roughness = source.roughness !== undefined ? source.roughness : 0.9), - (dest.straightness = source.straightness !== undefined ? source.straightness : 0.7), - (dest.up0 = source.up0 !== undefined ? vecCopy(source.up0) : new Vector3(0, 0, 1)) - ;(dest.up1 = source.up1 !== undefined ? vecCopy(source.up1) : new Vector3(0, 0, 1)), - (dest.radius0 = source.radius0 !== undefined ? source.radius0 : 1), - (dest.radius1 = source.radius1 !== undefined ? source.radius1 : 1), - (dest.radius0Factor = source.radius0Factor !== undefined ? source.radius0Factor : 0.5), - (dest.radius1Factor = source.radius1Factor !== undefined ? source.radius1Factor : 0.2), - (dest.minRadius = source.minRadius !== undefined ? source.minRadius : 0.2), - // These parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: - - (dest.isEternal = - source.isEternal !== undefined - ? source.isEternal - : source.birthTime === undefined || source.deathTime === undefined), - (dest.birthTime = source.birthTime), - (dest.deathTime = source.deathTime), - (dest.propagationTimeFactor = source.propagationTimeFactor !== undefined ? source.propagationTimeFactor : 0.1), - (dest.vanishingTimeFactor = source.vanishingTimeFactor !== undefined ? source.vanishingTimeFactor : 0.9), - (dest.subrayPeriod = source.subrayPeriod !== undefined ? source.subrayPeriod : 4), - (dest.subrayDutyCycle = source.subrayDutyCycle !== undefined ? source.subrayDutyCycle : 0.6) - - // These parameters cannot change after lightning creation: - - dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9 - dest.isStatic = source.isStatic !== undefined ? source.isStatic : false - dest.ramification = source.ramification !== undefined ? source.ramification : 5 - dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3 - dest.recursionProbability = source.recursionProbability !== undefined ? source.recursionProbability : 0.6 - dest.generateUVs = source.generateUVs !== undefined ? source.generateUVs : false - ;(dest.randomGenerator = source.randomGenerator), - (dest.noiseSeed = source.noiseSeed), - (dest.onDecideSubrayCreation = source.onDecideSubrayCreation), - (dest.onSubrayCreation = source.onSubrayCreation) - - return dest -} - export { LightningStrike } diff --git a/src/geometries/ParametricGeometries.js b/src/geometries/ParametricGeometries.js index e269daa8..db26c6a7 100644 --- a/src/geometries/ParametricGeometries.js +++ b/src/geometries/ParametricGeometries.js @@ -1,10 +1,56 @@ import { Curve, Vector3 } from 'three' import { ParametricGeometry } from './ParametricGeometry' +class TubeGeometry extends ParametricGeometry { + constructor(path, segments = 64, radius = 1, segmentsRadius = 8, closed = false) { + const numpoints = segments + 1 + + const frames = path.computeFrenetFrames(segments, closed), + tangents = frames.tangents, + normals = frames.normals, + binormals = frames.binormals + + const position = new Vector3() + + function ParametricTube(u, v, target) { + v *= 2 * Math.PI + + const i = Math.floor(u * (numpoints - 1)) + + path.getPointAt(u, position) + + const normal = normals[i] + const binormal = binormals[i] + + const cx = -radius * Math.cos(v) // TODO: Hack: Negating it so it faces outside. + const cy = radius * Math.sin(v) + + position.x += cx * normal.x + cy * binormal.x + position.y += cx * normal.y + cy * binormal.y + position.z += cx * normal.z + cy * binormal.z + + target.copy(position) + } + + super(ParametricTube, segments, segmentsRadius) + + // proxy internals + + this.tangents = tangents + this.normals = normals + this.binormals = binormals + + this.path = path + this.segments = segments + this.radius = radius + this.segmentsRadius = segmentsRadius + this.closed = closed + } +} + /** - * Experimenting of primitive geometry creation using Surface Parametric equations + * Experimental primitive geometry creation using Surface Parametric equations */ - const ParametricGeometries = { klein: function (v, u, target) { u *= Math.PI @@ -69,139 +115,68 @@ const ParametricGeometries = { target.set(x, y, z) }, -} - -/********************************************* - * - * Parametric Replacement for TubeGeometry - * - *********************************************/ - -ParametricGeometries.TubeGeometry = class TubeGeometry extends ParametricGeometry { - constructor(path, segments = 64, radius = 1, segmentsRadius = 8, closed = false) { - const numpoints = segments + 1 - - const frames = path.computeFrenetFrames(segments, closed), - tangents = frames.tangents, - normals = frames.normals, - binormals = frames.binormals - - const position = new Vector3() + TubeGeometry, + TorusKnotGeometry: class TorusKnotGeometry extends TubeGeometry { + constructor(radius = 200, tube = 40, segmentsT = 64, segmentsR = 8, p = 2, q = 3) { + class TorusKnotCurve extends Curve { + getPoint(t, optionalTarget = new Vector3()) { + const point = optionalTarget - function ParametricTube(u, v, target) { - v *= 2 * Math.PI + t *= Math.PI * 2 - const i = Math.floor(u * (numpoints - 1)) + const r = 0.5 - path.getPointAt(u, position) + const x = (1 + r * Math.cos(q * t)) * Math.cos(p * t) + const y = (1 + r * Math.cos(q * t)) * Math.sin(p * t) + const z = r * Math.sin(q * t) - const normal = normals[i] - const binormal = binormals[i] + return point.set(x, y, z).multiplyScalar(radius) + } + } - const cx = -radius * Math.cos(v) // TODO: Hack: Negating it so it faces outside. - const cy = radius * Math.sin(v) + const segments = segmentsT + const radiusSegments = segmentsR + const extrudePath = new TorusKnotCurve() - position.x += cx * normal.x + cy * binormal.x - position.y += cx * normal.y + cy * binormal.y - position.z += cx * normal.z + cy * binormal.z + super(extrudePath, segments, tube, radiusSegments, true, false) - target.copy(position) + this.radius = radius + this.tube = tube + this.segmentsT = segmentsT + this.segmentsR = segmentsR + this.p = p + this.q = q } + }, + SphereGeometry: class SphereGeometry extends ParametricGeometry { + constructor(size, u, v) { + function sphere(u, v, target) { + u *= Math.PI + v *= 2 * Math.PI - super(ParametricTube, segments, segmentsRadius) - - // proxy internals - - this.tangents = tangents - this.normals = normals - this.binormals = binormals - - this.path = path - this.segments = segments - this.radius = radius - this.segmentsRadius = segmentsRadius - this.closed = closed - } -} - -/********************************************* - * - * Parametric Replacement for TorusKnotGeometry - * - *********************************************/ -ParametricGeometries.TorusKnotGeometry = class TorusKnotGeometry extends ParametricGeometries.TubeGeometry { - constructor(radius = 200, tube = 40, segmentsT = 64, segmentsR = 8, p = 2, q = 3) { - class TorusKnotCurve extends Curve { - getPoint(t, optionalTarget = new Vector3()) { - const point = optionalTarget - - t *= Math.PI * 2 - - const r = 0.5 - - const x = (1 + r * Math.cos(q * t)) * Math.cos(p * t) - const y = (1 + r * Math.cos(q * t)) * Math.sin(p * t) - const z = r * Math.sin(q * t) + const x = size * Math.sin(u) * Math.cos(v) + const y = size * Math.sin(u) * Math.sin(v) + const z = size * Math.cos(u) - return point.set(x, y, z).multiplyScalar(radius) + target.set(x, y, z) } - } - - const segments = segmentsT - const radiusSegments = segmentsR - const extrudePath = new TorusKnotCurve() - - super(extrudePath, segments, tube, radiusSegments, true, false) - - this.radius = radius - this.tube = tube - this.segmentsT = segmentsT - this.segmentsR = segmentsR - this.p = p - this.q = q - } -} - -/********************************************* - * - * Parametric Replacement for SphereGeometry - * - *********************************************/ -ParametricGeometries.SphereGeometry = class SphereGeometry extends ParametricGeometry { - constructor(size, u, v) { - function sphere(u, v, target) { - u *= Math.PI - v *= 2 * Math.PI - - const x = size * Math.sin(u) * Math.cos(v) - const y = size * Math.sin(u) * Math.sin(v) - const z = size * Math.cos(u) - target.set(x, y, z) + super(sphere, u, v) } + }, + PlaneGeometry: class PlaneGeometry extends ParametricGeometry { + constructor(width, depth, segmentsWidth, segmentsDepth) { + function plane(u, v, target) { + const x = u * width + const y = 0 + const z = v * depth + + target.set(x, y, z) + } - super(sphere, u, v) - } -} - -/********************************************* - * - * Parametric Replacement for PlaneGeometry - * - *********************************************/ - -ParametricGeometries.PlaneGeometry = class PlaneGeometry extends ParametricGeometry { - constructor(width, depth, segmentsWidth, segmentsDepth) { - function plane(u, v, target) { - const x = u * width - const y = 0 - const z = v * depth - - target.set(x, y, z) + super(plane, segmentsWidth, segmentsDepth) } - - super(plane, segmentsWidth, segmentsDepth) - } + }, } export { ParametricGeometries } diff --git a/src/geometries/ParametricGeometry.js b/src/geometries/ParametricGeometry.js index 561947f8..1c239036 100644 --- a/src/geometries/ParametricGeometry.js +++ b/src/geometries/ParametricGeometry.js @@ -1,10 +1,9 @@ +import { BufferGeometry, Float32BufferAttribute, Vector3 } from 'three' + /** * Parametric Surfaces Geometry * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html */ - -import { BufferGeometry, Float32BufferAttribute, Vector3 } from 'three' - class ParametricGeometry extends BufferGeometry { constructor(func = (u, v, target) => target.set(u, v, Math.cos(u) * Math.sin(v)), slices = 8, stacks = 8) { super() diff --git a/src/geometries/RoundedBoxGeometry.js b/src/geometries/RoundedBoxGeometry.js index 3ee4e0e6..3a10f247 100644 --- a/src/geometries/RoundedBoxGeometry.js +++ b/src/geometries/RoundedBoxGeometry.js @@ -1,6 +1,7 @@ import { BoxGeometry, Vector3 } from 'three' -const tempNormal = new Vector3() +const tempNormal = /* @__PURE__ */ new Vector3() + function getUv(faceDirVector, normal, uvAxis, projectionAxis, radius, sideLength) { const totArcLength = (2 * Math.PI * radius) / 4 diff --git a/src/geometries/TextGeometry.ts b/src/geometries/TextGeometry.ts index 673fae79..b1dd5e1d 100644 --- a/src/geometries/TextGeometry.ts +++ b/src/geometries/TextGeometry.ts @@ -30,11 +30,13 @@ export class TextGeometry extends ExtrudeGeometry { } = parameters if (font === undefined) { + // @ts-ignore super() // generate default extrude geometry } else { const shapes = font.generateShapes(text, size, { lineHeight, letterSpacing }) super(shapes, { ...rest, bevelEnabled, bevelSize, bevelThickness, depth: height }) } + // @ts-ignore this.type = 'TextGeometry' } } diff --git a/src/helpers/RectAreaLightHelper.d.ts b/src/helpers/RectAreaLightHelper.d.ts index b0400f1d..80bbd8d9 100644 --- a/src/helpers/RectAreaLightHelper.d.ts +++ b/src/helpers/RectAreaLightHelper.d.ts @@ -1,10 +1,11 @@ -import { Line, RectAreaLight, ColorRepresentation } from 'three' +import { Line, RectAreaLight, Color } from 'three' export class RectAreaLightHelper extends Line { - constructor(light: RectAreaLight, color?: ColorRepresentation) + readonly type: 'RectAreaLightHelper' + constructor(light: RectAreaLight, color?: Color | string | number) light: RectAreaLight - color: ColorRepresentation | undefined + color: Color | string | number | undefined dispose(): void } diff --git a/src/helpers/RectAreaLightHelper.js b/src/helpers/RectAreaLightHelper.js index 927cce8f..3b67c10d 100644 --- a/src/helpers/RectAreaLightHelper.js +++ b/src/helpers/RectAreaLightHelper.js @@ -24,9 +24,9 @@ class RectAreaLightHelper extends Line { super(geometry, material) - this.type = 'RectAreaLightHelper' this.light = light this.color = color // optional hardwired color for the helper + this.type = 'RectAreaLightHelper' // @@ -56,7 +56,9 @@ class RectAreaLightHelper extends Line { this.children[0].material.color.copy(this.material.color) } - this.matrixWorld.copy(this.light.matrixWorld).scale(this.scale) + // ignore world scale on light + this.matrixWorld.extractRotation(this.light.matrixWorld).scale(this.scale).copyPosition(this.light.matrixWorld) + this.children[0].matrixWorld.copy(this.matrixWorld) } diff --git a/src/helpers/VertexNormalsHelper.d.ts b/src/helpers/VertexNormalsHelper.d.ts index 5a2a4244..c783bc48 100644 --- a/src/helpers/VertexNormalsHelper.d.ts +++ b/src/helpers/VertexNormalsHelper.d.ts @@ -1,10 +1,13 @@ import { Object3D, LineSegments } from 'three' export class VertexNormalsHelper extends LineSegments { + readonly type: 'VertexNormalsHelper' constructor(object: Object3D, size?: number, hex?: number) object: Object3D size: number update(): void + + dispose(): void } diff --git a/src/helpers/VertexNormalsHelper.js b/src/helpers/VertexNormalsHelper.js index 29195caf..b586a130 100644 --- a/src/helpers/VertexNormalsHelper.js +++ b/src/helpers/VertexNormalsHelper.js @@ -1,37 +1,22 @@ import { BufferGeometry, Float32BufferAttribute, LineSegments, LineBasicMaterial, Matrix3, Vector3 } from 'three' -const _v1 = new Vector3() -const _v2 = new Vector3() -const _normalMatrix = new Matrix3() +const _v1 = /* @__PURE__ */ new Vector3() +const _v2 = /* @__PURE__ */ new Vector3() +const _normalMatrix = /* @__PURE__ */ new Matrix3() class VertexNormalsHelper extends LineSegments { - constructor(object, size, hex) { + constructor(object, size = 1, color = 0xff0000) { const geometry = new BufferGeometry() - const color = hex !== undefined ? hex : 0xff0000 - - super(geometry, new LineBasicMaterial({ color, toneMapped: false })) - - this.object = object - - this.size = size !== undefined ? size : 0.1 - - let nNormals = 0 - - const objGeometry = this.object.geometry - - if (objGeometry && objGeometry.isGeometry) { - console.error('THREE.VertexNormalsHelper no longer supports Geometry. Use BufferGeometry instead.') - return - } else if (objGeometry && objGeometry.isBufferGeometry) { - nNormals = objGeometry.attributes.normal.count - } - - // + const nNormals = object.geometry.attributes.normal.count const positions = new Float32BufferAttribute(nNormals * 2 * 3, 3) geometry.setAttribute('position', positions) + super(geometry, new LineBasicMaterial({ color, toneMapped: false })) + + this.object = object + this.size = size this.type = 'VertexNormalsHelper' // @@ -54,10 +39,7 @@ class VertexNormalsHelper extends LineSegments { const objGeometry = this.object.geometry - if (objGeometry && objGeometry.isGeometry) { - console.error('THREE.VertexNormalsHelper no longer supports Geometry. Use BufferGeometry instead.') - return - } else if (objGeometry && objGeometry.isBufferGeometry) { + if (objGeometry) { const objPos = objGeometry.attributes.position const objNorm = objGeometry.attributes.normal @@ -67,9 +49,9 @@ class VertexNormalsHelper extends LineSegments { // for simplicity, ignore index and drawcalls, and render every normal for (let j = 0, jl = objPos.count; j < jl; j++) { - _v1.set(objPos.getX(j), objPos.getY(j), objPos.getZ(j)).applyMatrix4(matrixWorld) + _v1.fromBufferAttribute(objPos, j).applyMatrix4(matrixWorld) - _v2.set(objNorm.getX(j), objNorm.getY(j), objNorm.getZ(j)) + _v2.fromBufferAttribute(objNorm, j) _v2.applyMatrix3(_normalMatrix).normalize().multiplyScalar(this.size).add(_v1) @@ -85,6 +67,11 @@ class VertexNormalsHelper extends LineSegments { position.needsUpdate = true } + + dispose() { + this.geometry.dispose() + this.material.dispose() + } } export { VertexNormalsHelper } diff --git a/src/helpers/VertexTangentsHelper.d.ts b/src/helpers/VertexTangentsHelper.d.ts index 43872166..7768fbaf 100644 --- a/src/helpers/VertexTangentsHelper.d.ts +++ b/src/helpers/VertexTangentsHelper.d.ts @@ -1,10 +1,13 @@ import { Object3D, LineSegments } from 'three' export class VertexTangentsHelper extends LineSegments { + readonly type: 'VertexTangentsHelper' constructor(object: Object3D, size?: number, hex?: number) object: Object3D size: number update(): void + + dispose(): void } diff --git a/src/helpers/VertexTangentsHelper.js b/src/helpers/VertexTangentsHelper.js index 3333ce63..a9af184a 100644 --- a/src/helpers/VertexTangentsHelper.js +++ b/src/helpers/VertexTangentsHelper.js @@ -1,37 +1,22 @@ import { BufferGeometry, Float32BufferAttribute, LineSegments, LineBasicMaterial, Vector3 } from 'three' -const _v1 = new Vector3() -const _v2 = new Vector3() +const _v1 = /* @__PURE__ */ new Vector3() +const _v2 = /* @__PURE__ */ new Vector3() class VertexTangentsHelper extends LineSegments { - constructor(object, size, hex) { - const color = hex !== undefined ? hex : 0x00ffff - - // - - const objGeometry = object.geometry - - if (!(objGeometry && objGeometry.isBufferGeometry)) { - console.error('THREE.VertexTangentsHelper: geometry not an instance of THREE.BufferGeometry.', objGeometry) - return - } - - const nTangents = objGeometry.attributes.tangent.count - - // - + constructor(object, size = 1, color = 0x00ffff) { const geometry = new BufferGeometry() + const nTangents = object.geometry.attributes.tangent.count const positions = new Float32BufferAttribute(nTangents * 2 * 3, 3) geometry.setAttribute('position', positions) super(geometry, new LineBasicMaterial({ color, toneMapped: false })) - this.type = 'VertexTangentsHelper' this.object = object - - this.size = size !== undefined ? size : 1 + this.size = size + this.type = 'VertexTangentsHelper' // @@ -60,9 +45,9 @@ class VertexTangentsHelper extends LineSegments { // for simplicity, ignore index and drawcalls, and render every tangent for (let j = 0, jl = objPos.count; j < jl; j++) { - _v1.set(objPos.getX(j), objPos.getY(j), objPos.getZ(j)).applyMatrix4(matrixWorld) + _v1.fromBufferAttribute(objPos, j).applyMatrix4(matrixWorld) - _v2.set(objTan.getX(j), objTan.getY(j), objTan.getZ(j)) + _v2.fromBufferAttribute(objTan, j) _v2.transformDirection(matrixWorld).multiplyScalar(this.size).add(_v1) @@ -77,6 +62,11 @@ class VertexTangentsHelper extends LineSegments { position.needsUpdate = true } + + dispose() { + this.geometry.dispose() + this.material.dispose() + } } export { VertexTangentsHelper } diff --git a/src/index.ts b/src/index.ts index ad6e4945..60f76649 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,3 @@ -// @ts-nocheck -export * from './offscreen/scene' -export * from './offscreen/offscreen' -export * from './offscreen/jank' export * from './misc/MD2CharacterComplex' export * from './misc/ConvexObjectBreaker' export * from './misc/MorphBlendMesh' @@ -17,62 +13,6 @@ export * from './misc/TubePainter' export * from './misc/Volume' export * from './misc/ProgressiveLightmap' export * from './renderers/CSS2DRenderer' -export * from './renderers/nodes/core/constants' -export * from './renderers/nodes/core/NodeBuilder' -export * from './renderers/nodes/core/InputNode' -export * from './renderers/nodes/core/NodeUniform' -export * from './renderers/nodes/core/NodeVary' -export * from './renderers/nodes/core/NodeFrame' -export * from './renderers/nodes/core/Node' -export * from './renderers/nodes/core/NodeSlot' -export * from './renderers/nodes/core/NodeAttribute' -export * from './renderers/nodes/core/VaryNode' -export * from './renderers/nodes/core/AttributeNode' -export * from './renderers/nodes/accessors/CameraNode' -export * from './renderers/nodes/accessors/UVNode' -export * from './renderers/nodes/accessors/ModelNode' -export * from './renderers/nodes/accessors/NormalNode' -export * from './renderers/nodes/accessors/PositionNode' -export * from './renderers/nodes/accessors/ModelViewProjectionNode' -export * from './renderers/nodes/utils/SwitchNode' -export * from './renderers/nodes/utils/TimerNode' -export * from './renderers/nodes/math/OperatorNode' -export * from './renderers/nodes/math/MathNode' -export * from './renderers/nodes/inputs/ColorNode' -export * from './renderers/nodes/inputs/Vector3Node' -export * from './renderers/nodes/inputs/Matrix4Node' -export * from './renderers/nodes/inputs/TextureNode' -export * from './renderers/nodes/inputs/FloatNode' -export * from './renderers/nodes/inputs/Vector2Node' -export * from './renderers/nodes/inputs/Vector4Node' -export * from './renderers/nodes/inputs/Matrix3Node' -export * from './renderers/webgpu/constants' -export * from './renderers/webgpu/WebGPUTextures' -export * from './renderers/webgpu/WebGPUInfo' -export * from './renderers/webgpu/WebGPURenderLists' -export * from './renderers/webgpu/WebGPUTextureRenderer' -export * from './renderers/webgpu/WebGPURenderPipelines' -export * from './renderers/webgpu/WebGPUGeometries' -export * from './renderers/webgpu/nodes/WebGPUNodeBuilder' -export * from './renderers/webgpu/nodes/WebGPUNodeUniform' -export * from './renderers/webgpu/nodes/WebGPUNodeUniformsGroup' -export * from './renderers/webgpu/nodes/ShaderLib' -export * from './renderers/webgpu/nodes/WebGPUNodes' -export * from './renderers/webgpu/WebGPUBindings' -export * from './renderers/webgpu/WebGPUSampledTexture' -export * from './renderers/webgpu/WebGPU' -export * from './renderers/webgpu/WebGPUUniformsGroup' -export * from './renderers/webgpu/WebGPURenderer' -export * from './renderers/webgpu/WebGPUComputePipelines' -export * from './renderers/webgpu/WebGPUUniform' -export * from './renderers/webgpu/WebGPUObjects' -export * from './renderers/webgpu/WebGPUSampler' -export * from './renderers/webgpu/WebGPUBackground' -export * from './renderers/webgpu/WebGPUBinding' -export * from './renderers/webgpu/WebGPUProperties' -export * from './renderers/webgpu/WebGPUAttributes' -export * from './renderers/webgpu/WebGPUStorageBuffer' -export * from './renderers/webgpu/WebGPUTextureUtils' export * from './renderers/CSS3DRenderer' export * from './renderers/Projector' export * from './renderers/SVGRenderer' @@ -81,7 +21,6 @@ export * from './modifiers/CurveModifier' export * from './modifiers/SimplifyModifier' export * from './modifiers/EdgeSplitModifier' export * from './modifiers/TessellateModifier' -export * from './nodes/Nodes' export * from './exporters/GLTFExporter' export * from './exporters/USDZExporter' export * from './exporters/PLYExporter' @@ -193,8 +132,7 @@ export * from './shaders/BasicShader' export * from './shaders/BleachBypassShader' export * from './shaders/BlendShader' export * from './shaders/BokehShader' -export { BokehShader2 } from './shaders/BokehShader2' -export type { BokehShaderUniforms as BokehShader2Uniforms } from './shaders/BokehShader2' +export * from './shaders/BokehShader2' export * from './shaders/BrightnessContrastShader' export * from './shaders/ColorCorrectionShader' export * from './shaders/ColorifyShader' @@ -282,9 +220,6 @@ export * from './loaders/BasisTextureLoader' export * from './loaders/TDSLoader' export * from './loaders/LDrawLoader' export * from './loaders/GLTFLoader' -export * from './loaders/lwo/LWO3Parser' -export * from './loaders/lwo/LWO2Parser' -export * from './loaders/lwo/IFFParser' export * from './loaders/SVGLoader' export * from './loaders/3DMLoader' export * from './loaders/OBJLoader' @@ -299,7 +234,6 @@ export * from './loaders/PDBLoader' export * from './loaders/PRWMLoader' export * from './loaders/RGBMLoader' export * from './loaders/VOXLoader' -export * from './loaders/NodeMaterialLoader' export * from './loaders/PCDLoader' export * from './loaders/LWOLoader' export * from './loaders/PLYLoader' diff --git a/src/interactive/InteractiveGroup.js b/src/interactive/InteractiveGroup.js index 704f4ffb..263e979b 100644 --- a/src/interactive/InteractiveGroup.js +++ b/src/interactive/InteractiveGroup.js @@ -1,6 +1,6 @@ import { Group, Matrix4, Raycaster, Vector2 } from 'three' -const _pointer = new Vector2() +const _pointer = /* @__PURE__ */ new Vector2() const _event = { type: '', data: _pointer } class InteractiveGroup extends Group { diff --git a/src/interactive/SelectionBox.js b/src/interactive/SelectionBox.js index 3d73ee13..dda5dd6e 100644 --- a/src/interactive/SelectionBox.js +++ b/src/interactive/SelectionBox.js @@ -1,173 +1,165 @@ import { Frustum, Vector3 } from 'three' -/** - * This is a class to check whether objects are in a selection area in 3D space - */ - -const SelectionBox = (() => { - const frustum = new Frustum() - const center = new Vector3() - - const tmpPoint = new Vector3() - - const vecNear = new Vector3() - const vecTopLeft = new Vector3() - const vecTopRight = new Vector3() - const vecDownRight = new Vector3() - const vecDownLeft = new Vector3() - - const vecFarTopLeft = new Vector3() - const vecFarTopRight = new Vector3() - const vecFarDownRight = new Vector3() - const vecFarDownLeft = new Vector3() - - const vectemp1 = new Vector3() - const vectemp2 = new Vector3() - const vectemp3 = new Vector3() - - class SelectionBox { - constructor(camera, scene, deep) { - this.camera = camera - this.scene = scene - this.startPoint = new Vector3() - this.endPoint = new Vector3() - this.collection = [] - this.deep = deep || Number.MAX_VALUE - } +const frustum = /* @__PURE__ */ new Frustum() +const center = /* @__PURE__ */ new Vector3() + +const tmpPoint = /* @__PURE__ */ new Vector3() + +const vecNear = /* @__PURE__ */ new Vector3() +const vecTopLeft = /* @__PURE__ */ new Vector3() +const vecTopRight = /* @__PURE__ */ new Vector3() +const vecDownRight = /* @__PURE__ */ new Vector3() +const vecDownLeft = /* @__PURE__ */ new Vector3() + +const vecFarTopLeft = /* @__PURE__ */ new Vector3() +const vecFarTopRight = /* @__PURE__ */ new Vector3() +const vecFarDownRight = /* @__PURE__ */ new Vector3() +const vecFarDownLeft = /* @__PURE__ */ new Vector3() + +const vectemp1 = /* @__PURE__ */ new Vector3() +const vectemp2 = /* @__PURE__ */ new Vector3() +const vectemp3 = /* @__PURE__ */ new Vector3() + +class SelectionBox { + constructor(camera, scene, deep) { + this.camera = camera + this.scene = scene + this.startPoint = new Vector3() + this.endPoint = new Vector3() + this.collection = [] + this.deep = deep || Number.MAX_VALUE + } - select(startPoint, endPoint) { - this.startPoint = startPoint || this.startPoint - this.endPoint = endPoint || this.endPoint - this.collection = [] + select(startPoint, endPoint) { + this.startPoint = startPoint || this.startPoint + this.endPoint = endPoint || this.endPoint + this.collection = [] - this.updateFrustum(this.startPoint, this.endPoint) - this.searchChildInFrustum(frustum, this.scene) + this.updateFrustum(this.startPoint, this.endPoint) + this.searchChildInFrustum(frustum, this.scene) - return this.collection - } + return this.collection + } - updateFrustum(startPoint, endPoint) { - startPoint = startPoint || this.startPoint - endPoint = endPoint || this.endPoint + updateFrustum(startPoint, endPoint) { + startPoint = startPoint || this.startPoint + endPoint = endPoint || this.endPoint - // Avoid invalid frustum + // Avoid invalid frustum - if (startPoint.x === endPoint.x) { - endPoint.x += Number.EPSILON - } + if (startPoint.x === endPoint.x) { + endPoint.x += Number.EPSILON + } - if (startPoint.y === endPoint.y) { - endPoint.y += Number.EPSILON - } + if (startPoint.y === endPoint.y) { + endPoint.y += Number.EPSILON + } - this.camera.updateProjectionMatrix() - this.camera.updateMatrixWorld() - - if (this.camera.isPerspectiveCamera) { - tmpPoint.copy(startPoint) - tmpPoint.x = Math.min(startPoint.x, endPoint.x) - tmpPoint.y = Math.max(startPoint.y, endPoint.y) - endPoint.x = Math.max(startPoint.x, endPoint.x) - endPoint.y = Math.min(startPoint.y, endPoint.y) - - vecNear.setFromMatrixPosition(this.camera.matrixWorld) - vecTopLeft.copy(tmpPoint) - vecTopRight.set(endPoint.x, tmpPoint.y, 0) - vecDownRight.copy(endPoint) - vecDownLeft.set(tmpPoint.x, endPoint.y, 0) - - vecTopLeft.unproject(this.camera) - vecTopRight.unproject(this.camera) - vecDownRight.unproject(this.camera) - vecDownLeft.unproject(this.camera) - - vectemp1.copy(vecTopLeft).sub(vecNear) - vectemp2.copy(vecTopRight).sub(vecNear) - vectemp3.copy(vecDownRight).sub(vecNear) - vectemp1.normalize() - vectemp2.normalize() - vectemp3.normalize() - - vectemp1.multiplyScalar(this.deep) - vectemp2.multiplyScalar(this.deep) - vectemp3.multiplyScalar(this.deep) - vectemp1.add(vecNear) - vectemp2.add(vecNear) - vectemp3.add(vecNear) - - var planes = frustum.planes - - planes[0].setFromCoplanarPoints(vecNear, vecTopLeft, vecTopRight) - planes[1].setFromCoplanarPoints(vecNear, vecTopRight, vecDownRight) - planes[2].setFromCoplanarPoints(vecDownRight, vecDownLeft, vecNear) - planes[3].setFromCoplanarPoints(vecDownLeft, vecTopLeft, vecNear) - planes[4].setFromCoplanarPoints(vecTopRight, vecDownRight, vecDownLeft) - planes[5].setFromCoplanarPoints(vectemp3, vectemp2, vectemp1) - planes[5].normal.multiplyScalar(-1) - } else if (this.camera.isOrthographicCamera) { - const left = Math.min(startPoint.x, endPoint.x) - const top = Math.max(startPoint.y, endPoint.y) - const right = Math.max(startPoint.x, endPoint.x) - const down = Math.min(startPoint.y, endPoint.y) - - vecTopLeft.set(left, top, -1) - vecTopRight.set(right, top, -1) - vecDownRight.set(right, down, -1) - vecDownLeft.set(left, down, -1) - - vecFarTopLeft.set(left, top, 1) - vecFarTopRight.set(right, top, 1) - vecFarDownRight.set(right, down, 1) - vecFarDownLeft.set(left, down, 1) - - vecTopLeft.unproject(this.camera) - vecTopRight.unproject(this.camera) - vecDownRight.unproject(this.camera) - vecDownLeft.unproject(this.camera) - - vecFarTopLeft.unproject(this.camera) - vecFarTopRight.unproject(this.camera) - vecFarDownRight.unproject(this.camera) - vecFarDownLeft.unproject(this.camera) - - var planes = frustum.planes - - planes[0].setFromCoplanarPoints(vecTopLeft, vecFarTopLeft, vecFarTopRight) - planes[1].setFromCoplanarPoints(vecTopRight, vecFarTopRight, vecFarDownRight) - planes[2].setFromCoplanarPoints(vecFarDownRight, vecFarDownLeft, vecDownLeft) - planes[3].setFromCoplanarPoints(vecFarDownLeft, vecFarTopLeft, vecTopLeft) - planes[4].setFromCoplanarPoints(vecTopRight, vecDownRight, vecDownLeft) - planes[5].setFromCoplanarPoints(vecFarDownRight, vecFarTopRight, vecFarTopLeft) - planes[5].normal.multiplyScalar(-1) - } else { - console.error('THREE.SelectionBox: Unsupported camera type.') - } + this.camera.updateProjectionMatrix() + this.camera.updateMatrixWorld() + + if (this.camera.isPerspectiveCamera) { + tmpPoint.copy(startPoint) + tmpPoint.x = Math.min(startPoint.x, endPoint.x) + tmpPoint.y = Math.max(startPoint.y, endPoint.y) + endPoint.x = Math.max(startPoint.x, endPoint.x) + endPoint.y = Math.min(startPoint.y, endPoint.y) + + vecNear.setFromMatrixPosition(this.camera.matrixWorld) + vecTopLeft.copy(tmpPoint) + vecTopRight.set(endPoint.x, tmpPoint.y, 0) + vecDownRight.copy(endPoint) + vecDownLeft.set(tmpPoint.x, endPoint.y, 0) + + vecTopLeft.unproject(this.camera) + vecTopRight.unproject(this.camera) + vecDownRight.unproject(this.camera) + vecDownLeft.unproject(this.camera) + + vectemp1.copy(vecTopLeft).sub(vecNear) + vectemp2.copy(vecTopRight).sub(vecNear) + vectemp3.copy(vecDownRight).sub(vecNear) + vectemp1.normalize() + vectemp2.normalize() + vectemp3.normalize() + + vectemp1.multiplyScalar(this.deep) + vectemp2.multiplyScalar(this.deep) + vectemp3.multiplyScalar(this.deep) + vectemp1.add(vecNear) + vectemp2.add(vecNear) + vectemp3.add(vecNear) + + var planes = frustum.planes + + planes[0].setFromCoplanarPoints(vecNear, vecTopLeft, vecTopRight) + planes[1].setFromCoplanarPoints(vecNear, vecTopRight, vecDownRight) + planes[2].setFromCoplanarPoints(vecDownRight, vecDownLeft, vecNear) + planes[3].setFromCoplanarPoints(vecDownLeft, vecTopLeft, vecNear) + planes[4].setFromCoplanarPoints(vecTopRight, vecDownRight, vecDownLeft) + planes[5].setFromCoplanarPoints(vectemp3, vectemp2, vectemp1) + planes[5].normal.multiplyScalar(-1) + } else if (this.camera.isOrthographicCamera) { + const left = Math.min(startPoint.x, endPoint.x) + const top = Math.max(startPoint.y, endPoint.y) + const right = Math.max(startPoint.x, endPoint.x) + const down = Math.min(startPoint.y, endPoint.y) + + vecTopLeft.set(left, top, -1) + vecTopRight.set(right, top, -1) + vecDownRight.set(right, down, -1) + vecDownLeft.set(left, down, -1) + + vecFarTopLeft.set(left, top, 1) + vecFarTopRight.set(right, top, 1) + vecFarDownRight.set(right, down, 1) + vecFarDownLeft.set(left, down, 1) + + vecTopLeft.unproject(this.camera) + vecTopRight.unproject(this.camera) + vecDownRight.unproject(this.camera) + vecDownLeft.unproject(this.camera) + + vecFarTopLeft.unproject(this.camera) + vecFarTopRight.unproject(this.camera) + vecFarDownRight.unproject(this.camera) + vecFarDownLeft.unproject(this.camera) + + var planes = frustum.planes + + planes[0].setFromCoplanarPoints(vecTopLeft, vecFarTopLeft, vecFarTopRight) + planes[1].setFromCoplanarPoints(vecTopRight, vecFarTopRight, vecFarDownRight) + planes[2].setFromCoplanarPoints(vecFarDownRight, vecFarDownLeft, vecDownLeft) + planes[3].setFromCoplanarPoints(vecFarDownLeft, vecFarTopLeft, vecTopLeft) + planes[4].setFromCoplanarPoints(vecTopRight, vecDownRight, vecDownLeft) + planes[5].setFromCoplanarPoints(vecFarDownRight, vecFarTopRight, vecFarTopLeft) + planes[5].normal.multiplyScalar(-1) + } else { + console.error('THREE.SelectionBox: Unsupported camera type.') } + } - searchChildInFrustum(frustum, object) { - if (object.isMesh || object.isLine || object.isPoints) { - if (object.material !== undefined) { - if (object.geometry.boundingSphere === null) object.geometry.computeBoundingSphere() + searchChildInFrustum(frustum, object) { + if (object.isMesh || object.isLine || object.isPoints) { + if (object.material !== undefined) { + if (object.geometry.boundingSphere === null) object.geometry.computeBoundingSphere() - center.copy(object.geometry.boundingSphere.center) + center.copy(object.geometry.boundingSphere.center) - center.applyMatrix4(object.matrixWorld) + center.applyMatrix4(object.matrixWorld) - if (frustum.containsPoint(center)) { - this.collection.push(object) - } + if (frustum.containsPoint(center)) { + this.collection.push(object) } } + } - if (object.children.length > 0) { - for (let x = 0; x < object.children.length; x++) { - this.searchChildInFrustum(frustum, object.children[x]) - } + if (object.children.length > 0) { + for (let x = 0; x < object.children.length; x++) { + this.searchChildInFrustum(frustum, object.children[x]) } } } - - return SelectionBox -})() +} export { SelectionBox } diff --git a/src/interactive/SelectionHelper.js b/src/interactive/SelectionHelper.js index 892bb85a..3921bfa4 100644 --- a/src/interactive/SelectionHelper.js +++ b/src/interactive/SelectionHelper.js @@ -1,67 +1,63 @@ import { Vector2 } from 'three' -const SelectionHelper = (() => { - class SelectionHelper { - constructor(selectionBox, renderer, cssClassName) { - this.element = document.createElement('div') - this.element.classList.add(cssClassName) - this.element.style.pointerEvents = 'none' +class SelectionHelper { + constructor(selectionBox, renderer, cssClassName) { + this.element = document.createElement('div') + this.element.classList.add(cssClassName) + this.element.style.pointerEvents = 'none' - this.renderer = renderer + this.renderer = renderer - this.startPoint = new Vector2() - this.pointTopLeft = new Vector2() - this.pointBottomRight = new Vector2() + this.startPoint = new Vector2() + this.pointTopLeft = new Vector2() + this.pointBottomRight = new Vector2() - this.isDown = false - - this.renderer.domElement.addEventListener('pointerdown', (event) => { - this.isDown = true - this.onSelectStart(event) - }) + this.isDown = false - this.renderer.domElement.addEventListener('pointermove', (event) => { - if (this.isDown) { - this.onSelectMove(event) - } - }) + this.renderer.domElement.addEventListener('pointerdown', (event) => { + this.isDown = true + this.onSelectStart(event) + }) - this.renderer.domElement.addEventListener('pointerup', (event) => { - this.isDown = false - this.onSelectOver(event) - }) - } + this.renderer.domElement.addEventListener('pointermove', (event) => { + if (this.isDown) { + this.onSelectMove(event) + } + }) - onSelectStart(event) { - this.renderer.domElement.parentElement.appendChild(this.element) + this.renderer.domElement.addEventListener('pointerup', (event) => { + this.isDown = false + this.onSelectOver(event) + }) + } - this.element.style.left = `${event.clientX}px` - this.element.style.top = `${event.clientY}px` - this.element.style.width = '0px' - this.element.style.height = '0px' + onSelectStart(event) { + this.renderer.domElement.parentElement.appendChild(this.element) - this.startPoint.x = event.clientX - this.startPoint.y = event.clientY - } + this.element.style.left = `${event.clientX}px` + this.element.style.top = `${event.clientY}px` + this.element.style.width = '0px' + this.element.style.height = '0px' - onSelectMove(event) { - this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX) - this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY) - this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX) - this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY) + this.startPoint.x = event.clientX + this.startPoint.y = event.clientY + } - this.element.style.left = `${this.pointTopLeft.x}px` - this.element.style.top = `${this.pointTopLeft.y}px` - this.element.style.width = `${this.pointBottomRight.x - this.pointTopLeft.x}px` - this.element.style.height = `${this.pointBottomRight.y - this.pointTopLeft.y}px` - } + onSelectMove(event) { + this.pointBottomRight.x = Math.max(this.startPoint.x, event.clientX) + this.pointBottomRight.y = Math.max(this.startPoint.y, event.clientY) + this.pointTopLeft.x = Math.min(this.startPoint.x, event.clientX) + this.pointTopLeft.y = Math.min(this.startPoint.y, event.clientY) - onSelectOver() { - this.element.parentElement.removeChild(this.element) - } + this.element.style.left = `${this.pointTopLeft.x}px` + this.element.style.top = `${this.pointTopLeft.y}px` + this.element.style.width = `${this.pointBottomRight.x - this.pointTopLeft.x}px` + this.element.style.height = `${this.pointBottomRight.y - this.pointTopLeft.y}px` } - return SelectionHelper -})() + onSelectOver() { + this.element.parentElement.removeChild(this.element) + } +} export { SelectionHelper } diff --git a/src/libs/MotionControllers.ts b/src/libs/MotionControllers.ts index 315b8ca3..41f6617a 100644 --- a/src/libs/MotionControllers.ts +++ b/src/libs/MotionControllers.ts @@ -2,7 +2,7 @@ * @webxr-input-profiles/motion-controllers 1.0.0 https://github.com/immersive-web/webxr-input-profiles */ -import type { Object3D, XRGamepad, XRHandedness, XRInputSource } from 'three' +import type { Object3D } from 'three' interface GamepadIndices { button: number @@ -54,41 +54,41 @@ interface ProfilesList { } const MotionControllerConstants = { - Handedness: Object.freeze({ + Handedness: { NONE: 'none', LEFT: 'left', RIGHT: 'right', - }), + }, - ComponentState: Object.freeze({ + ComponentState: { DEFAULT: 'default', TOUCHED: 'touched', PRESSED: 'pressed', - }), + }, - ComponentProperty: Object.freeze({ + ComponentProperty: { BUTTON: 'button', X_AXIS: 'xAxis', Y_AXIS: 'yAxis', STATE: 'state', - }), + }, - ComponentType: Object.freeze({ + ComponentType: { TRIGGER: 'trigger', SQUEEZE: 'squeeze', TOUCHPAD: 'touchpad', THUMBSTICK: 'thumbstick', BUTTON: 'button', - }), + }, ButtonTouchThreshold: 0.05, AxisTouchThreshold: 0.1, - VisualResponseProperty: Object.freeze({ + VisualResponseProperty: { TRANSFORM: 'transform', VISIBILITY: 'visibility', - }), + }, } /** @@ -133,7 +133,7 @@ async function fetchProfile( // Find the relative path to the first requested profile that is recognized let match: { profileId: string; profilePath: string; deprecated: boolean } | undefined = undefined - xrInputSource.profiles.some((profileId) => { + xrInputSource.profiles.some((profileId: string) => { const supportedProfile = supportedProfilesList[profileId] if (supportedProfile) { match = { @@ -366,7 +366,7 @@ class Component implements ComponentDescription { * @description Poll for updated data based on current gamepad state * @param {Object} gamepad - The gamepad object from which the component data should be polled */ - updateFromGamepad(gamepad: XRGamepad): void { + updateFromGamepad(gamepad: Gamepad): void { // Set the state to default before processing other data sources this.values.state = MotionControllerConstants.ComponentState.DEFAULT @@ -374,13 +374,13 @@ class Component implements ComponentDescription { if (this.gamepadIndices.button !== undefined && gamepad.buttons.length > this.gamepadIndices.button) { const gamepadButton = gamepad.buttons[this.gamepadIndices.button] this.values.button = gamepadButton.value - this.values.button = this.values.button < 0 ? 0 : this.values.button - this.values.button = this.values.button > 1 ? 1 : this.values.button + this.values.button = this.values.button! < 0 ? 0 : this.values.button + this.values.button = this.values.button! > 1 ? 1 : this.values.button // Set the state based on the button if (gamepadButton.pressed || this.values.button === 1) { this.values.state = MotionControllerConstants.ComponentState.PRESSED - } else if (gamepadButton.touched || this.values.button > MotionControllerConstants.ButtonTouchThreshold) { + } else if (gamepadButton.touched || this.values.button! > MotionControllerConstants.ButtonTouchThreshold) { this.values.state = MotionControllerConstants.ComponentState.TOUCHED } } @@ -388,13 +388,13 @@ class Component implements ComponentDescription { // Get and normalize x axis value if (this.gamepadIndices.xAxis !== undefined && gamepad.axes.length > this.gamepadIndices.xAxis) { this.values.xAxis = gamepad.axes[this.gamepadIndices.xAxis] - this.values.xAxis = this.values.xAxis < -1 ? -1 : this.values.xAxis - this.values.xAxis = this.values.xAxis > 1 ? 1 : this.values.xAxis + this.values.xAxis = this.values.xAxis! < -1 ? -1 : this.values.xAxis + this.values.xAxis = this.values.xAxis! > 1 ? 1 : this.values.xAxis // If the state is still default, check if the xAxis makes it touched if ( this.values.state === MotionControllerConstants.ComponentState.DEFAULT && - Math.abs(this.values.xAxis) > MotionControllerConstants.AxisTouchThreshold + Math.abs(this.values.xAxis!) > MotionControllerConstants.AxisTouchThreshold ) { this.values.state = MotionControllerConstants.ComponentState.TOUCHED } @@ -403,13 +403,13 @@ class Component implements ComponentDescription { // Get and normalize Y axis value if (this.gamepadIndices.yAxis !== undefined && gamepad.axes.length > this.gamepadIndices.yAxis) { this.values.yAxis = gamepad.axes[this.gamepadIndices.yAxis] - this.values.yAxis = this.values.yAxis < -1 ? -1 : this.values.yAxis - this.values.yAxis = this.values.yAxis > 1 ? 1 : this.values.yAxis + this.values.yAxis = this.values.yAxis! < -1 ? -1 : this.values.yAxis + this.values.yAxis = this.values.yAxis! > 1 ? 1 : this.values.yAxis // If the state is still default, check if the yAxis makes it touched if ( this.values.state === MotionControllerConstants.ComponentState.DEFAULT && - Math.abs(this.values.yAxis) > MotionControllerConstants.AxisTouchThreshold + Math.abs(this.values.yAxis!) > MotionControllerConstants.AxisTouchThreshold ) { this.values.state = MotionControllerConstants.ComponentState.TOUCHED } @@ -491,7 +491,7 @@ class MotionController { */ updateFromGamepad(): void { Object.values(this.components).forEach((component) => { - component.updateFromGamepad(this.xrInputSource.gamepad) + component.updateFromGamepad(this.xrInputSource.gamepad!) }) } } diff --git a/src/lights/LightProbeGenerator.js b/src/lights/LightProbeGenerator.js index 0a31c556..216438e4 100644 --- a/src/lights/LightProbeGenerator.js +++ b/src/lights/LightProbeGenerator.js @@ -1,46 +1,43 @@ import { Color, LightProbe, SphericalHarmonics3, Vector3 } from 'three' -var LightProbeGenerator = { +class LightProbeGenerator { // https://www.ppsloan.org/publications/StupidSH36.pdf - fromCubeTexture: function (cubeTexture) { - var norm, - lengthSq, - weight, - totalWeight = 0 + static fromCubeTexture(cubeTexture) { + let totalWeight = 0 - var coord = new Vector3() + const coord = new Vector3() - var dir = new Vector3() + const dir = new Vector3() - var color = new Color() + const color = new Color() - var shBasis = [0, 0, 0, 0, 0, 0, 0, 0, 0] + const shBasis = [0, 0, 0, 0, 0, 0, 0, 0, 0] - var sh = new SphericalHarmonics3() - var shCoefficients = sh.coefficients + const sh = new SphericalHarmonics3() + const shCoefficients = sh.coefficients for (let faceIndex = 0; faceIndex < 6; faceIndex++) { - var image = cubeTexture.image[faceIndex] + const image = cubeTexture.image[faceIndex] - var width = image.width - var height = image.height + const width = image.width + const height = image.height - var canvas = document.createElement('canvas') + const canvas = document.createElement('canvas') canvas.width = width canvas.height = height - var context = canvas.getContext('2d') + const context = canvas.getContext('2d') context.drawImage(image, 0, 0, width, height) - var imageData = context.getImageData(0, 0, width, height) + const imageData = context.getImageData(0, 0, width, height) - var data = imageData.data + const data = imageData.data - var imageWidth = imageData.width // assumed to be square + const imageWidth = imageData.width // assumed to be square - var pixelSize = 2 / imageWidth + const pixelSize = 2 / imageWidth for (let i = 0, il = data.length; i < il; i += 4) { // RGBA assumed @@ -60,11 +57,11 @@ var LightProbeGenerator = { // pixel coordinate on unit cube - var pixelIndex = i / 4 + const pixelIndex = i / 4 - var col = -1 + ((pixelIndex % imageWidth) + 0.5) * pixelSize + const col = -1 + ((pixelIndex % imageWidth) + 0.5) * pixelSize - var row = 1 - (Math.floor(pixelIndex / imageWidth) + 0.5) * pixelSize + const row = 1 - (Math.floor(pixelIndex / imageWidth) + 0.5) * pixelSize switch (faceIndex) { case 0: @@ -94,9 +91,9 @@ var LightProbeGenerator = { // weight assigned to this pixel - lengthSq = coord.lengthSq() + const lengthSq = coord.lengthSq() - weight = 4 / (Math.sqrt(lengthSq) * lengthSq) + const weight = 4 / (Math.sqrt(lengthSq) * lengthSq) totalWeight += weight @@ -116,7 +113,7 @@ var LightProbeGenerator = { } // normalize - norm = (4 * Math.PI) / totalWeight + const norm = (4 * Math.PI) / totalWeight for (let j = 0; j < 9; j++) { shCoefficients[j].x *= norm @@ -125,32 +122,29 @@ var LightProbeGenerator = { } return new LightProbe(sh) - }, + } - fromCubeRenderTarget: function (renderer, cubeRenderTarget) { + static fromCubeRenderTarget(renderer, cubeRenderTarget) { // The renderTarget must be set to RGBA in order to make readRenderTargetPixels works - var norm, - lengthSq, - weight, - totalWeight = 0 + let totalWeight = 0 - var coord = new Vector3() + const coord = new Vector3() - var dir = new Vector3() + const dir = new Vector3() - var color = new Color() + const color = new Color() - var shBasis = [0, 0, 0, 0, 0, 0, 0, 0, 0] + const shBasis = [0, 0, 0, 0, 0, 0, 0, 0, 0] - var sh = new SphericalHarmonics3() - var shCoefficients = sh.coefficients + const sh = new SphericalHarmonics3() + const shCoefficients = sh.coefficients for (let faceIndex = 0; faceIndex < 6; faceIndex++) { - var imageWidth = cubeRenderTarget.width // assumed to be square - var data = new Uint8Array(imageWidth * imageWidth * 4) + const imageWidth = cubeRenderTarget.width // assumed to be square + const data = new Uint8Array(imageWidth * imageWidth * 4) renderer.readRenderTargetPixels(cubeRenderTarget, 0, 0, imageWidth, imageWidth, data, faceIndex) - var pixelSize = 2 / imageWidth + const pixelSize = 2 / imageWidth for (let i = 0, il = data.length; i < il; i += 4) { // RGBA assumed @@ -170,11 +164,11 @@ var LightProbeGenerator = { // pixel coordinate on unit cube - var pixelIndex = i / 4 + const pixelIndex = i / 4 - var col = -1 + ((pixelIndex % imageWidth) + 0.5) * pixelSize + const col = -1 + ((pixelIndex % imageWidth) + 0.5) * pixelSize - var row = 1 - (Math.floor(pixelIndex / imageWidth) + 0.5) * pixelSize + const row = 1 - (Math.floor(pixelIndex / imageWidth) + 0.5) * pixelSize switch (faceIndex) { case 0: @@ -204,9 +198,9 @@ var LightProbeGenerator = { // weight assigned to this pixel - lengthSq = coord.lengthSq() + const lengthSq = coord.lengthSq() - weight = 4 / (Math.sqrt(lengthSq) * lengthSq) + const weight = 4 / (Math.sqrt(lengthSq) * lengthSq) totalWeight += weight @@ -226,7 +220,7 @@ var LightProbeGenerator = { } // normalize - norm = (4 * Math.PI) / totalWeight + const norm = (4 * Math.PI) / totalWeight for (let j = 0; j < 9; j++) { shCoefficients[j].x *= norm @@ -235,7 +229,7 @@ var LightProbeGenerator = { } return new LightProbe(sh) - }, + } } export { LightProbeGenerator } diff --git a/src/lights/RectAreaLightUniformsLib.js b/src/lights/RectAreaLightUniformsLib.js index d3e5ef12..c14634f1 100644 --- a/src/lights/RectAreaLightUniformsLib.js +++ b/src/lights/RectAreaLightUniformsLib.js @@ -28,11 +28,11 @@ import { // by Eric Heitz, Jonathan Dupuy, Stephen Hill and David Neubelt // code: https://github.com/selfshadow/ltc_code/ -var RectAreaLightUniformsLib = { - init: function () { +class RectAreaLightUniformsLib { + static init() { // source: https://github.com/selfshadow/ltc_code/tree/master/fit/results/ltc.js - var LTC_MAT_1 = [ + const LTC_MAT_1 = [ 1, 0, 0, @@ -16419,7 +16419,7 @@ var RectAreaLightUniformsLib = { 1.6577, ] - var LTC_MAT_2 = [ + const LTC_MAT_2 = [ 1, 0, 0, @@ -32838,6 +32838,9 @@ var RectAreaLightUniformsLib = { 1, ) + UniformsLib.LTC_FLOAT_1.needsUpdate = true + UniformsLib.LTC_FLOAT_2.needsUpdate = true + const ltc_half_1 = new Uint16Array(LTC_MAT_1.length) LTC_MAT_1.forEach(function (x, index) { @@ -32876,7 +32879,10 @@ var RectAreaLightUniformsLib = { NearestFilter, 1, ) - }, + + UniformsLib.LTC_HALF_1.needsUpdate = true + UniformsLib.LTC_HALF_2.needsUpdate = true + } } export { RectAreaLightUniformsLib } diff --git a/src/lines/LineMaterial.js b/src/lines/LineMaterial.js index 4389884e..137dd4d2 100644 --- a/src/lines/LineMaterial.js +++ b/src/lines/LineMaterial.js @@ -11,423 +11,418 @@ * } */ -import { ShaderLib, ShaderMaterial, UniformsLib, UniformsUtils, Vector2 } from 'three' - -UniformsLib.line = { - worldUnits: { value: 1 }, - linewidth: { value: 1 }, - resolution: { value: new Vector2(1, 1) }, - dashOffset: { value: 0 }, - dashScale: { value: 1 }, - dashSize: { value: 1 }, - gapSize: { value: 1 }, // todo FIX - maybe change to totalSize -} - -ShaderLib['line'] = { - uniforms: UniformsUtils.merge([UniformsLib.common, UniformsLib.fog, UniformsLib.line]), - - vertexShader: /* glsl */ ` - #include - #include - #include - #include - #include - - uniform float linewidth; - uniform vec2 resolution; +import { ShaderMaterial, UniformsLib, UniformsUtils, Vector2 } from 'three' - attribute vec3 instanceStart; - attribute vec3 instanceEnd; +class LineMaterial extends ShaderMaterial { + constructor(parameters) { + super({ + type: 'LineMaterial', - attribute vec3 instanceColorStart; - attribute vec3 instanceColorEnd; + uniforms: UniformsUtils.clone( + UniformsUtils.merge([ + UniformsLib.common, + UniformsLib.fog, + { + worldUnits: { value: 1 }, + linewidth: { value: 1 }, + resolution: { value: new Vector2(1, 1) }, + dashOffset: { value: 0 }, + dashScale: { value: 1 }, + dashSize: { value: 1 }, + gapSize: { value: 1 }, // todo FIX - maybe change to totalSize + }, + ]), + ), - #ifdef WORLD_UNITS + vertexShader: /* glsl */ ` + #include + #include + #include + #include + #include - varying vec4 worldPos; - varying vec3 worldStart; - varying vec3 worldEnd; + uniform float linewidth; + uniform vec2 resolution; - #ifdef USE_DASH + attribute vec3 instanceStart; + attribute vec3 instanceEnd; - varying vec2 vUv; + attribute vec3 instanceColorStart; + attribute vec3 instanceColorEnd; - #endif + #ifdef WORLD_UNITS - #else + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; - varying vec2 vUv; + #ifdef USE_DASH - #endif + varying vec2 vUv; - #ifdef USE_DASH + #endif - uniform float dashScale; - attribute float instanceDistanceStart; - attribute float instanceDistanceEnd; - varying float vLineDistance; + #else - #endif + varying vec2 vUv; - void trimSegment( const in vec4 start, inout vec4 end ) { + #endif - // trim end segment so it terminates between the camera plane and the near plane + #ifdef USE_DASH - // conservative estimate of the near plane - float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column - float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column - float nearEstimate = - 0.5 * b / a; + uniform float dashScale; + attribute float instanceDistanceStart; + attribute float instanceDistanceEnd; + varying float vLineDistance; - float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); + #endif - end.xyz = mix( start.xyz, end.xyz, alpha ); + void trimSegment( const in vec4 start, inout vec4 end ) { - } + // trim end segment so it terminates between the camera plane and the near plane - void main() { + // conservative estimate of the near plane + float a = projectionMatrix[ 2 ][ 2 ]; // 3nd entry in 3th column + float b = projectionMatrix[ 3 ][ 2 ]; // 3nd entry in 4th column + float nearEstimate = - 0.5 * b / a; - #ifdef USE_COLOR + float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); - vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; + end.xyz = mix( start.xyz, end.xyz, alpha ); - #endif + } - #ifdef USE_DASH + void main() { - vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; - vUv = uv; + #ifdef USE_COLOR - #endif + vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; - float aspect = resolution.x / resolution.y; + #endif - // camera space - vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); - vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); + #ifdef USE_DASH - #ifdef WORLD_UNITS + vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; + vUv = uv; - worldStart = start.xyz; - worldEnd = end.xyz; + #endif - #else + float aspect = resolution.x / resolution.y; - vUv = uv; + // camera space + vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); + vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); - #endif + #ifdef WORLD_UNITS - // special case for perspective projection, and segments that terminate either in, or behind, the camera plane - // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space - // but we need to perform ndc-space calculations in the shader, so we must address this issue directly - // perhaps there is a more elegant solution -- WestLangley + worldStart = start.xyz; + worldEnd = end.xyz; - bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column + #else - if ( perspective ) { + vUv = uv; - if ( start.z < 0.0 && end.z >= 0.0 ) { + #endif - trimSegment( start, end ); + // special case for perspective projection, and segments that terminate either in, or behind, the camera plane + // clearly the gpu firmware has a way of addressing this issue when projecting into ndc space + // but we need to perform ndc-space calculations in the shader, so we must address this issue directly + // perhaps there is a more elegant solution -- WestLangley - } else if ( end.z < 0.0 && start.z >= 0.0 ) { + bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column - trimSegment( end, start ); + if ( perspective ) { - } + if ( start.z < 0.0 && end.z >= 0.0 ) { - } + trimSegment( start, end ); - // clip space - vec4 clipStart = projectionMatrix * start; - vec4 clipEnd = projectionMatrix * end; + } else if ( end.z < 0.0 && start.z >= 0.0 ) { - // ndc space - vec3 ndcStart = clipStart.xyz / clipStart.w; - vec3 ndcEnd = clipEnd.xyz / clipEnd.w; + trimSegment( end, start ); - // direction - vec2 dir = ndcEnd.xy - ndcStart.xy; + } - // account for clip-space aspect ratio - dir.x *= aspect; - dir = normalize( dir ); + } - #ifdef WORLD_UNITS + // clip space + vec4 clipStart = projectionMatrix * start; + vec4 clipEnd = projectionMatrix * end; - // get the offset direction as perpendicular to the view vector - vec3 worldDir = normalize( end.xyz - start.xyz ); - vec3 offset; - if ( position.y < 0.5 ) { + // ndc space + vec3 ndcStart = clipStart.xyz / clipStart.w; + vec3 ndcEnd = clipEnd.xyz / clipEnd.w; - offset = normalize( cross( start.xyz, worldDir ) ); + // direction + vec2 dir = ndcEnd.xy - ndcStart.xy; - } else { + // account for clip-space aspect ratio + dir.x *= aspect; + dir = normalize( dir ); - offset = normalize( cross( end.xyz, worldDir ) ); + #ifdef WORLD_UNITS - } + // get the offset direction as perpendicular to the view vector + vec3 worldDir = normalize( end.xyz - start.xyz ); + vec3 offset; + if ( position.y < 0.5 ) { - // sign flip - if ( position.x < 0.0 ) offset *= - 1.0; + offset = normalize( cross( start.xyz, worldDir ) ); - float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); + } else { - // don't extend the line if we're rendering dashes because we - // won't be rendering the endcaps - #ifndef USE_DASH + offset = normalize( cross( end.xyz, worldDir ) ); - // extend the line bounds to encompass endcaps - start.xyz += - worldDir * linewidth * 0.5; - end.xyz += worldDir * linewidth * 0.5; + } - // shift the position of the quad so it hugs the forward edge of the line - offset.xy -= dir * forwardOffset; - offset.z += 0.5; + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; - #endif + float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); - // endcaps - if ( position.y > 1.0 || position.y < 0.0 ) { + // don't extend the line if we're rendering dashes because we + // won't be rendering the endcaps + #ifndef USE_DASH - offset.xy += dir * 2.0 * forwardOffset; + // extend the line bounds to encompass endcaps + start.xyz += - worldDir * linewidth * 0.5; + end.xyz += worldDir * linewidth * 0.5; - } + // shift the position of the quad so it hugs the forward edge of the line + offset.xy -= dir * forwardOffset; + offset.z += 0.5; - // adjust for linewidth - offset *= linewidth * 0.5; + #endif - // set the world position - worldPos = ( position.y < 0.5 ) ? start : end; - worldPos.xyz += offset; + // endcaps + if ( position.y > 1.0 || position.y < 0.0 ) { - // project the worldpos - vec4 clip = projectionMatrix * worldPos; + offset.xy += dir * 2.0 * forwardOffset; - // shift the depth of the projected points so the line - // segments overlap neatly - vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; - clip.z = clipPose.z * clip.w; + } - #else + // adjust for linewidth + offset *= linewidth * 0.5; - vec2 offset = vec2( dir.y, - dir.x ); - // undo aspect ratio adjustment - dir.x /= aspect; - offset.x /= aspect; + // set the world position + worldPos = ( position.y < 0.5 ) ? start : end; + worldPos.xyz += offset; - // sign flip - if ( position.x < 0.0 ) offset *= - 1.0; + // project the worldpos + vec4 clip = projectionMatrix * worldPos; - // endcaps - if ( position.y < 0.0 ) { + // shift the depth of the projected points so the line + // segments overlap neatly + vec3 clipPose = ( position.y < 0.5 ) ? ndcStart : ndcEnd; + clip.z = clipPose.z * clip.w; - offset += - dir; + #else - } else if ( position.y > 1.0 ) { + vec2 offset = vec2( dir.y, - dir.x ); + // undo aspect ratio adjustment + dir.x /= aspect; + offset.x /= aspect; - offset += dir; + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; - } + // endcaps + if ( position.y < 0.0 ) { - // adjust for linewidth - offset *= linewidth; + offset += - dir; - // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... - offset /= resolution.y; + } else if ( position.y > 1.0 ) { - // select end - vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; + offset += dir; - // back to clip space - offset *= clip.w; - - clip.xy += offset; + } - #endif + // adjust for linewidth + offset *= linewidth; - gl_Position = clip; + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset /= resolution.y; - vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation + // select end + vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; - #include - #include - #include + // back to clip space + offset *= clip.w; - } - `, + clip.xy += offset; - fragmentShader: /* glsl */ ` - uniform vec3 diffuse; - uniform float opacity; - uniform float linewidth; + #endif - #ifdef USE_DASH + gl_Position = clip; - uniform float dashOffset; - uniform float dashSize; - uniform float gapSize; + vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation - #endif + #include + #include + #include - varying float vLineDistance; + } + `, + fragmentShader: /* glsl */ ` + uniform vec3 diffuse; + uniform float opacity; + uniform float linewidth; - #ifdef WORLD_UNITS + #ifdef USE_DASH - varying vec4 worldPos; - varying vec3 worldStart; - varying vec3 worldEnd; + uniform float dashOffset; + uniform float dashSize; + uniform float gapSize; - #ifdef USE_DASH + #endif - varying vec2 vUv; + varying float vLineDistance; - #endif + #ifdef WORLD_UNITS - #else + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; - varying vec2 vUv; + #ifdef USE_DASH - #endif + varying vec2 vUv; - #include - #include - #include - #include - #include + #endif - vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { + #else - float mua; - float mub; + varying vec2 vUv; - vec3 p13 = p1 - p3; - vec3 p43 = p4 - p3; + #endif - vec3 p21 = p2 - p1; + #include + #include + #include + #include + #include - float d1343 = dot( p13, p43 ); - float d4321 = dot( p43, p21 ); - float d1321 = dot( p13, p21 ); - float d4343 = dot( p43, p43 ); - float d2121 = dot( p21, p21 ); + vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { - float denom = d2121 * d4343 - d4321 * d4321; + float mua; + float mub; - float numer = d1343 * d4321 - d1321 * d4343; + vec3 p13 = p1 - p3; + vec3 p43 = p4 - p3; - mua = numer / denom; - mua = clamp( mua, 0.0, 1.0 ); - mub = ( d1343 + d4321 * ( mua ) ) / d4343; - mub = clamp( mub, 0.0, 1.0 ); + vec3 p21 = p2 - p1; - return vec2( mua, mub ); + float d1343 = dot( p13, p43 ); + float d4321 = dot( p43, p21 ); + float d1321 = dot( p13, p21 ); + float d4343 = dot( p43, p43 ); + float d2121 = dot( p21, p21 ); - } + float denom = d2121 * d4343 - d4321 * d4321; - void main() { + float numer = d1343 * d4321 - d1321 * d4343; - #include + mua = numer / denom; + mua = clamp( mua, 0.0, 1.0 ); + mub = ( d1343 + d4321 * ( mua ) ) / d4343; + mub = clamp( mub, 0.0, 1.0 ); - #ifdef USE_DASH + return vec2( mua, mub ); - if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps + } - if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX + void main() { - #endif + #include - float alpha = opacity; + #ifdef USE_DASH - #ifdef WORLD_UNITS + if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps - // Find the closest points on the view ray and the line segment - vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; - vec3 lineDir = worldEnd - worldStart; - vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); + if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX - vec3 p1 = worldStart + lineDir * params.x; - vec3 p2 = rayEnd * params.y; - vec3 delta = p1 - p2; - float len = length( delta ); - float norm = len / linewidth; + #endif - #ifndef USE_DASH + float alpha = opacity; - #ifdef USE_ALPHA_TO_COVERAGE + #ifdef WORLD_UNITS - float dnorm = fwidth( norm ); - alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); + // Find the closest points on the view ray and the line segment + vec3 rayEnd = normalize( worldPos.xyz ) * 1e5; + vec3 lineDir = worldEnd - worldStart; + vec2 params = closestLineToLine( worldStart, worldEnd, vec3( 0.0, 0.0, 0.0 ), rayEnd ); - #else + vec3 p1 = worldStart + lineDir * params.x; + vec3 p2 = rayEnd * params.y; + vec3 delta = p1 - p2; + float len = length( delta ); + float norm = len / linewidth; - if ( norm > 0.5 ) { + #ifndef USE_DASH - discard; + #ifdef USE_ALPHA_TO_COVERAGE - } + float dnorm = fwidth( norm ); + alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); - #endif + #else - #endif + if ( norm > 0.5 ) { - #else + discard; - #ifdef USE_ALPHA_TO_COVERAGE + } - // artifacts appear on some hardware if a derivative is taken within a conditional - float a = vUv.x; - float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; - float len2 = a * a + b * b; - float dlen = fwidth( len2 ); + #endif - if ( abs( vUv.y ) > 1.0 ) { + #endif - alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); + #else - } + #ifdef USE_ALPHA_TO_COVERAGE - #else + // artifacts appear on some hardware if a derivative is taken within a conditional + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; + float dlen = fwidth( len2 ); - if ( abs( vUv.y ) > 1.0 ) { + if ( abs( vUv.y ) > 1.0 ) { - float a = vUv.x; - float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; - float len2 = a * a + b * b; + alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); - if ( len2 > 1.0 ) discard; + } - } + #else - #endif + if ( abs( vUv.y ) > 1.0 ) { - #endif + float a = vUv.x; + float b = ( vUv.y > 0.0 ) ? vUv.y - 1.0 : vUv.y + 1.0; + float len2 = a * a + b * b; - vec4 diffuseColor = vec4( diffuse, alpha ); + if ( len2 > 1.0 ) discard; - #include - #include + } - gl_FragColor = vec4( diffuseColor.rgb, alpha ); + #endif - #include - #include - #include - #include + #endif - } - `, -} + vec4 diffuseColor = vec4( diffuse, alpha ); -class LineMaterial extends ShaderMaterial { - constructor(parameters) { - super({ - type: 'LineMaterial', + #include + #include - uniforms: UniformsUtils.clone(ShaderLib['line'].uniforms), + gl_FragColor = vec4( diffuseColor.rgb, alpha ); - vertexShader: ShaderLib['line'].vertexShader, - fragmentShader: ShaderLib['line'].fragmentShader, + #include + #include + #include + #include + } + `, clipping: true, // required for clipping support }) diff --git a/src/lines/LineSegments2.js b/src/lines/LineSegments2.js index f9feecf0..d1542c51 100644 --- a/src/lines/LineSegments2.js +++ b/src/lines/LineSegments2.js @@ -13,21 +13,21 @@ import { import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry' import { LineMaterial } from '../lines/LineMaterial' -const _start = new Vector3() -const _end = new Vector3() +const _start = /* @__PURE__ */ new Vector3() +const _end = /* @__PURE__ */ new Vector3() -const _start4 = new Vector4() -const _end4 = new Vector4() +const _start4 = /* @__PURE__ */ new Vector4() +const _end4 = /* @__PURE__ */ new Vector4() -const _ssOrigin = new Vector4() -const _ssOrigin3 = new Vector3() -const _mvMatrix = new Matrix4() -const _line = new Line3() -const _closestPoint = new Vector3() +const _ssOrigin = /* @__PURE__ */ new Vector4() +const _ssOrigin3 = /* @__PURE__ */ new Vector3() +const _mvMatrix = /* @__PURE__ */ new Matrix4() +const _line = /* @__PURE__ */ new Line3() +const _closestPoint = /* @__PURE__ */ new Vector3() -const _box = new Box3() -const _sphere = new Sphere() -const _clipToWorldVector = new Vector4() +const _box = /* @__PURE__ */ new Box3() +const _sphere = /* @__PURE__ */ new Sphere() +const _clipToWorldVector = /* @__PURE__ */ new Vector4() let _ray, _instanceStart, _instanceEnd, _lineWidth diff --git a/src/lines/LineSegmentsGeometry.js b/src/lines/LineSegmentsGeometry.js index 1579c309..7813d0d4 100644 --- a/src/lines/LineSegmentsGeometry.js +++ b/src/lines/LineSegmentsGeometry.js @@ -9,8 +9,8 @@ import { WireframeGeometry, } from 'three' -const _box = new Box3() -const _vector = new Vector3() +const _box = /* @__PURE__ */ new Box3() +const _vector = /* @__PURE__ */ new Vector3() class LineSegmentsGeometry extends InstancedBufferGeometry { constructor() { diff --git a/src/lines/Wireframe.js b/src/lines/Wireframe.js index 2e193dd4..09766dab 100644 --- a/src/lines/Wireframe.js +++ b/src/lines/Wireframe.js @@ -2,8 +2,8 @@ import { InstancedInterleavedBuffer, InterleavedBufferAttribute, Mesh, Vector3 } import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry' import { LineMaterial } from '../lines/LineMaterial' -const _start = new Vector3() -const _end = new Vector3() +const _start = /* @__PURE__ */ new Vector3() +const _end = /* @__PURE__ */ new Vector3() class Wireframe extends Mesh { constructor(geometry = new LineSegmentsGeometry(), material = new LineMaterial({ color: Math.random() * 0xffffff })) { diff --git a/src/loaders/3DMLoader.d.ts b/src/loaders/3DMLoader.d.ts index 4ef0ab76..53fc643e 100644 --- a/src/loaders/3DMLoader.d.ts +++ b/src/loaders/3DMLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Object3D } from 'three' +import { LoadingManager, Object3D } from 'three' +import { Loader } from '../types/Loader' export class Rhino3dmLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/3MFLoader.d.ts b/src/loaders/3MFLoader.d.ts index 8ae86298..f36c1c4a 100644 --- a/src/loaders/3MFLoader.d.ts +++ b/src/loaders/3MFLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Group } from 'three' +import { LoadingManager, Group } from 'three' +import { Loader } from '../types/Loader' export class ThreeMFLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/AMFLoader.d.ts b/src/loaders/AMFLoader.d.ts index f109ed21..f8bc6e63 100644 --- a/src/loaders/AMFLoader.d.ts +++ b/src/loaders/AMFLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Group } from 'three' +import { LoadingManager, Group } from 'three' +import { Loader } from '../types/Loader' export class AMFLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/AssimpLoader.d.ts b/src/loaders/AssimpLoader.d.ts new file mode 100644 index 00000000..94e8c2a4 --- /dev/null +++ b/src/loaders/AssimpLoader.d.ts @@ -0,0 +1,20 @@ +import { Object3D, LoadingManager } from 'three' +import { Loader } from '../types/Loader' + +export interface Assimp { + animation: any + object: Object3D +} + +export class AssimpLoader extends Loader { + constructor(manager?: LoadingManager) + + load( + url: string, + onLoad: (result: Assimp) => void, + onProgress?: (event: ProgressEvent) => void, + onError?: (event: ErrorEvent) => void, + ): void + loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise + parse(buffer: ArrayBuffer, path: string): Assimp +} diff --git a/src/loaders/AssimpLoader.js b/src/loaders/AssimpLoader.js index 8052914c..2b82ea9e 100644 --- a/src/loaders/AssimpLoader.js +++ b/src/loaders/AssimpLoader.js @@ -18,14 +18,8 @@ import { Vector3, } from 'three' -var AssimpLoader = function (manager) { - Loader.call(this, manager) -} - -AssimpLoader.prototype = Object.assign(Object.create(Loader.prototype), { - constructor: AssimpLoader, - - load: function (url, onLoad, onProgress, onError) { +class AssimpLoader extends Loader { + load(url, onLoad, onProgress, onError) { var scope = this var path = scope.path === '' ? LoaderUtils.extractUrlBase(url) : scope.path @@ -54,50 +48,52 @@ AssimpLoader.prototype = Object.assign(Object.create(Loader.prototype), { onProgress, onError, ) - }, + } - parse: function (buffer, path) { + parse(buffer, path) { var textureLoader = new TextureLoader(this.manager) textureLoader.setPath(this.resourcePath || path).setCrossOrigin(this.crossOrigin) var Virtulous = {} - Virtulous.KeyFrame = function (time, matrix) { - this.time = time - this.matrix = matrix.clone() - this.position = new Vector3() - this.quaternion = new Quaternion() - this.scale = new Vector3(1, 1, 1) - this.matrix.decompose(this.position, this.quaternion, this.scale) - this.clone = function () { - var n = new Virtulous.KeyFrame(this.time, this.matrix) - return n - } + Virtulous.KeyFrame = class { + constructor(time, matrix) { + this.time = time + this.matrix = matrix.clone() + this.position = new Vector3() + this.quaternion = new Quaternion() + this.scale = new Vector3(1, 1, 1) + this.matrix.decompose(this.position, this.quaternion, this.scale) + this.clone = function () { + var n = new Virtulous.KeyFrame(this.time, this.matrix) + return n + } - this.lerp = function (nextKey, time) { - time -= this.time - var dist = nextKey.time - this.time - var l = time / dist - var l2 = 1 - l - var keypos = this.position - var keyrot = this.quaternion - // var keyscl = key.parentspaceScl || key.scl; - var key2pos = nextKey.position - var key2rot = nextKey.quaternion - // var key2scl = key2.parentspaceScl || key2.scl; - Virtulous.KeyFrame.tempAniPos.x = keypos.x * l2 + key2pos.x * l - Virtulous.KeyFrame.tempAniPos.y = keypos.y * l2 + key2pos.y * l - Virtulous.KeyFrame.tempAniPos.z = keypos.z * l2 + key2pos.z * l - // tempAniScale.x = keyscl[0] * l2 + key2scl[0] * l; - // tempAniScale.y = keyscl[1] * l2 + key2scl[1] * l; - // tempAniScale.z = keyscl[2] * l2 + key2scl[2] * l; - Virtulous.KeyFrame.tempAniQuat.set(keyrot.x, keyrot.y, keyrot.z, keyrot.w) - Virtulous.KeyFrame.tempAniQuat.slerp(key2rot, l) - return Virtulous.KeyFrame.tempAniMatrix.compose( - Virtulous.KeyFrame.tempAniPos, - Virtulous.KeyFrame.tempAniQuat, - Virtulous.KeyFrame.tempAniScale, - ) + this.lerp = function (nextKey, time) { + time -= this.time + var dist = nextKey.time - this.time + var l = time / dist + var l2 = 1 - l + var keypos = this.position + var keyrot = this.quaternion + // var keyscl = key.parentspaceScl || key.scl; + var key2pos = nextKey.position + var key2rot = nextKey.quaternion + // var key2scl = key2.parentspaceScl || key2.scl; + Virtulous.KeyFrame.tempAniPos.x = keypos.x * l2 + key2pos.x * l + Virtulous.KeyFrame.tempAniPos.y = keypos.y * l2 + key2pos.y * l + Virtulous.KeyFrame.tempAniPos.z = keypos.z * l2 + key2pos.z * l + // tempAniScale.x = keyscl[0] * l2 + key2scl[0] * l; + // tempAniScale.y = keyscl[1] * l2 + key2scl[1] * l; + // tempAniScale.z = keyscl[2] * l2 + key2scl[2] * l; + Virtulous.KeyFrame.tempAniQuat.set(keyrot.x, keyrot.y, keyrot.z, keyrot.w) + Virtulous.KeyFrame.tempAniQuat.slerp(key2rot, l) + return Virtulous.KeyFrame.tempAniMatrix.compose( + Virtulous.KeyFrame.tempAniPos, + Virtulous.KeyFrame.tempAniQuat, + Virtulous.KeyFrame.tempAniScale, + ) + } } } @@ -514,286 +510,310 @@ AssimpLoader.prototype = Object.assign(Object.create(Loader.prototype), { return undefined } - function aiMesh() { - this.mPrimitiveTypes = 0 - this.mNumVertices = 0 - this.mNumFaces = 0 - this.mNumBones = 0 - this.mMaterialIndex = 0 - this.mVertices = [] - this.mNormals = [] - this.mTangents = [] - this.mBitangents = [] - this.mColors = [[]] - this.mTextureCoords = [[]] - this.mFaces = [] - this.mBones = [] - this.hookupSkeletons = function (scene) { - if (this.mBones.length == 0) return - - var allBones = [] - var offsetMatrix = [] - var skeletonRoot = scene.findNode(this.mBones[0].mName) - - while (skeletonRoot.mParent && skeletonRoot.mParent.isBone) { - skeletonRoot = skeletonRoot.mParent - } - - var threeSkeletonRoot = skeletonRoot.toTHREE(scene) - var threeSkeletonRootBone = cloneTreeToBones(threeSkeletonRoot, scene) - this.threeNode.add(threeSkeletonRootBone) + class aiMesh { + constructor() { + this.mPrimitiveTypes = 0 + this.mNumVertices = 0 + this.mNumFaces = 0 + this.mNumBones = 0 + this.mMaterialIndex = 0 + this.mVertices = [] + this.mNormals = [] + this.mTangents = [] + this.mBitangents = [] + this.mColors = [[]] + this.mTextureCoords = [[]] + this.mFaces = [] + this.mBones = [] + this.hookupSkeletons = function (scene) { + if (this.mBones.length == 0) return + + var allBones = [] + var offsetMatrix = [] + var skeletonRoot = scene.findNode(this.mBones[0].mName) + + while (skeletonRoot.mParent && skeletonRoot.mParent.isBone) { + skeletonRoot = skeletonRoot.mParent + } - for (let i = 0; i < this.mBones.length; i++) { - var bone = findMatchingBone(threeSkeletonRootBone, this.mBones[i].mName) + var threeSkeletonRoot = skeletonRoot.toTHREE(scene) + var threeSkeletonRootBone = cloneTreeToBones(threeSkeletonRoot, scene) + this.threeNode.add(threeSkeletonRootBone) - if (bone) { - var tbone = bone - allBones.push(tbone) - //tbone.matrixAutoUpdate = false; - offsetMatrix.push(this.mBones[i].mOffsetMatrix.toTHREE()) - } else { - var skeletonRoot = scene.findNode(this.mBones[i].mName) - if (!skeletonRoot) return - var threeSkeletonRoot = skeletonRoot.toTHREE(scene) - var threeSkeletonRootBone = cloneTreeToBones(threeSkeletonRoot, scene) - this.threeNode.add(threeSkeletonRootBone) + for (let i = 0; i < this.mBones.length; i++) { var bone = findMatchingBone(threeSkeletonRootBone, this.mBones[i].mName) - var tbone = bone - allBones.push(tbone) - //tbone.matrixAutoUpdate = false; - offsetMatrix.push(this.mBones[i].mOffsetMatrix.toTHREE()) - } - } - var skeleton = new Skeleton(allBones, offsetMatrix) + if (bone) { + var tbone = bone + allBones.push(tbone) + //tbone.matrixAutoUpdate = false; + offsetMatrix.push(this.mBones[i].mOffsetMatrix.toTHREE()) + } else { + var skeletonRoot = scene.findNode(this.mBones[i].mName) + if (!skeletonRoot) return + var threeSkeletonRoot = skeletonRoot.toTHREE(scene) + var threeSkeletonRootBone = cloneTreeToBones(threeSkeletonRoot, scene) + this.threeNode.add(threeSkeletonRootBone) + var bone = findMatchingBone(threeSkeletonRootBone, this.mBones[i].mName) + var tbone = bone + allBones.push(tbone) + //tbone.matrixAutoUpdate = false; + offsetMatrix.push(this.mBones[i].mOffsetMatrix.toTHREE()) + } + } - this.threeNode.bind(skeleton, new Matrix4()) - this.threeNode.material.skinning = true - } + var skeleton = new Skeleton(allBones, offsetMatrix) - this.toTHREE = function (scene) { - if (this.threeNode) return this.threeNode - var geometry = new BufferGeometry() - var mat - if (scene.mMaterials[this.mMaterialIndex]) mat = scene.mMaterials[this.mMaterialIndex].toTHREE(scene) - else mat = new MeshLambertMaterial() - geometry.setIndex(new BufferAttribute(new Uint32Array(this.mIndexArray), 1)) - geometry.setAttribute('position', new BufferAttribute(this.mVertexBuffer, 3)) - if (this.mNormalBuffer && this.mNormalBuffer.length > 0) { - geometry.setAttribute('normal', new BufferAttribute(this.mNormalBuffer, 3)) - } - if (this.mColorBuffer && this.mColorBuffer.length > 0) { - geometry.setAttribute('color', new BufferAttribute(this.mColorBuffer, 4)) - } - if (this.mTexCoordsBuffers[0] && this.mTexCoordsBuffers[0].length > 0) { - geometry.setAttribute('uv', new BufferAttribute(new Float32Array(this.mTexCoordsBuffers[0]), 2)) - } - if (this.mTexCoordsBuffers[1] && this.mTexCoordsBuffers[1].length > 0) { - geometry.setAttribute('uv1', new BufferAttribute(new Float32Array(this.mTexCoordsBuffers[1]), 2)) + this.threeNode.bind(skeleton, new Matrix4()) + this.threeNode.material.skinning = true } - if (this.mTangentBuffer && this.mTangentBuffer.length > 0) { - geometry.setAttribute('tangents', new BufferAttribute(this.mTangentBuffer, 3)) - } - if (this.mBitangentBuffer && this.mBitangentBuffer.length > 0) { - geometry.setAttribute('bitangents', new BufferAttribute(this.mBitangentBuffer, 3)) - } - if (this.mBones.length > 0) { - var weights = [] - var bones = [] - for (let i = 0; i < this.mBones.length; i++) { - for (let j = 0; j < this.mBones[i].mWeights.length; j++) { - var weight = this.mBones[i].mWeights[j] - if (weight) { - if (!weights[weight.mVertexId]) weights[weight.mVertexId] = [] - if (!bones[weight.mVertexId]) bones[weight.mVertexId] = [] - weights[weight.mVertexId].push(weight.mWeight) - bones[weight.mVertexId].push(parseInt(i)) + this.toTHREE = function (scene) { + if (this.threeNode) return this.threeNode + var geometry = new BufferGeometry() + var mat + if (scene.mMaterials[this.mMaterialIndex]) mat = scene.mMaterials[this.mMaterialIndex].toTHREE(scene) + else mat = new MeshLambertMaterial() + geometry.setIndex(new BufferAttribute(new Uint32Array(this.mIndexArray), 1)) + geometry.setAttribute('position', new BufferAttribute(this.mVertexBuffer, 3)) + if (this.mNormalBuffer && this.mNormalBuffer.length > 0) { + geometry.setAttribute('normal', new BufferAttribute(this.mNormalBuffer, 3)) + } + if (this.mColorBuffer && this.mColorBuffer.length > 0) { + geometry.setAttribute('color', new BufferAttribute(this.mColorBuffer, 4)) + } + if (this.mTexCoordsBuffers[0] && this.mTexCoordsBuffers[0].length > 0) { + geometry.setAttribute('uv', new BufferAttribute(new Float32Array(this.mTexCoordsBuffers[0]), 2)) + } + if (this.mTexCoordsBuffers[1] && this.mTexCoordsBuffers[1].length > 0) { + geometry.setAttribute('uv1', new BufferAttribute(new Float32Array(this.mTexCoordsBuffers[1]), 2)) + } + if (this.mTangentBuffer && this.mTangentBuffer.length > 0) { + geometry.setAttribute('tangents', new BufferAttribute(this.mTangentBuffer, 3)) + } + if (this.mBitangentBuffer && this.mBitangentBuffer.length > 0) { + geometry.setAttribute('bitangents', new BufferAttribute(this.mBitangentBuffer, 3)) + } + if (this.mBones.length > 0) { + var weights = [] + var bones = [] + + for (let i = 0; i < this.mBones.length; i++) { + for (let j = 0; j < this.mBones[i].mWeights.length; j++) { + var weight = this.mBones[i].mWeights[j] + if (weight) { + if (!weights[weight.mVertexId]) weights[weight.mVertexId] = [] + if (!bones[weight.mVertexId]) bones[weight.mVertexId] = [] + weights[weight.mVertexId].push(weight.mWeight) + bones[weight.mVertexId].push(parseInt(i)) + } } } - } - for (let i in bones) { - sortWeights(bones[i], weights[i]) - } + for (let i in bones) { + sortWeights(bones[i], weights[i]) + } - var _weights = [] - var _bones = [] - - for (let i = 0; i < weights.length; i++) { - for (let j = 0; j < 4; j++) { - if (weights[i] && bones[i]) { - _weights.push(weights[i][j]) - _bones.push(bones[i][j]) - } else { - _weights.push(0) - _bones.push(0) + var _weights = [] + var _bones = [] + + for (let i = 0; i < weights.length; i++) { + for (let j = 0; j < 4; j++) { + if (weights[i] && bones[i]) { + _weights.push(weights[i][j]) + _bones.push(bones[i][j]) + } else { + _weights.push(0) + _bones.push(0) + } } } + + geometry.setAttribute('skinWeight', new BufferAttribute(new Float32Array(_weights), BONESPERVERT)) + geometry.setAttribute('skinIndex', new BufferAttribute(new Float32Array(_bones), BONESPERVERT)) } - geometry.setAttribute('skinWeight', new BufferAttribute(new Float32Array(_weights), BONESPERVERT)) - geometry.setAttribute('skinIndex', new BufferAttribute(new Float32Array(_bones), BONESPERVERT)) - } + var mesh - var mesh + if (this.mBones.length == 0) mesh = new Mesh(geometry, mat) - if (this.mBones.length == 0) mesh = new Mesh(geometry, mat) + if (this.mBones.length > 0) { + mesh = new SkinnedMesh(geometry, mat) + mesh.normalizeSkinWeights() + } - if (this.mBones.length > 0) { - mesh = new SkinnedMesh(geometry, mat) - mesh.normalizeSkinWeights() + this.threeNode = mesh + //mesh.matrixAutoUpdate = false; + return mesh } - - this.threeNode = mesh - //mesh.matrixAutoUpdate = false; - return mesh } } - function aiFace() { - this.mNumIndices = 0 - this.mIndices = [] + class aiFace { + constructor() { + this.mNumIndices = 0 + this.mIndices = [] + } } - function aiVector3D() { - this.x = 0 - this.y = 0 - this.z = 0 + class aiVector3D { + constructor() { + this.x = 0 + this.y = 0 + this.z = 0 - this.toTHREE = function () { - return new Vector3(this.x, this.y, this.z) + this.toTHREE = function () { + return new Vector3(this.x, this.y, this.z) + } } } - function aiColor3D() { - this.r = 0 - this.g = 0 - this.b = 0 - this.a = 0 - this.toTHREE = function () { - return new Color(this.r, this.g, this.b) + class aiColor3D { + constructor() { + this.r = 0 + this.g = 0 + this.b = 0 + this.a = 0 + this.toTHREE = function () { + return new Color(this.r, this.g, this.b) + } } } - function aiQuaternion() { - this.x = 0 - this.y = 0 - this.z = 0 - this.w = 0 - this.toTHREE = function () { - return new Quaternion(this.x, this.y, this.z, this.w) + class aiQuaternion { + constructor() { + this.x = 0 + this.y = 0 + this.z = 0 + this.w = 0 + this.toTHREE = function () { + return new Quaternion(this.x, this.y, this.z, this.w) + } } } - function aiVertexWeight() { - this.mVertexId = 0 - this.mWeight = 0 + class aiVertexWeight { + constructor() { + this.mVertexId = 0 + this.mWeight = 0 + } } - function aiString() { - this.data = [] - this.toString = function () { - var str = '' - this.data.forEach(function (i) { - str += String.fromCharCode(i) - }) - return str.replace(/[^\x20-\x7E]+/g, '') + class aiString { + constructor() { + this.data = [] + this.toString = function () { + var str = '' + this.data.forEach(function (i) { + str += String.fromCharCode(i) + }) + return str.replace(/[^\x20-\x7E]+/g, '') + } } } - function aiVectorKey() { - this.mTime = 0 - this.mValue = null + class aiVectorKey { + constructor() { + this.mTime = 0 + this.mValue = null + } } - function aiQuatKey() { - this.mTime = 0 - this.mValue = null + class aiQuatKey { + constructor() { + this.mTime = 0 + this.mValue = null + } } - function aiNode() { - this.mName = '' - this.mTransformation = [] - this.mNumChildren = 0 - this.mNumMeshes = 0 - this.mMeshes = [] - this.mChildren = [] - this.toTHREE = function (scene) { - if (this.threeNode) return this.threeNode - var o = new Object3D() - o.name = this.mName - o.matrix = this.mTransformation.toTHREE() + class aiNode { + constructor() { + this.mName = '' + this.mTransformation = [] + this.mNumChildren = 0 + this.mNumMeshes = 0 + this.mMeshes = [] + this.mChildren = [] + this.toTHREE = function (scene) { + if (this.threeNode) return this.threeNode + var o = new Object3D() + o.name = this.mName + o.matrix = this.mTransformation.toTHREE() - for (let i = 0; i < this.mChildren.length; i++) { - o.add(this.mChildren[i].toTHREE(scene)) - } + for (let i = 0; i < this.mChildren.length; i++) { + o.add(this.mChildren[i].toTHREE(scene)) + } - for (let i = 0; i < this.mMeshes.length; i++) { - o.add(scene.mMeshes[this.mMeshes[i]].toTHREE(scene)) - } + for (let i = 0; i < this.mMeshes.length; i++) { + o.add(scene.mMeshes[this.mMeshes[i]].toTHREE(scene)) + } - this.threeNode = o - //o.matrixAutoUpdate = false; - o.matrix.decompose(o.position, o.quaternion, o.scale) - return o + this.threeNode = o + //o.matrixAutoUpdate = false; + o.matrix.decompose(o.position, o.quaternion, o.scale) + return o + } } } - function aiBone() { - this.mName = '' - this.mNumWeights = 0 - this.mOffsetMatrix = 0 + class aiBone { + constructor() { + this.mName = '' + this.mNumWeights = 0 + this.mOffsetMatrix = 0 + } } - function aiMaterialProperty() { - this.mKey = '' - this.mSemantic = 0 - this.mIndex = 0 - this.mData = [] - this.mDataLength = 0 - this.mType = 0 - this.dataAsColor = function () { - var array = new Uint8Array(this.mData).buffer - var reader = new DataView(array) - var r = reader.getFloat32(0, true) - var g = reader.getFloat32(4, true) - var b = reader.getFloat32(8, true) - //var a = reader.getFloat32(12, true); - return new Color(r, g, b) - } + class aiMaterialProperty { + constructor() { + this.mKey = '' + this.mSemantic = 0 + this.mIndex = 0 + this.mData = [] + this.mDataLength = 0 + this.mType = 0 + this.dataAsColor = function () { + var array = new Uint8Array(this.mData).buffer + var reader = new DataView(array) + var r = reader.getFloat32(0, true) + var g = reader.getFloat32(4, true) + var b = reader.getFloat32(8, true) + //var a = reader.getFloat32(12, true); + return new Color(r, g, b) + } - this.dataAsFloat = function () { - var array = new Uint8Array(this.mData).buffer - var reader = new DataView(array) - var r = reader.getFloat32(0, true) - return r - } + this.dataAsFloat = function () { + var array = new Uint8Array(this.mData).buffer + var reader = new DataView(array) + var r = reader.getFloat32(0, true) + return r + } - this.dataAsBool = function () { - var array = new Uint8Array(this.mData).buffer - var reader = new DataView(array) - var r = reader.getFloat32(0, true) - return !!r - } + this.dataAsBool = function () { + var array = new Uint8Array(this.mData).buffer + var reader = new DataView(array) + var r = reader.getFloat32(0, true) + return !!r + } - this.dataAsString = function () { - var s = new aiString() - s.data = this.mData - return s.toString() - } + this.dataAsString = function () { + var s = new aiString() + s.data = this.mData + return s.toString() + } - this.dataAsMap = function () { - var s = new aiString() - s.data = this.mData - var path = s.toString() - path = path.replace(/\\/g, '/') + this.dataAsMap = function () { + var s = new aiString() + s.data = this.mData + var path = s.toString() + path = path.replace(/\\/g, '/') - if (path.indexOf('/') != -1) { - path = path.substr(path.lastIndexOf('/') + 1) - } + if (path.indexOf('/') != -1) { + path = path.substr(path.lastIndexOf('/') + 1) + } - return textureLoader.load(path) + return textureLoader.load(path) + } } } @@ -831,42 +851,44 @@ AssimpLoader.prototype = Object.assign(Object.create(Loader.prototype), { '$tex.file': 'map', } - function aiMaterial() { - this.mNumAllocated = 0 - this.mNumProperties = 0 - this.mProperties = [] - this.toTHREE = function () { - var mat = new MeshPhongMaterial() + class aiMaterial { + constructor() { + this.mNumAllocated = 0 + this.mNumProperties = 0 + this.mProperties = [] + this.toTHREE = function () { + var mat = new MeshPhongMaterial() - for (let i = 0; i < this.mProperties.length; i++) { - if (nameTypeMapping[this.mProperties[i].mKey] == 'float') { - mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsFloat() - } - if (nameTypeMapping[this.mProperties[i].mKey] == 'color') { - mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsColor() - } - if (nameTypeMapping[this.mProperties[i].mKey] == 'bool') { - mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsBool() - } - if (nameTypeMapping[this.mProperties[i].mKey] == 'string') { - mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsString() - } - if (nameTypeMapping[this.mProperties[i].mKey] == 'map') { - var prop = this.mProperties[i] - if (prop.mSemantic == aiTextureType_DIFFUSE) mat.map = this.mProperties[i].dataAsMap() - if (prop.mSemantic == aiTextureType_NORMALS) mat.normalMap = this.mProperties[i].dataAsMap() - if (prop.mSemantic == aiTextureType_LIGHTMAP) mat.lightMap = this.mProperties[i].dataAsMap() - if (prop.mSemantic == aiTextureType_OPACITY) mat.alphaMap = this.mProperties[i].dataAsMap() + for (let i = 0; i < this.mProperties.length; i++) { + if (nameTypeMapping[this.mProperties[i].mKey] == 'float') { + mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsFloat() + } + if (nameTypeMapping[this.mProperties[i].mKey] == 'color') { + mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsColor() + } + if (nameTypeMapping[this.mProperties[i].mKey] == 'bool') { + mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsBool() + } + if (nameTypeMapping[this.mProperties[i].mKey] == 'string') { + mat[namePropMapping[this.mProperties[i].mKey]] = this.mProperties[i].dataAsString() + } + if (nameTypeMapping[this.mProperties[i].mKey] == 'map') { + var prop = this.mProperties[i] + if (prop.mSemantic == aiTextureType_DIFFUSE) mat.map = this.mProperties[i].dataAsMap() + if (prop.mSemantic == aiTextureType_NORMALS) mat.normalMap = this.mProperties[i].dataAsMap() + if (prop.mSemantic == aiTextureType_LIGHTMAP) mat.lightMap = this.mProperties[i].dataAsMap() + if (prop.mSemantic == aiTextureType_OPACITY) mat.alphaMap = this.mProperties[i].dataAsMap() + } } - } - mat.ambient.r = 0.53 - mat.ambient.g = 0.53 - mat.ambient.b = 0.53 - mat.color.r = 1 - mat.color.g = 1 - mat.color.b = 1 - return mat + mat.ambient.r = 0.53 + mat.ambient.g = 0.53 + mat.ambient.b = 0.53 + mat.color.r = 1 + mat.color.g = 1 + mat.color.b = 1 + return mat + } } } @@ -920,220 +942,234 @@ AssimpLoader.prototype = Object.assign(Object.create(Loader.prototype), { } } - function aiNodeAnim() { - this.mNodeName = '' - this.mNumPositionKeys = 0 - this.mNumRotationKeys = 0 - this.mNumScalingKeys = 0 - this.mPositionKeys = [] - this.mRotationKeys = [] - this.mScalingKeys = [] - this.mPreState = '' - this.mPostState = '' - this.init = function (tps) { - if (!tps) tps = 1 - - function t(t) { - t.mTime /= tps - } + class aiNodeAnim { + constructor() { + this.mNodeName = '' + this.mNumPositionKeys = 0 + this.mNumRotationKeys = 0 + this.mNumScalingKeys = 0 + this.mPositionKeys = [] + this.mRotationKeys = [] + this.mScalingKeys = [] + this.mPreState = '' + this.mPostState = '' + this.init = function (tps) { + if (!tps) tps = 1 - this.mPositionKeys.forEach(t) - this.mRotationKeys.forEach(t) - this.mScalingKeys.forEach(t) - } + function t(t) { + t.mTime /= tps + } - this.sortKeys = function () { - function comp(a, b) { - return a.mTime - b.mTime + this.mPositionKeys.forEach(t) + this.mRotationKeys.forEach(t) + this.mScalingKeys.forEach(t) } - this.mPositionKeys.sort(comp) - this.mRotationKeys.sort(comp) - this.mScalingKeys.sort(comp) - } + this.sortKeys = function () { + function comp(a, b) { + return a.mTime - b.mTime + } - this.getLength = function () { - return Math.max( - Math.max.apply( - null, - this.mPositionKeys.map(function (a) { - return a.mTime - }), - ), - Math.max.apply( - null, - this.mRotationKeys.map(function (a) { - return a.mTime - }), - ), - Math.max.apply( - null, - this.mScalingKeys.map(function (a) { - return a.mTime - }), - ), - ) - } + this.mPositionKeys.sort(comp) + this.mRotationKeys.sort(comp) + this.mScalingKeys.sort(comp) + } - this.toTHREE = function (o) { - this.sortKeys() - var length = this.getLength() - var track = new Virtulous.KeyFrameTrack() - - for (let i = 0; i < length; i += 0.05) { - var matrix = new Matrix4() - var time = i - var pos = sampleTrack(this.mPositionKeys, time, length, veclerp) - var scale = sampleTrack(this.mScalingKeys, time, length, veclerp) - var rotation = sampleTrack(this.mRotationKeys, time, length, quatlerp) - matrix.compose(pos, rotation, scale) - - var key = new Virtulous.KeyFrame(time, matrix) - track.addKey(key) + this.getLength = function () { + return Math.max( + Math.max.apply( + null, + this.mPositionKeys.map(function (a) { + return a.mTime + }), + ), + Math.max.apply( + null, + this.mRotationKeys.map(function (a) { + return a.mTime + }), + ), + Math.max.apply( + null, + this.mScalingKeys.map(function (a) { + return a.mTime + }), + ), + ) } - track.target = o.findNode(this.mNodeName).toTHREE() + this.toTHREE = function (o) { + this.sortKeys() + var length = this.getLength() + var track = new Virtulous.KeyFrameTrack() + + for (let i = 0; i < length; i += 0.05) { + var matrix = new Matrix4() + var time = i + var pos = sampleTrack(this.mPositionKeys, time, length, veclerp) + var scale = sampleTrack(this.mScalingKeys, time, length, veclerp) + var rotation = sampleTrack(this.mRotationKeys, time, length, quatlerp) + matrix.compose(pos, rotation, scale) + + var key = new Virtulous.KeyFrame(time, matrix) + track.addKey(key) + } + + track.target = o.findNode(this.mNodeName).toTHREE() - var tracks = [track] + var tracks = [track] - if (o.nodeToBoneMap[this.mNodeName]) { - for (let i = 0; i < o.nodeToBoneMap[this.mNodeName].length; i++) { - var t2 = track.clone() - t2.target = o.nodeToBoneMap[this.mNodeName][i] - tracks.push(t2) + if (o.nodeToBoneMap[this.mNodeName]) { + for (let i = 0; i < o.nodeToBoneMap[this.mNodeName].length; i++) { + var t2 = track.clone() + t2.target = o.nodeToBoneMap[this.mNodeName][i] + tracks.push(t2) + } } - } - return tracks + return tracks + } } } - function aiAnimation() { - this.mName = '' - this.mDuration = 0 - this.mTicksPerSecond = 0 - this.mNumChannels = 0 - this.mChannels = [] - this.toTHREE = function (root) { - var animationHandle = new Virtulous.Animation() + class aiAnimation { + constructor() { + this.mName = '' + this.mDuration = 0 + this.mTicksPerSecond = 0 + this.mNumChannels = 0 + this.mChannels = [] + this.toTHREE = function (root) { + var animationHandle = new Virtulous.Animation() - for (let i in this.mChannels) { - this.mChannels[i].init(this.mTicksPerSecond) + for (let i in this.mChannels) { + this.mChannels[i].init(this.mTicksPerSecond) - var tracks = this.mChannels[i].toTHREE(root) + var tracks = this.mChannels[i].toTHREE(root) - for (let j in tracks) { - tracks[j].init() - animationHandle.addTrack(tracks[j]) + for (let j in tracks) { + tracks[j].init() + animationHandle.addTrack(tracks[j]) + } } - } - animationHandle.length = Math.max.apply( - null, - animationHandle.tracks.map(function (e) { - return e.length - }), - ) - return animationHandle - } - } - - function aiTexture() { - this.mWidth = 0 - this.mHeight = 0 - this.texAchFormatHint = [] - this.pcData = [] - } - - function aiLight() { - this.mName = '' - this.mType = 0 - this.mAttenuationConstant = 0 - this.mAttenuationLinear = 0 - this.mAttenuationQuadratic = 0 - this.mAngleInnerCone = 0 - this.mAngleOuterCone = 0 - this.mColorDiffuse = null - this.mColorSpecular = null - this.mColorAmbient = null - } - - function aiCamera() { - this.mName = '' - this.mPosition = null - this.mLookAt = null - this.mUp = null - this.mHorizontalFOV = 0 - this.mClipPlaneNear = 0 - this.mClipPlaneFar = 0 - this.mAspect = 0 - } - - function aiScene() { - this.versionMajor = 0 - this.versionMinor = 0 - this.versionRevision = 0 - this.compileFlags = 0 - this.mFlags = 0 - this.mNumMeshes = 0 - this.mNumMaterials = 0 - this.mNumAnimations = 0 - this.mNumTextures = 0 - this.mNumLights = 0 - this.mNumCameras = 0 - this.mRootNode = null - this.mMeshes = [] - this.mMaterials = [] - this.mAnimations = [] - this.mLights = [] - this.mCameras = [] - this.nodeToBoneMap = {} - this.findNode = function (name, root) { - if (!root) { - root = this.mRootNode + animationHandle.length = Math.max.apply( + null, + animationHandle.tracks.map(function (e) { + return e.length + }), + ) + return animationHandle } + } + } - if (root.mName == name) { - return root - } + class aiTexture { + constructor() { + this.mWidth = 0 + this.mHeight = 0 + this.texAchFormatHint = [] + this.pcData = [] + } + } + + class aiLight { + constructor() { + this.mName = '' + this.mType = 0 + this.mAttenuationConstant = 0 + this.mAttenuationLinear = 0 + this.mAttenuationQuadratic = 0 + this.mAngleInnerCone = 0 + this.mAngleOuterCone = 0 + this.mColorDiffuse = null + this.mColorSpecular = null + this.mColorAmbient = null + } + } + + class aiCamera { + constructor() { + this.mName = '' + this.mPosition = null + this.mLookAt = null + this.mUp = null + this.mHorizontalFOV = 0 + this.mClipPlaneNear = 0 + this.mClipPlaneFar = 0 + this.mAspect = 0 + } + } + + class aiScene { + constructor() { + this.versionMajor = 0 + this.versionMinor = 0 + this.versionRevision = 0 + this.compileFlags = 0 + this.mFlags = 0 + this.mNumMeshes = 0 + this.mNumMaterials = 0 + this.mNumAnimations = 0 + this.mNumTextures = 0 + this.mNumLights = 0 + this.mNumCameras = 0 + this.mRootNode = null + this.mMeshes = [] + this.mMaterials = [] + this.mAnimations = [] + this.mLights = [] + this.mCameras = [] + this.nodeToBoneMap = {} + this.findNode = function (name, root) { + if (!root) { + root = this.mRootNode + } + + if (root.mName == name) { + return root + } - for (let i = 0; i < root.mChildren.length; i++) { - var ret = this.findNode(name, root.mChildren[i]) - if (ret) return ret + for (let i = 0; i < root.mChildren.length; i++) { + var ret = this.findNode(name, root.mChildren[i]) + if (ret) return ret + } + + return null } - return null - } + this.toTHREE = function () { + this.nodeCount = 0 - this.toTHREE = function () { - this.nodeCount = 0 + markBones(this) - markBones(this) + var o = this.mRootNode.toTHREE(this) - var o = this.mRootNode.toTHREE(this) + for (let i in this.mMeshes) this.mMeshes[i].hookupSkeletons(this) - for (let i in this.mMeshes) this.mMeshes[i].hookupSkeletons(this) + if (this.mAnimations.length > 0) { + var a = this.mAnimations[0].toTHREE(this) + } - if (this.mAnimations.length > 0) { - var a = this.mAnimations[0].toTHREE(this) + return { object: o, animation: a } } - - return { object: o, animation: a } } } - function aiMatrix4() { - this.elements = [[], [], [], []] - this.toTHREE = function () { - var m = new Matrix4() + class aiMatrix4 { + constructor() { + this.elements = [[], [], [], []] + this.toTHREE = function () { + var m = new Matrix4() - for (let i = 0; i < 4; ++i) { - for (let i2 = 0; i2 < 4; ++i2) { - m.elements[i * 4 + i2] = this.elements[i2][i] + for (let i = 0; i < 4; ++i) { + for (let i2 = 0; i2 < 4; ++i2) { + m.elements[i * 4 + i2] = this.elements[i2][i] + } } - } - return m + return m + } } } @@ -1787,7 +1823,7 @@ AssimpLoader.prototype = Object.assign(Object.create(Loader.prototype), { } return InternReadFile(buffer) - }, -}) + } +} export { AssimpLoader } diff --git a/src/loaders/BVHLoader.d.ts b/src/loaders/BVHLoader.d.ts index 52ede955..2dab6296 100644 --- a/src/loaders/BVHLoader.d.ts +++ b/src/loaders/BVHLoader.d.ts @@ -1,4 +1,5 @@ -import { AnimationClip, Skeleton, Loader, LoadingManager } from 'three' +import { AnimationClip, Skeleton, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export interface BVH { clip: AnimationClip diff --git a/src/loaders/BasisTextureLoader.d.ts b/src/loaders/BasisTextureLoader.d.ts index 159dd25f..8667e828 100644 --- a/src/loaders/BasisTextureLoader.d.ts +++ b/src/loaders/BasisTextureLoader.d.ts @@ -1,4 +1,5 @@ -import { CompressedTexture, Loader, LoadingManager, WebGLRenderer } from 'three' +import { CompressedTexture, LoadingManager, WebGLRenderer } from 'three' +import { Loader } from '../types/Loader' export class BasisTextureLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/BasisTextureLoader.js b/src/loaders/BasisTextureLoader.js index c9b1070c..f568464c 100644 --- a/src/loaders/BasisTextureLoader.js +++ b/src/loaders/BasisTextureLoader.js @@ -33,6 +33,384 @@ import { const _taskCache = new WeakMap() class BasisTextureLoader extends Loader { + /* CONSTANTS */ + + static BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, + } + + static TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, + } + + static EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, + } + + /* WEB WORKER */ + + static BasisWorker = function () { + let config + let transcoderPending + let BasisModule + + const EngineFormat = _EngineFormat + const TranscoderFormat = _TranscoderFormat + const BasisFormat = _BasisFormat + + onmessage = function (e) { + const message = e.data + + switch (message.type) { + case 'init': + config = message.config + init(message.transcoderBinary) + break + + case 'transcode': + transcoderPending.then(() => { + try { + const { width, height, hasAlpha, mipmaps, format } = message.taskConfig.lowLevel + ? transcodeLowLevel(message.taskConfig) + : transcode(message.buffers[0]) + + const buffers = [] + + for (let i = 0; i < mipmaps.length; ++i) { + buffers.push(mipmaps[i].data.buffer) + } + + self.postMessage({ type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers) + } catch (error) { + console.error(error) + + self.postMessage({ type: 'error', id: message.id, error: error.message }) + } + }) + break + } + } + + function init(wasmBinary) { + transcoderPending = new Promise((resolve) => { + BasisModule = { wasmBinary, onRuntimeInitialized: resolve } + BASIS(BasisModule) + }).then(() => { + BasisModule.initializeBasis() + }) + } + + function transcodeLowLevel(taskConfig) { + const { basisFormat, width, height, hasAlpha } = taskConfig + + const { transcoderFormat, engineFormat } = getTranscoderFormat(basisFormat, width, height, hasAlpha) + + const blockByteLength = BasisModule.getBytesPerBlockOrPixel(transcoderFormat) + + assert(BasisModule.isFormatSupported(transcoderFormat), 'THREE.BasisTextureLoader: Unsupported format.') + + const mipmaps = [] + + if (basisFormat === BasisFormat.ETC1S) { + const transcoder = new BasisModule.LowLevelETC1SImageTranscoder() + + const { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData + + try { + let ok + + ok = transcoder.decodePalettes(endpointCount, endpointsData, selectorCount, selectorsData) + + assert(ok, 'THREE.BasisTextureLoader: decodePalettes() failed.') + + ok = transcoder.decodeTables(tablesData) + + assert(ok, 'THREE.BasisTextureLoader: decodeTables() failed.') + + for (let i = 0; i < taskConfig.levels.length; i++) { + const level = taskConfig.levels[i] + const imageDesc = taskConfig.globalData.imageDescs[i] + + const dstByteLength = getTranscodedImageByteLength(transcoderFormat, level.width, level.height) + const dst = new Uint8Array(dstByteLength) + + ok = transcoder.transcodeImage( + transcoderFormat, + dst, + dstByteLength / blockByteLength, + level.data, + getWidthInBlocks(transcoderFormat, level.width), + getHeightInBlocks(transcoderFormat, level.height), + level.width, + level.height, + level.index, + imageDesc.rgbSliceByteOffset, + imageDesc.rgbSliceByteLength, + imageDesc.alphaSliceByteOffset, + imageDesc.alphaSliceByteLength, + imageDesc.imageFlags, + hasAlpha, + false, + 0, + 0, + ) + + assert(ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.') + + mipmaps.push({ data: dst, width: level.width, height: level.height }) + } + } finally { + transcoder.delete() + } + } else { + for (let i = 0; i < taskConfig.levels.length; i++) { + const level = taskConfig.levels[i] + + const dstByteLength = getTranscodedImageByteLength(transcoderFormat, level.width, level.height) + const dst = new Uint8Array(dstByteLength) + + const ok = BasisModule.transcodeUASTCImage( + transcoderFormat, + dst, + dstByteLength / blockByteLength, + level.data, + getWidthInBlocks(transcoderFormat, level.width), + getHeightInBlocks(transcoderFormat, level.height), + level.width, + level.height, + level.index, + 0, + level.data.byteLength, + 0, + hasAlpha, + false, + 0, + 0, + -1, + -1, + ) + + assert(ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.') + + mipmaps.push({ data: dst, width: level.width, height: level.height }) + } + } + + return { width, height, hasAlpha, mipmaps, format: engineFormat } + } + + function transcode(buffer) { + const basisFile = new BasisModule.BasisFile(new Uint8Array(buffer)) + + const basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S + const width = basisFile.getImageWidth(0, 0) + const height = basisFile.getImageHeight(0, 0) + const levels = basisFile.getNumLevels(0) + const hasAlpha = basisFile.getHasAlpha() + + function cleanup() { + basisFile.close() + basisFile.delete() + } + + const { transcoderFormat, engineFormat } = getTranscoderFormat(basisFormat, width, height, hasAlpha) + + if (!width || !height || !levels) { + cleanup() + throw new Error('THREE.BasisTextureLoader: Invalid texture') + } + + if (!basisFile.startTranscoding()) { + cleanup() + throw new Error('THREE.BasisTextureLoader: .startTranscoding failed') + } + + const mipmaps = [] + + for (let mip = 0; mip < levels; mip++) { + const mipWidth = basisFile.getImageWidth(0, mip) + const mipHeight = basisFile.getImageHeight(0, mip) + const dst = new Uint8Array(basisFile.getImageTranscodedSizeInBytes(0, mip, transcoderFormat)) + + const status = basisFile.transcodeImage(dst, 0, mip, transcoderFormat, 0, hasAlpha) + + if (!status) { + cleanup() + throw new Error('THREE.BasisTextureLoader: .transcodeImage failed.') + } + + mipmaps.push({ data: dst, width: mipWidth, height: mipHeight }) + } + + cleanup() + + return { width, height, hasAlpha, mipmaps, format: engineFormat } + } + + // + + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4], + engineFormat: [EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5], + engineFormat: [EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.BC1, TranscoderFormat.BC3], + engineFormat: [EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC2], + engineFormat: [EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC1], + engineFormat: [EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA], + engineFormat: [EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ] + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { + return a.priorityETC1S - b.priorityETC1S + }) + const UASTC_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { + return a.priorityUASTC - b.priorityUASTC + }) + + function getTranscoderFormat(basisFormat, width, height, hasAlpha) { + let transcoderFormat + let engineFormat + + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS + + for (let i = 0; i < options.length; i++) { + const opt = options[i] + + if (!config[opt.if]) continue + if (!opt.basisFormat.includes(basisFormat)) continue + if (opt.needsPowerOfTwo && !(isPowerOfTwo(width) && isPowerOfTwo(height))) continue + + transcoderFormat = opt.transcoderFormat[hasAlpha ? 1 : 0] + engineFormat = opt.engineFormat[hasAlpha ? 1 : 0] + + return { transcoderFormat, engineFormat } + } + + console.warn('THREE.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.') + + transcoderFormat = TranscoderFormat.RGBA32 + engineFormat = EngineFormat.RGBAFormat + + return { transcoderFormat, engineFormat } + } + + function assert(ok, message) { + if (!ok) throw new Error(message) + } + + function getWidthInBlocks(transcoderFormat, width) { + return Math.ceil(width / BasisModule.getFormatBlockWidth(transcoderFormat)) + } + + function getHeightInBlocks(transcoderFormat, height) { + return Math.ceil(height / BasisModule.getFormatBlockHeight(transcoderFormat)) + } + + function getTranscodedImageByteLength(transcoderFormat, width, height) { + const blockByteLength = BasisModule.getBytesPerBlockOrPixel(transcoderFormat) + + if (BasisModule.formatIsUncompressed(transcoderFormat)) { + return width * height * blockByteLength + } + + if (transcoderFormat === TranscoderFormat.PVRTC1_4_RGB || transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA) { + // GL requires extra padding for very small textures: + // https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt + const paddedWidth = (width + 3) & ~3 + const paddedHeight = (height + 3) & ~3 + + return (Math.max(8, paddedWidth) * Math.max(8, paddedHeight) * 4 + 7) / 8 + } + + return getWidthInBlocks(transcoderFormat, width) * getHeightInBlocks(transcoderFormat, height) * blockByteLength + } + + function isPowerOfTwo(value) { + if (value <= 2) return true + + return (value & (value - 1)) === 0 && value !== 0 + } + } + constructor(manager) { super(manager) @@ -275,382 +653,4 @@ class BasisTextureLoader extends Loader { } } -/* CONSTANTS */ - -BasisTextureLoader.BasisFormat = { - ETC1S: 0, - UASTC_4x4: 1, -} - -BasisTextureLoader.TranscoderFormat = { - ETC1: 0, - ETC2: 1, - BC1: 2, - BC3: 3, - BC4: 4, - BC5: 5, - BC7_M6_OPAQUE_ONLY: 6, - BC7_M5: 7, - PVRTC1_4_RGB: 8, - PVRTC1_4_RGBA: 9, - ASTC_4x4: 10, - ATC_RGB: 11, - ATC_RGBA_INTERPOLATED_ALPHA: 12, - RGBA32: 13, - RGB565: 14, - BGR565: 15, - RGBA4444: 16, -} - -BasisTextureLoader.EngineFormat = { - RGBAFormat: RGBAFormat, - RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, - RGBA_BPTC_Format: RGBA_BPTC_Format, - RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - RGB_ETC2_Format: RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, -} - -/* WEB WORKER */ - -BasisTextureLoader.BasisWorker = function () { - let config - let transcoderPending - let BasisModule - - const EngineFormat = _EngineFormat - const TranscoderFormat = _TranscoderFormat - const BasisFormat = _BasisFormat - - onmessage = function (e) { - const message = e.data - - switch (message.type) { - case 'init': - config = message.config - init(message.transcoderBinary) - break - - case 'transcode': - transcoderPending.then(() => { - try { - const { width, height, hasAlpha, mipmaps, format } = message.taskConfig.lowLevel - ? transcodeLowLevel(message.taskConfig) - : transcode(message.buffers[0]) - - const buffers = [] - - for (let i = 0; i < mipmaps.length; ++i) { - buffers.push(mipmaps[i].data.buffer) - } - - self.postMessage({ type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers) - } catch (error) { - console.error(error) - - self.postMessage({ type: 'error', id: message.id, error: error.message }) - } - }) - break - } - } - - function init(wasmBinary) { - transcoderPending = new Promise((resolve) => { - BasisModule = { wasmBinary, onRuntimeInitialized: resolve } - BASIS(BasisModule) - }).then(() => { - BasisModule.initializeBasis() - }) - } - - function transcodeLowLevel(taskConfig) { - const { basisFormat, width, height, hasAlpha } = taskConfig - - const { transcoderFormat, engineFormat } = getTranscoderFormat(basisFormat, width, height, hasAlpha) - - const blockByteLength = BasisModule.getBytesPerBlockOrPixel(transcoderFormat) - - assert(BasisModule.isFormatSupported(transcoderFormat), 'THREE.BasisTextureLoader: Unsupported format.') - - const mipmaps = [] - - if (basisFormat === BasisFormat.ETC1S) { - const transcoder = new BasisModule.LowLevelETC1SImageTranscoder() - - const { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData - - try { - let ok - - ok = transcoder.decodePalettes(endpointCount, endpointsData, selectorCount, selectorsData) - - assert(ok, 'THREE.BasisTextureLoader: decodePalettes() failed.') - - ok = transcoder.decodeTables(tablesData) - - assert(ok, 'THREE.BasisTextureLoader: decodeTables() failed.') - - for (let i = 0; i < taskConfig.levels.length; i++) { - const level = taskConfig.levels[i] - const imageDesc = taskConfig.globalData.imageDescs[i] - - const dstByteLength = getTranscodedImageByteLength(transcoderFormat, level.width, level.height) - const dst = new Uint8Array(dstByteLength) - - ok = transcoder.transcodeImage( - transcoderFormat, - dst, - dstByteLength / blockByteLength, - level.data, - getWidthInBlocks(transcoderFormat, level.width), - getHeightInBlocks(transcoderFormat, level.height), - level.width, - level.height, - level.index, - imageDesc.rgbSliceByteOffset, - imageDesc.rgbSliceByteLength, - imageDesc.alphaSliceByteOffset, - imageDesc.alphaSliceByteLength, - imageDesc.imageFlags, - hasAlpha, - false, - 0, - 0, - ) - - assert(ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.') - - mipmaps.push({ data: dst, width: level.width, height: level.height }) - } - } finally { - transcoder.delete() - } - } else { - for (let i = 0; i < taskConfig.levels.length; i++) { - const level = taskConfig.levels[i] - - const dstByteLength = getTranscodedImageByteLength(transcoderFormat, level.width, level.height) - const dst = new Uint8Array(dstByteLength) - - const ok = BasisModule.transcodeUASTCImage( - transcoderFormat, - dst, - dstByteLength / blockByteLength, - level.data, - getWidthInBlocks(transcoderFormat, level.width), - getHeightInBlocks(transcoderFormat, level.height), - level.width, - level.height, - level.index, - 0, - level.data.byteLength, - 0, - hasAlpha, - false, - 0, - 0, - -1, - -1, - ) - - assert(ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.') - - mipmaps.push({ data: dst, width: level.width, height: level.height }) - } - } - - return { width, height, hasAlpha, mipmaps, format: engineFormat } - } - - function transcode(buffer) { - const basisFile = new BasisModule.BasisFile(new Uint8Array(buffer)) - - const basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S - const width = basisFile.getImageWidth(0, 0) - const height = basisFile.getImageHeight(0, 0) - const levels = basisFile.getNumLevels(0) - const hasAlpha = basisFile.getHasAlpha() - - function cleanup() { - basisFile.close() - basisFile.delete() - } - - const { transcoderFormat, engineFormat } = getTranscoderFormat(basisFormat, width, height, hasAlpha) - - if (!width || !height || !levels) { - cleanup() - throw new Error('THREE.BasisTextureLoader: Invalid texture') - } - - if (!basisFile.startTranscoding()) { - cleanup() - throw new Error('THREE.BasisTextureLoader: .startTranscoding failed') - } - - const mipmaps = [] - - for (let mip = 0; mip < levels; mip++) { - const mipWidth = basisFile.getImageWidth(0, mip) - const mipHeight = basisFile.getImageHeight(0, mip) - const dst = new Uint8Array(basisFile.getImageTranscodedSizeInBytes(0, mip, transcoderFormat)) - - const status = basisFile.transcodeImage(dst, 0, mip, transcoderFormat, 0, hasAlpha) - - if (!status) { - cleanup() - throw new Error('THREE.BasisTextureLoader: .transcodeImage failed.') - } - - mipmaps.push({ data: dst, width: mipWidth, height: mipHeight }) - } - - cleanup() - - return { width, height, hasAlpha, mipmaps, format: engineFormat } - } - - // - - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. - // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. - const FORMAT_OPTIONS = [ - { - if: 'astcSupported', - basisFormat: [BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4], - engineFormat: [EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format], - priorityETC1S: Infinity, - priorityUASTC: 1, - needsPowerOfTwo: false, - }, - { - if: 'bptcSupported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5], - engineFormat: [EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format], - priorityETC1S: 3, - priorityUASTC: 2, - needsPowerOfTwo: false, - }, - { - if: 'dxtSupported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.BC1, TranscoderFormat.BC3], - engineFormat: [EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format], - priorityETC1S: 4, - priorityUASTC: 5, - needsPowerOfTwo: false, - }, - { - if: 'etc2Supported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC2], - engineFormat: [EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format], - priorityETC1S: 1, - priorityUASTC: 3, - needsPowerOfTwo: false, - }, - { - if: 'etc1Supported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC1], - engineFormat: [EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format], - priorityETC1S: 2, - priorityUASTC: 4, - needsPowerOfTwo: false, - }, - { - if: 'pvrtcSupported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA], - engineFormat: [EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format], - priorityETC1S: 5, - priorityUASTC: 6, - needsPowerOfTwo: true, - }, - ] - - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { - return a.priorityETC1S - b.priorityETC1S - }) - const UASTC_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { - return a.priorityUASTC - b.priorityUASTC - }) - - function getTranscoderFormat(basisFormat, width, height, hasAlpha) { - let transcoderFormat - let engineFormat - - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS - - for (let i = 0; i < options.length; i++) { - const opt = options[i] - - if (!config[opt.if]) continue - if (!opt.basisFormat.includes(basisFormat)) continue - if (opt.needsPowerOfTwo && !(isPowerOfTwo(width) && isPowerOfTwo(height))) continue - - transcoderFormat = opt.transcoderFormat[hasAlpha ? 1 : 0] - engineFormat = opt.engineFormat[hasAlpha ? 1 : 0] - - return { transcoderFormat, engineFormat } - } - - console.warn('THREE.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.') - - transcoderFormat = TranscoderFormat.RGBA32 - engineFormat = EngineFormat.RGBAFormat - - return { transcoderFormat, engineFormat } - } - - function assert(ok, message) { - if (!ok) throw new Error(message) - } - - function getWidthInBlocks(transcoderFormat, width) { - return Math.ceil(width / BasisModule.getFormatBlockWidth(transcoderFormat)) - } - - function getHeightInBlocks(transcoderFormat, height) { - return Math.ceil(height / BasisModule.getFormatBlockHeight(transcoderFormat)) - } - - function getTranscodedImageByteLength(transcoderFormat, width, height) { - const blockByteLength = BasisModule.getBytesPerBlockOrPixel(transcoderFormat) - - if (BasisModule.formatIsUncompressed(transcoderFormat)) { - return width * height * blockByteLength - } - - if (transcoderFormat === TranscoderFormat.PVRTC1_4_RGB || transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA) { - // GL requires extra padding for very small textures: - // https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt - const paddedWidth = (width + 3) & ~3 - const paddedHeight = (height + 3) & ~3 - - return (Math.max(8, paddedWidth) * Math.max(8, paddedHeight) * 4 + 7) / 8 - } - - return getWidthInBlocks(transcoderFormat, width) * getHeightInBlocks(transcoderFormat, height) * blockByteLength - } - - function isPowerOfTwo(value) { - if (value <= 2) return true - - return (value & (value - 1)) === 0 && value !== 0 - } -} - export { BasisTextureLoader } diff --git a/src/loaders/ColladaLoader.d.ts b/src/loaders/ColladaLoader.d.ts index e58ecb28..681bdf2a 100644 --- a/src/loaders/ColladaLoader.d.ts +++ b/src/loaders/ColladaLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Scene } from 'three' +import { LoadingManager, Scene } from 'three' +import { Loader } from '../types/Loader' export interface Collada { kinematics: object diff --git a/src/loaders/ColladaLoader.js b/src/loaders/ColladaLoader.js index 4df58002..6b534f87 100644 --- a/src/loaders/ColladaLoader.js +++ b/src/loaders/ColladaLoader.js @@ -38,7 +38,7 @@ import { Vector3, VectorKeyframeTrack, } from 'three' -import { TGALoader } from '../loaders/TGALoader.js' +import { TGALoader } from '../loaders/TGALoader' class ColladaLoader extends Loader { constructor(manager) { diff --git a/src/loaders/DRACOLoader.d.ts b/src/loaders/DRACOLoader.d.ts index 074d9d66..a2429e6b 100644 --- a/src/loaders/DRACOLoader.d.ts +++ b/src/loaders/DRACOLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, BufferGeometry } from 'three' +import { LoadingManager, BufferGeometry } from 'three' +import { Loader } from '../types/Loader' export class DRACOLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/FBXLoader.d.ts b/src/loaders/FBXLoader.d.ts index 12157bce..0f677efc 100644 --- a/src/loaders/FBXLoader.d.ts +++ b/src/loaders/FBXLoader.d.ts @@ -1,4 +1,5 @@ -import { Group, Loader, LoadingManager } from 'three' +import { Group, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class FBXLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/FBXLoader.js b/src/loaders/FBXLoader.js index 7410e737..3e18c5fb 100644 --- a/src/loaders/FBXLoader.js +++ b/src/loaders/FBXLoader.js @@ -3116,8 +3116,8 @@ function getData(polygonVertexIndex, polygonIndex, vertexIndex, infoObject) { return slice(dataArray, infoObject.buffer, from, to) } -const tempEuler = new Euler() -const tempVec = new Vector3() +const tempEuler = /* @__PURE__ */ new Euler() +const tempVec = /* @__PURE__ */ new Vector3() // generate transformation from FBX transform data // ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm diff --git a/src/loaders/FontLoader.ts b/src/loaders/FontLoader.ts index 0de9e092..b03ea6cc 100644 --- a/src/loaders/FontLoader.ts +++ b/src/loaders/FontLoader.ts @@ -1,4 +1,5 @@ -import { FileLoader, Loader, ShapePath } from 'three' +import { FileLoader, ShapePath } from 'three' +import { Loader } from '../types/Loader' import type { LoadingManager, Shape } from 'three' diff --git a/src/loaders/GCodeLoader.d.ts b/src/loaders/GCodeLoader.d.ts index 0d57851a..e4c73e7c 100644 --- a/src/loaders/GCodeLoader.d.ts +++ b/src/loaders/GCodeLoader.d.ts @@ -1,4 +1,5 @@ -import { Group, Loader, LoadingManager } from 'three' +import { Group, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class GCodeLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/GLTFLoader.d.ts b/src/loaders/GLTFLoader.d.ts index 7a82936c..268972e7 100644 --- a/src/loaders/GLTFLoader.d.ts +++ b/src/loaders/GLTFLoader.d.ts @@ -5,7 +5,6 @@ import { Camera, Group, InterleavedBufferAttribute, - Loader, LoadingManager, Mesh, MeshStandardMaterial, @@ -16,7 +15,9 @@ import { TextureLoader, FileLoader, ImageBitmapLoader, + Skeleton, } from 'three' +import { Loader } from '../types/Loader' import { DRACOLoader } from './DRACOLoader' import { KTX2Loader } from './KTX2Loader' @@ -65,7 +66,7 @@ export class GLTFLoader extends Loader { onError?: (event: ErrorEvent) => void, ): void - parseAsync(data: ArrayBuffer | string, path: string): Promise + parseAsync(data: ArrayBuffer | string, path: string): Promise } export type GLTFReferenceType = 'materials' | 'nodes' | 'textures' | 'meshes' @@ -91,10 +92,15 @@ export class GLTFParser { fileLoader: FileLoader textureLoader: TextureLoader | ImageBitmapLoader - plugins: GLTFLoaderPlugin + plugins: { [name: string]: GLTFLoaderPlugin } extensions: { [name: string]: any } associations: Map + setExtensions(extensions: { [name: string]: any }): void + setPlugins(plugins: { [name: string]: GLTFLoaderPlugin }): void + + parse(onLoad: (gltf: GLTF) => void, onError?: (event: ErrorEvent) => void): void + getDependency: (type: string, index: number) => Promise getDependencies: (type: string) => Promise loadBuffer: (bufferIndex: number) => Promise @@ -126,12 +132,7 @@ export class GLTFParser { ) => Promise loadMesh: (meshIndex: number) => Promise loadCamera: (cameraIndex: number) => Promise - loadSkin: ( - skinIndex: number, - ) => Promise<{ - joints: number[] - inverseBindMatrices?: BufferAttribute | InterleavedBufferAttribute | undefined - }> + loadSkin: (skinIndex: number) => Promise loadAnimation: (animationIndex: number) => Promise loadNode: (nodeIndex: number) => Promise loadScene: () => Promise @@ -140,6 +141,7 @@ export class GLTFParser { export interface GLTFLoaderPlugin { beforeRoot?: (() => Promise | null) | undefined afterRoot?: ((result: GLTF) => Promise | null) | undefined + loadNode?: ((nodeIndex: number) => Promise | null) | undefined loadMesh?: ((meshIndex: number) => Promise | null) | undefined loadBufferView?: ((bufferViewIndex: number) => Promise | null) | undefined loadMaterial?: ((materialIndex: number) => Promise | null) | undefined diff --git a/src/loaders/GLTFLoader.js b/src/loaders/GLTFLoader.js index bdf833d2..9eb957eb 100644 --- a/src/loaders/GLTFLoader.js +++ b/src/loaders/GLTFLoader.js @@ -1507,7 +1507,7 @@ class GLTFCubicSplineInterpolant extends Interpolant { } } -const _q = new Quaternion() +const _q = /* @__PURE__ */ new Quaternion() class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { interpolate_(i1, t0, t, t1) { @@ -1588,7 +1588,7 @@ const ATTRIBUTES = { // uv => uv1, 4 uv channels // https://github.com/mrdoob/three.js/pull/25943 // https://github.com/mrdoob/three.js/pull/25788 - ...(REVISION.replace(/\D+/g, '') >= 152 + .../* @__PURE__ */ (REVISION.replace(/\D+/g, '') >= 152 ? { TEXCOORD_0: 'uv', TEXCOORD_1: 'uv1', @@ -1830,7 +1830,7 @@ function getImageURIMimeType(uri) { return 'image/png' } -const _identityMatrix = new Matrix4() +const _identityMatrix = /* @__PURE__ */ new Matrix4() /* GLTF PARSER */ diff --git a/src/loaders/HDRCubeTextureLoader.d.ts b/src/loaders/HDRCubeTextureLoader.d.ts index 52cfbd68..d773a908 100644 --- a/src/loaders/HDRCubeTextureLoader.d.ts +++ b/src/loaders/HDRCubeTextureLoader.d.ts @@ -1,4 +1,5 @@ -import { CubeTexture, Loader, LoadingManager, TextureDataType } from 'three' +import { CubeTexture, LoadingManager, TextureDataType } from 'three' +import { Loader } from '../types/Loader' import { RGBELoader } from './RGBELoader' diff --git a/src/loaders/KMZLoader.d.ts b/src/loaders/KMZLoader.d.ts index 762db712..53501a0f 100644 --- a/src/loaders/KMZLoader.d.ts +++ b/src/loaders/KMZLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager } from 'three' +import { LoadingManager } from 'three' +import { Loader } from '../types/Loader' import { Collada } from './ColladaLoader' diff --git a/src/loaders/KTX2Loader.js b/src/loaders/KTX2Loader.js index e5d56698..eef1646d 100644 --- a/src/loaders/KTX2Loader.js +++ b/src/loaders/KTX2Loader.js @@ -34,7 +34,7 @@ import { RGFormat, UnsignedByteType, } from 'three' -import { WorkerPool } from '../utils/WorkerPool.js' +import { WorkerPool } from '../utils/WorkerPool' import { read, KHR_DF_FLAG_ALPHA_PREMULTIPLIED, @@ -66,6 +66,309 @@ let _activeLoaders = 0 let _zstd class KTX2Loader extends Loader { + /* CONSTANTS */ + + static BasisFormat = { + ETC1S: 0, + UASTC_4x4: 1, + } + + static TranscoderFormat = { + ETC1: 0, + ETC2: 1, + BC1: 2, + BC3: 3, + BC4: 4, + BC5: 5, + BC7_M6_OPAQUE_ONLY: 6, + BC7_M5: 7, + PVRTC1_4_RGB: 8, + PVRTC1_4_RGBA: 9, + ASTC_4x4: 10, + ATC_RGB: 11, + ATC_RGBA_INTERPOLATED_ALPHA: 12, + RGBA32: 13, + RGB565: 14, + BGR565: 15, + RGBA4444: 16, + } + + static EngineFormat = { + RGBAFormat: RGBAFormat, + RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format: RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format: RGB_ETC1_Format, + RGB_ETC2_Format: RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, + } + + /* WEB WORKER */ + + static BasisWorker = function () { + let config + let transcoderPending + let BasisModule + + const EngineFormat = _EngineFormat + const TranscoderFormat = _TranscoderFormat + const BasisFormat = _BasisFormat + + self.addEventListener('message', function (e) { + const message = e.data + + switch (message.type) { + case 'init': + config = message.config + init(message.transcoderBinary) + break + + case 'transcode': + transcoderPending.then(() => { + try { + const { width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags } = transcode(message.buffer) + + const buffers = [] + + for (let i = 0; i < mipmaps.length; ++i) { + buffers.push(mipmaps[i].data.buffer) + } + + self.postMessage( + { + type: 'transcode', + id: message.id, + width, + height, + hasAlpha, + mipmaps, + format, + dfdTransferFn, + dfdFlags, + }, + buffers, + ) + } catch (error) { + console.error(error) + + self.postMessage({ type: 'error', id: message.id, error: error.message }) + } + }) + break + } + }) + + function init(wasmBinary) { + transcoderPending = new Promise((resolve) => { + BasisModule = { wasmBinary, onRuntimeInitialized: resolve } + BASIS(BasisModule) + }).then(() => { + BasisModule.initializeBasis() + + if (BasisModule.KTX2File === undefined) { + console.warn('THREE.KTX2Loader: Please update Basis Universal transcoder.') + } + }) + } + + function transcode(buffer) { + const ktx2File = new BasisModule.KTX2File(new Uint8Array(buffer)) + + function cleanup() { + ktx2File.close() + ktx2File.delete() + } + + if (!ktx2File.isValid()) { + cleanup() + throw new Error('THREE.KTX2Loader: Invalid or unsupported .ktx2 file') + } + + const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S + const width = ktx2File.getWidth() + const height = ktx2File.getHeight() + const layers = ktx2File.getLayers() || 1 + const levels = ktx2File.getLevels() + const hasAlpha = ktx2File.getHasAlpha() + const dfdTransferFn = ktx2File.getDFDTransferFunc() + const dfdFlags = ktx2File.getDFDFlags() + + const { transcoderFormat, engineFormat } = getTranscoderFormat(basisFormat, width, height, hasAlpha) + + if (!width || !height || !levels) { + cleanup() + throw new Error('THREE.KTX2Loader: Invalid texture') + } + + if (!ktx2File.startTranscoding()) { + cleanup() + throw new Error('THREE.KTX2Loader: .startTranscoding failed') + } + + const mipmaps = [] + + for (let mip = 0; mip < levels; mip++) { + const layerMips = [] + + let mipWidth, mipHeight + + for (let layer = 0; layer < layers; layer++) { + const levelInfo = ktx2File.getImageLevelInfo(mip, layer, 0) + mipWidth = levelInfo.origWidth < 4 ? levelInfo.origWidth : levelInfo.width + mipHeight = levelInfo.origHeight < 4 ? levelInfo.origHeight : levelInfo.height + const dst = new Uint8Array(ktx2File.getImageTranscodedSizeInBytes(mip, layer, 0, transcoderFormat)) + const status = ktx2File.transcodeImage(dst, mip, layer, 0, transcoderFormat, 0, -1, -1) + + if (!status) { + cleanup() + throw new Error('THREE.KTX2Loader: .transcodeImage failed.') + } + + layerMips.push(dst) + } + + mipmaps.push({ data: concat(layerMips), width: mipWidth, height: mipHeight }) + } + + cleanup() + + return { width, height, hasAlpha, mipmaps, format: engineFormat, dfdTransferFn, dfdFlags } + } + + // + + // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), + // device capabilities, and texture dimensions. The list below ranks the formats separately + // for ETC1S and UASTC. + // + // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at + // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently + // chooses RGBA32 only as a last resort and does not expose that option to the caller. + const FORMAT_OPTIONS = [ + { + if: 'astcSupported', + basisFormat: [BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4], + engineFormat: [EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format], + priorityETC1S: Infinity, + priorityUASTC: 1, + needsPowerOfTwo: false, + }, + { + if: 'bptcSupported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5], + engineFormat: [EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format], + priorityETC1S: 3, + priorityUASTC: 2, + needsPowerOfTwo: false, + }, + { + if: 'dxtSupported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.BC1, TranscoderFormat.BC3], + engineFormat: [EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format], + priorityETC1S: 4, + priorityUASTC: 5, + needsPowerOfTwo: false, + }, + { + if: 'etc2Supported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC2], + engineFormat: [EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format], + priorityETC1S: 1, + priorityUASTC: 3, + needsPowerOfTwo: false, + }, + { + if: 'etc1Supported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.ETC1], + engineFormat: [EngineFormat.RGB_ETC1_Format], + priorityETC1S: 2, + priorityUASTC: 4, + needsPowerOfTwo: false, + }, + { + if: 'pvrtcSupported', + basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], + transcoderFormat: [TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA], + engineFormat: [EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format], + priorityETC1S: 5, + priorityUASTC: 6, + needsPowerOfTwo: true, + }, + ] + + const ETC1S_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { + return a.priorityETC1S - b.priorityETC1S + }) + const UASTC_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { + return a.priorityUASTC - b.priorityUASTC + }) + + function getTranscoderFormat(basisFormat, width, height, hasAlpha) { + let transcoderFormat + let engineFormat + + const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS + + for (let i = 0; i < options.length; i++) { + const opt = options[i] + + if (!config[opt.if]) continue + if (!opt.basisFormat.includes(basisFormat)) continue + if (hasAlpha && opt.transcoderFormat.length < 2) continue + if (opt.needsPowerOfTwo && !(isPowerOfTwo(width) && isPowerOfTwo(height))) continue + + transcoderFormat = opt.transcoderFormat[hasAlpha ? 1 : 0] + engineFormat = opt.engineFormat[hasAlpha ? 1 : 0] + + return { transcoderFormat, engineFormat } + } + + console.warn('THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.') + + transcoderFormat = TranscoderFormat.RGBA32 + engineFormat = EngineFormat.RGBAFormat + + return { transcoderFormat, engineFormat } + } + + function isPowerOfTwo(value) { + if (value <= 2) return true + + return (value & (value - 1)) === 0 && value !== 0 + } + + /** Concatenates N byte arrays. */ + function concat(arrays) { + let totalByteLength = 0 + + for (let i = 0; i < arrays.length; i++) { + const array = arrays[i] + totalByteLength += array.byteLength + } + + const result = new Uint8Array(totalByteLength) + + let byteOffset = 0 + + for (let i = 0; i < arrays.length; i++) { + const array = arrays[i] + result.set(array, byteOffset) + + byteOffset += array.byteLength + } + + return result + } + } + constructor(manager) { super(manager) @@ -262,299 +565,6 @@ class KTX2Loader extends Loader { } } -/* CONSTANTS */ - -KTX2Loader.BasisFormat = { - ETC1S: 0, - UASTC_4x4: 1, -} - -KTX2Loader.TranscoderFormat = { - ETC1: 0, - ETC2: 1, - BC1: 2, - BC3: 3, - BC4: 4, - BC5: 5, - BC7_M6_OPAQUE_ONLY: 6, - BC7_M5: 7, - PVRTC1_4_RGB: 8, - PVRTC1_4_RGBA: 9, - ASTC_4x4: 10, - ATC_RGB: 11, - ATC_RGBA_INTERPOLATED_ALPHA: 12, - RGBA32: 13, - RGB565: 14, - BGR565: 15, - RGBA4444: 16, -} - -KTX2Loader.EngineFormat = { - RGBAFormat: RGBAFormat, - RGBA_ASTC_4x4_Format: RGBA_ASTC_4x4_Format, - RGBA_BPTC_Format: RGBA_BPTC_Format, - RGBA_ETC2_EAC_Format: RGBA_ETC2_EAC_Format, - RGBA_PVRTC_4BPPV1_Format: RGBA_PVRTC_4BPPV1_Format, - RGBA_S3TC_DXT5_Format: RGBA_S3TC_DXT5_Format, - RGB_ETC1_Format: RGB_ETC1_Format, - RGB_ETC2_Format: RGB_ETC2_Format, - RGB_PVRTC_4BPPV1_Format: RGB_PVRTC_4BPPV1_Format, - RGB_S3TC_DXT1_Format: RGB_S3TC_DXT1_Format, -} - -/* WEB WORKER */ - -KTX2Loader.BasisWorker = function () { - let config - let transcoderPending - let BasisModule - - const EngineFormat = _EngineFormat - const TranscoderFormat = _TranscoderFormat - const BasisFormat = _BasisFormat - - self.addEventListener('message', function (e) { - const message = e.data - - switch (message.type) { - case 'init': - config = message.config - init(message.transcoderBinary) - break - - case 'transcode': - transcoderPending.then(() => { - try { - const { width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags } = transcode(message.buffer) - - const buffers = [] - - for (let i = 0; i < mipmaps.length; ++i) { - buffers.push(mipmaps[i].data.buffer) - } - - self.postMessage( - { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format, dfdTransferFn, dfdFlags }, - buffers, - ) - } catch (error) { - console.error(error) - - self.postMessage({ type: 'error', id: message.id, error: error.message }) - } - }) - break - } - }) - - function init(wasmBinary) { - transcoderPending = new Promise((resolve) => { - BasisModule = { wasmBinary, onRuntimeInitialized: resolve } - BASIS(BasisModule) - }).then(() => { - BasisModule.initializeBasis() - - if (BasisModule.KTX2File === undefined) { - console.warn('THREE.KTX2Loader: Please update Basis Universal transcoder.') - } - }) - } - - function transcode(buffer) { - const ktx2File = new BasisModule.KTX2File(new Uint8Array(buffer)) - - function cleanup() { - ktx2File.close() - ktx2File.delete() - } - - if (!ktx2File.isValid()) { - cleanup() - throw new Error('THREE.KTX2Loader: Invalid or unsupported .ktx2 file') - } - - const basisFormat = ktx2File.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S - const width = ktx2File.getWidth() - const height = ktx2File.getHeight() - const layers = ktx2File.getLayers() || 1 - const levels = ktx2File.getLevels() - const hasAlpha = ktx2File.getHasAlpha() - const dfdTransferFn = ktx2File.getDFDTransferFunc() - const dfdFlags = ktx2File.getDFDFlags() - - const { transcoderFormat, engineFormat } = getTranscoderFormat(basisFormat, width, height, hasAlpha) - - if (!width || !height || !levels) { - cleanup() - throw new Error('THREE.KTX2Loader: Invalid texture') - } - - if (!ktx2File.startTranscoding()) { - cleanup() - throw new Error('THREE.KTX2Loader: .startTranscoding failed') - } - - const mipmaps = [] - - for (let mip = 0; mip < levels; mip++) { - const layerMips = [] - - let mipWidth, mipHeight - - for (let layer = 0; layer < layers; layer++) { - const levelInfo = ktx2File.getImageLevelInfo(mip, layer, 0) - mipWidth = levelInfo.origWidth < 4 ? levelInfo.origWidth : levelInfo.width - mipHeight = levelInfo.origHeight < 4 ? levelInfo.origHeight : levelInfo.height - const dst = new Uint8Array(ktx2File.getImageTranscodedSizeInBytes(mip, layer, 0, transcoderFormat)) - const status = ktx2File.transcodeImage(dst, mip, layer, 0, transcoderFormat, 0, -1, -1) - - if (!status) { - cleanup() - throw new Error('THREE.KTX2Loader: .transcodeImage failed.') - } - - layerMips.push(dst) - } - - mipmaps.push({ data: concat(layerMips), width: mipWidth, height: mipHeight }) - } - - cleanup() - - return { width, height, hasAlpha, mipmaps, format: engineFormat, dfdTransferFn, dfdFlags } - } - - // - - // Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC), - // device capabilities, and texture dimensions. The list below ranks the formats separately - // for ETC1S and UASTC. - // - // In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at - // significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently - // chooses RGBA32 only as a last resort and does not expose that option to the caller. - const FORMAT_OPTIONS = [ - { - if: 'astcSupported', - basisFormat: [BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4], - engineFormat: [EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format], - priorityETC1S: Infinity, - priorityUASTC: 1, - needsPowerOfTwo: false, - }, - { - if: 'bptcSupported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5], - engineFormat: [EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format], - priorityETC1S: 3, - priorityUASTC: 2, - needsPowerOfTwo: false, - }, - { - if: 'dxtSupported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.BC1, TranscoderFormat.BC3], - engineFormat: [EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format], - priorityETC1S: 4, - priorityUASTC: 5, - needsPowerOfTwo: false, - }, - { - if: 'etc2Supported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.ETC1, TranscoderFormat.ETC2], - engineFormat: [EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format], - priorityETC1S: 1, - priorityUASTC: 3, - needsPowerOfTwo: false, - }, - { - if: 'etc1Supported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.ETC1], - engineFormat: [EngineFormat.RGB_ETC1_Format], - priorityETC1S: 2, - priorityUASTC: 4, - needsPowerOfTwo: false, - }, - { - if: 'pvrtcSupported', - basisFormat: [BasisFormat.ETC1S, BasisFormat.UASTC_4x4], - transcoderFormat: [TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA], - engineFormat: [EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format], - priorityETC1S: 5, - priorityUASTC: 6, - needsPowerOfTwo: true, - }, - ] - - const ETC1S_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { - return a.priorityETC1S - b.priorityETC1S - }) - const UASTC_OPTIONS = FORMAT_OPTIONS.sort(function (a, b) { - return a.priorityUASTC - b.priorityUASTC - }) - - function getTranscoderFormat(basisFormat, width, height, hasAlpha) { - let transcoderFormat - let engineFormat - - const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS - - for (let i = 0; i < options.length; i++) { - const opt = options[i] - - if (!config[opt.if]) continue - if (!opt.basisFormat.includes(basisFormat)) continue - if (hasAlpha && opt.transcoderFormat.length < 2) continue - if (opt.needsPowerOfTwo && !(isPowerOfTwo(width) && isPowerOfTwo(height))) continue - - transcoderFormat = opt.transcoderFormat[hasAlpha ? 1 : 0] - engineFormat = opt.engineFormat[hasAlpha ? 1 : 0] - - return { transcoderFormat, engineFormat } - } - - console.warn('THREE.KTX2Loader: No suitable compressed texture format found. Decoding to RGBA32.') - - transcoderFormat = TranscoderFormat.RGBA32 - engineFormat = EngineFormat.RGBAFormat - - return { transcoderFormat, engineFormat } - } - - function isPowerOfTwo(value) { - if (value <= 2) return true - - return (value & (value - 1)) === 0 && value !== 0 - } - - /** Concatenates N byte arrays. */ - function concat(arrays) { - let totalByteLength = 0 - - for (let i = 0; i < arrays.length; i++) { - const array = arrays[i] - totalByteLength += array.byteLength - } - - const result = new Uint8Array(totalByteLength) - - let byteOffset = 0 - - for (let i = 0; i < arrays.length; i++) { - const array = arrays[i] - result.set(array, byteOffset) - - byteOffset += array.byteLength - } - - return result - } -} - // // DataTexture and Data3DTexture parsing. diff --git a/src/loaders/LDrawLoader.d.ts b/src/loaders/LDrawLoader.d.ts index a4d9bed6..635f8638 100644 --- a/src/loaders/LDrawLoader.d.ts +++ b/src/loaders/LDrawLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Group, Material } from 'three' +import { LoadingManager, Group, Material } from 'three' +import { Loader } from '../types/Loader' export class LDrawLoader extends Loader { materials: Material[] diff --git a/src/loaders/LDrawLoader.js b/src/loaders/LDrawLoader.js index 8de7de7f..3debe46c 100644 --- a/src/loaders/LDrawLoader.js +++ b/src/loaders/LDrawLoader.js @@ -39,8 +39,8 @@ const FILE_LOCATION_NOT_FOUND = 6 const MAIN_COLOUR_CODE = '16' const MAIN_EDGE_COLOUR_CODE = '24' -const _tempVec0 = new Vector3() -const _tempVec1 = new Vector3() +const _tempVec0 = /* @__PURE__ */ new Vector3() +const _tempVec1 = /* @__PURE__ */ new Vector3() class LDrawConditionalLineMaterial extends ShaderMaterial { constructor(parameters) { @@ -179,7 +179,7 @@ function generateFaceNormals(faces) { } } -const _ray = new Ray() +const _ray = /* @__PURE__ */ new Ray() function smoothNormals(faces, lineSegments, checkSubSegments = false) { // NOTE: 1e2 is pretty coarse but was chosen to quantize the resulting value because // it allows edges to be smoothed as expected (see minifig arms). diff --git a/src/loaders/LUT3dlLoader.d.ts b/src/loaders/LUT3dlLoader.d.ts index 180453e7..72d7e4be 100644 --- a/src/loaders/LUT3dlLoader.d.ts +++ b/src/loaders/LUT3dlLoader.d.ts @@ -1,9 +1,10 @@ -import { Loader, LoadingManager, DataTexture, Data3DTexture } from 'three' +import { LoadingManager, DataTexture, Texture } from 'three' +import { Loader } from '../types/Loader' export interface LUT3dlResult { size: number texture: DataTexture - texture3D: Data3DTexture + texture3D: Texture // Data3DTexture } export class LUT3dlLoader extends Loader { diff --git a/src/loaders/LUTCubeLoader.d.ts b/src/loaders/LUTCubeLoader.d.ts index daa1eb29..15ab2a63 100644 --- a/src/loaders/LUTCubeLoader.d.ts +++ b/src/loaders/LUTCubeLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Vector3, DataTexture, Data3DTexture } from 'three' +import { LoadingManager, Vector3, DataTexture, Texture } from 'three' +import { Loader } from '../types/Loader' export interface LUTCubeResult { title: string @@ -6,7 +7,7 @@ export interface LUTCubeResult { domainMin: Vector3 domainMax: Vector3 texture: DataTexture - texture3D: Data3DTexture + texture3D: Texture // Data3DTexture } export class LUTCubeLoader extends Loader { diff --git a/src/loaders/LWOLoader.d.ts b/src/loaders/LWOLoader.d.ts index e35d7aad..8de38e9a 100644 --- a/src/loaders/LWOLoader.d.ts +++ b/src/loaders/LWOLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Material, Object3D } from 'three' +import { LoadingManager, Material, Object3D } from 'three' +import { Loader } from '../types/Loader' export interface LWO { materials: Material[] diff --git a/src/loaders/LottieLoader.d.ts b/src/loaders/LottieLoader.d.ts index 093cf231..66931ba8 100644 --- a/src/loaders/LottieLoader.d.ts +++ b/src/loaders/LottieLoader.d.ts @@ -1,4 +1,5 @@ -import { CanvasTexture, Loader, LoadingManager } from 'three' +import { CanvasTexture, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class LottieLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/MD2Loader.d.ts b/src/loaders/MD2Loader.d.ts index 771f3be6..58a4ebe1 100644 --- a/src/loaders/MD2Loader.d.ts +++ b/src/loaders/MD2Loader.d.ts @@ -1,4 +1,5 @@ -import { BufferGeometry, Loader, LoadingManager } from 'three' +import { BufferGeometry, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class MD2Loader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/MDDLoader.d.ts b/src/loaders/MDDLoader.d.ts index 2255818b..e39c9e78 100644 --- a/src/loaders/MDDLoader.d.ts +++ b/src/loaders/MDDLoader.d.ts @@ -1,4 +1,5 @@ -import { AnimationClip, BufferAttribute, Loader, LoadingManager } from 'three' +import { AnimationClip, BufferAttribute, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export interface MDD { morphTargets: BufferAttribute[] diff --git a/src/loaders/MMDLoader.d.ts b/src/loaders/MMDLoader.d.ts index be709e03..8190eb25 100644 --- a/src/loaders/MMDLoader.d.ts +++ b/src/loaders/MMDLoader.d.ts @@ -1,4 +1,5 @@ -import { Camera, AnimationClip, FileLoader, Loader, LoadingManager, SkinnedMesh } from 'three' +import { Camera, AnimationClip, FileLoader, LoadingManager, SkinnedMesh } from 'three' +import { Loader } from '../types/Loader' export interface MMDLoaderAnimationObject { animation: AnimationClip diff --git a/src/loaders/MTLLoader.d.ts b/src/loaders/MTLLoader.d.ts index 176a37b7..169f4dae 100644 --- a/src/loaders/MTLLoader.d.ts +++ b/src/loaders/MTLLoader.d.ts @@ -1,4 +1,5 @@ -import { Material, LoadingManager, Mapping, Loader, BufferGeometry, Side, Texture, Vector2, Wrapping } from 'three' +import { Material, LoadingManager, Mapping, BufferGeometry, Side, Texture, Vector2, Wrapping } from 'three' +import { Loader } from '../types/Loader' export interface MaterialCreatorOptions { /** diff --git a/src/loaders/NodeMaterialLoader.js b/src/loaders/NodeMaterialLoader.js deleted file mode 100644 index ab5e4ce1..00000000 --- a/src/loaders/NodeMaterialLoader.js +++ /dev/null @@ -1,191 +0,0 @@ -import { Loader, FileLoader } from 'three' -import * as Nodes from '../nodes/Nodes' - -class NodeMaterialLoader extends Loader { - constructor(manager, library = {}) { - super(manager) - - this.nodes = {} - this.materials = {} - this.passes = {} - this.names = {} - this.library = library - } - - load(url, onLoad, onProgress, onError) { - const scope = this - - const loader = new FileLoader(scope.manager) - loader.setPath(scope.path) - loader.load( - url, - function (text) { - onLoad(scope.parse(JSON.parse(text))) - }, - onProgress, - onError, - ) - - return this - } - - getObjectByName(uuid) { - return this.names[uuid] - } - - getObjectById(uuid) { - return this.library[uuid] || this.nodes[uuid] || this.materials[uuid] || this.passes[uuid] || this.names[uuid] - } - - getNode(uuid) { - const object = this.getObjectById(uuid) - - if (!object) { - console.warn('Node "' + uuid + '" not found.') - } - - return object - } - - resolve(json) { - switch (typeof json) { - case 'boolean': - case 'number': - return json - - case 'string': - if (/^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$/i.test(json) || this.library[json]) { - return this.getNode(json) - } - - return json - - default: - if (Array.isArray(json)) { - for (let i = 0; i < json.length; i++) { - json[i] = this.resolve(json[i]) - } - } else { - for (const prop in json) { - if (prop === 'uuid') continue - - json[prop] = this.resolve(json[prop]) - } - } - } - - return json - } - - declare(json) { - let uuid, node, object - - for (uuid in json.nodes) { - node = json.nodes[uuid] - - object = new Nodes[node.nodeType + 'Node']() - - if (node.name) { - object.name = node.name - - this.names[object.name] = object - } - - this.nodes[uuid] = object - } - - for (uuid in json.materials) { - node = json.materials[uuid] - - object = new Nodes[node.type]() - - if (node.name) { - object.name = node.name - - this.names[object.name] = object - } - - this.materials[uuid] = object - } - - for (uuid in json.passes) { - node = json.passes[uuid] - - object = new Nodes[node.type]() - - if (node.name) { - object.name = node.name - - this.names[object.name] = object - } - - this.passes[uuid] = object - } - - if (json.material) this.material = this.materials[json.material] - - if (json.pass) this.pass = this.passes[json.pass] - - return json - } - - parse(json) { - let uuid - - json = this.resolve(this.declare(json)) - - for (uuid in json.nodes) { - this.nodes[uuid].copy(json.nodes[uuid]) - } - - for (uuid in json.materials) { - this.materials[uuid].copy(json.materials[uuid]) - } - - for (uuid in json.passes) { - this.passes[uuid].copy(json.passes[uuid]) - } - - return this.material || this.pass || this - } -} - -class NodeMaterialLoaderUtils { - static replaceUUIDObject(object, uuid, value, recursive) { - recursive = recursive !== undefined ? recursive : true - - if (typeof uuid === 'object') uuid = uuid.uuid - - if (typeof object === 'object') { - const keys = Object.keys(object) - - for (let i = 0; i < keys.length; i++) { - const key = keys[i] - - if (recursive) { - object[key] = this.replaceUUIDObject(object[key], uuid, value) - } - - if (key === uuid) { - object[uuid] = object[key] - - delete object[key] - } - } - } - - return object === uuid ? value : object - } - - static replaceUUID(json, uuid, value) { - this.replaceUUIDObject(json, uuid, value, false) - this.replaceUUIDObject(json.nodes, uuid, value) - this.replaceUUIDObject(json.materials, uuid, value) - this.replaceUUIDObject(json.passes, uuid, value) - this.replaceUUIDObject(json.library, uuid, value, false) - - return json - } -} - -export { NodeMaterialLoader, NodeMaterialLoaderUtils } diff --git a/src/loaders/OBJLoader.d.ts b/src/loaders/OBJLoader.d.ts index 800e7d35..20c306b9 100644 --- a/src/loaders/OBJLoader.d.ts +++ b/src/loaders/OBJLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Group } from 'three' +import { LoadingManager, Group } from 'three' +import { Loader } from '../types/Loader' import { MaterialCreator } from './MTLLoader' export class OBJLoader extends Loader { diff --git a/src/loaders/OBJLoader.js b/src/loaders/OBJLoader.js index f5a5ffce..82ffde04 100644 --- a/src/loaders/OBJLoader.js +++ b/src/loaders/OBJLoader.js @@ -23,12 +23,12 @@ const _material_use_pattern = /^usemtl / // usemap map_name const _map_use_pattern = /^usemap / -const _vA = new Vector3() -const _vB = new Vector3() -const _vC = new Vector3() +const _vA = /* @__PURE__ */ new Vector3() +const _vB = /* @__PURE__ */ new Vector3() +const _vC = /* @__PURE__ */ new Vector3() -const _ab = new Vector3() -const _cb = new Vector3() +const _ab = /* @__PURE__ */ new Vector3() +const _cb = /* @__PURE__ */ new Vector3() function ParserState() { const state = { diff --git a/src/loaders/PCDLoader.d.ts b/src/loaders/PCDLoader.d.ts index f7aaae56..31312941 100644 --- a/src/loaders/PCDLoader.d.ts +++ b/src/loaders/PCDLoader.d.ts @@ -1,4 +1,5 @@ -import { Points, Loader, LoadingManager } from 'three' +import { Points, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class PCDLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/PDBLoader.d.ts b/src/loaders/PDBLoader.d.ts index d41e4a84..c68a47e6 100644 --- a/src/loaders/PDBLoader.d.ts +++ b/src/loaders/PDBLoader.d.ts @@ -1,4 +1,5 @@ -import { BufferGeometry, Loader, LoadingManager } from 'three' +import { BufferGeometry, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export interface PDB { geometryAtoms: BufferGeometry diff --git a/src/loaders/PLYLoader.d.ts b/src/loaders/PLYLoader.d.ts index 1150c37c..d21f73dd 100644 --- a/src/loaders/PLYLoader.d.ts +++ b/src/loaders/PLYLoader.d.ts @@ -1,4 +1,5 @@ -import { BufferGeometry, Loader, LoadingManager } from 'three' +import { BufferGeometry, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class PLYLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/PRWMLoader.d.ts b/src/loaders/PRWMLoader.d.ts index 6ae9e1ad..68a49416 100644 --- a/src/loaders/PRWMLoader.d.ts +++ b/src/loaders/PRWMLoader.d.ts @@ -1,4 +1,5 @@ -import { BufferGeometry, Loader, LoadingManager } from 'three' +import { BufferGeometry, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class PRWMLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/RGBMLoader.js b/src/loaders/RGBMLoader.js index c1e3406a..81c7ab30 100644 --- a/src/loaders/RGBMLoader.js +++ b/src/loaders/RGBMLoader.js @@ -1,1205 +1,1211 @@ import { DataTextureLoader, RGBAFormat, LinearFilter, CubeTexture, HalfFloatType, DataUtils } from 'three' -class RGBMLoader extends DataTextureLoader { - constructor(manager) { - super(manager) +let UPNG - this.type = HalfFloatType - this.maxRange = 7 // more information about this property at https://iwasbeingirony.blogspot.com/2010/06/difference-between-rgbm-and-rgbd.html - } +function init() { + if (UPNG) return UPNG + // from https://github.com/photopea/UPNG.js (MIT License) - setDataType(value) { - this.type = value - return this - } + UPNG = {} - setMaxRange(value) { - this.maxRange = value - return this - } - - loadCubemap(urls, onLoad, onProgress, onError) { - const texture = new CubeTexture() + UPNG.toRGBA8 = function (out) { + var w = out.width, + h = out.height + if (out.tabs.acTL == null) return [UPNG.toRGBA8.decodeImage(out.data, w, h, out).buffer] - let loaded = 0 + var frms = [] + if (out.frames[0].data == null) out.frames[0].data = out.data - const scope = this - - function loadTexture(i) { - scope.load( - urls[i], - function (image) { - texture.images[i] = image + var len = w * h * 4, + img = new Uint8Array(len), + empty = new Uint8Array(len), + prev = new Uint8Array(len) + for (var i = 0; i < out.frames.length; i++) { + var frm = out.frames[i] + var fx = frm.rect.x, + fy = frm.rect.y, + fw = frm.rect.width, + fh = frm.rect.height + var fdata = UPNG.toRGBA8.decodeImage(frm.data, fw, fh, out) - loaded++ + if (i != 0) for (var j = 0; j < len; j++) prev[j] = img[j] - if (loaded === 6) { - texture.needsUpdate = true + if (frm.blend == 0) UPNG._copyTile(fdata, fw, fh, img, w, h, fx, fy, 0) + else if (frm.blend == 1) UPNG._copyTile(fdata, fw, fh, img, w, h, fx, fy, 1) - if (onLoad) onLoad(texture) - } - }, - undefined, - onError, - ) - } + frms.push(img.buffer.slice(0)) - for (let i = 0; i < urls.length; ++i) { - loadTexture(i) + if (frm.dispose == 1) UPNG._copyTile(empty, fw, fh, img, w, h, fx, fy, 0) + else if (frm.dispose == 2) for (var j = 0; j < len; j++) img[j] = prev[j] } - texture.type = this.type - texture.format = RGBAFormat - texture.minFilter = LinearFilter - texture.generateMipmaps = false - - return texture + return frms } - parse(buffer) { - const img = UPNG.decode(buffer) - const rgba = UPNG.toRGBA8(img)[0] - - const data = new Uint8Array(rgba) - const size = img.width * img.height * 4 - - const output = this.type === HalfFloatType ? new Uint16Array(size) : new Float32Array(size) + UPNG.toRGBA8.decodeImage = function (data, w, h, out) { + var area = w * h, + bpp = UPNG.decode._getBPP(out) + var bpl = Math.ceil((w * bpp) / 8) // bytes per line - // decode RGBM + var bf = new Uint8Array(area * 4), + bf32 = new Uint32Array(bf.buffer) + var ctype = out.ctype, + depth = out.depth + var rs = UPNG._bin.readUshort - for (let i = 0; i < data.length; i += 4) { - const r = data[i + 0] / 255 - const g = data[i + 1] / 255 - const b = data[i + 2] / 255 - const a = data[i + 3] / 255 + if (ctype == 6) { + // RGB + alpha - if (this.type === HalfFloatType) { - output[i + 0] = DataUtils.toHalfFloat(Math.min(r * a * this.maxRange, 65504)) - output[i + 1] = DataUtils.toHalfFloat(Math.min(g * a * this.maxRange, 65504)) - output[i + 2] = DataUtils.toHalfFloat(Math.min(b * a * this.maxRange, 65504)) - output[i + 3] = DataUtils.toHalfFloat(1) - } else { - output[i + 0] = r * a * this.maxRange - output[i + 1] = g * a * this.maxRange - output[i + 2] = b * a * this.maxRange - output[i + 3] = 1 + var qarea = area << 2 + if (depth == 8) { + for (var i = 0; i < qarea; i += 4) { + bf[i] = data[i] + bf[i + 1] = data[i + 1] + bf[i + 2] = data[i + 2] + bf[i + 3] = data[i + 3] + } } - } - - return { - width: img.width, - height: img.height, - data: output, - format: RGBAFormat, - type: this.type, - flipY: true, - } - } -} -// from https://github.com/photopea/UPNG.js (MIT License) - -var UPNG = {} - -UPNG.toRGBA8 = function (out) { - var w = out.width, - h = out.height - if (out.tabs.acTL == null) return [UPNG.toRGBA8.decodeImage(out.data, w, h, out).buffer] - - var frms = [] - if (out.frames[0].data == null) out.frames[0].data = out.data - - var len = w * h * 4, - img = new Uint8Array(len), - empty = new Uint8Array(len), - prev = new Uint8Array(len) - for (var i = 0; i < out.frames.length; i++) { - var frm = out.frames[i] - var fx = frm.rect.x, - fy = frm.rect.y, - fw = frm.rect.width, - fh = frm.rect.height - var fdata = UPNG.toRGBA8.decodeImage(frm.data, fw, fh, out) - - if (i != 0) for (var j = 0; j < len; j++) prev[j] = img[j] - - if (frm.blend == 0) UPNG._copyTile(fdata, fw, fh, img, w, h, fx, fy, 0) - else if (frm.blend == 1) UPNG._copyTile(fdata, fw, fh, img, w, h, fx, fy, 1) - - frms.push(img.buffer.slice(0)) + if (depth == 16) { + for (var i = 0; i < qarea; i++) { + bf[i] = data[i << 1] + } + } + } else if (ctype == 2) { + // RGB + + var ts = out.tabs['tRNS'] + if (ts == null) { + if (depth == 8) { + for (var i = 0; i < area; i++) { + var ti = i * 3 + bf32[i] = (255 << 24) | (data[ti + 2] << 16) | (data[ti + 1] << 8) | data[ti] + } + } - if (frm.dispose == 1) UPNG._copyTile(empty, fw, fh, img, w, h, fx, fy, 0) - else if (frm.dispose == 2) for (var j = 0; j < len; j++) img[j] = prev[j] - } + if (depth == 16) { + for (var i = 0; i < area; i++) { + var ti = i * 6 + bf32[i] = (255 << 24) | (data[ti + 4] << 16) | (data[ti + 2] << 8) | data[ti] + } + } + } else { + var tr = ts[0], + tg = ts[1], + tb = ts[2] + if (depth == 8) { + for (var i = 0; i < area; i++) { + var qi = i << 2, + ti = i * 3 + bf32[i] = (255 << 24) | (data[ti + 2] << 16) | (data[ti + 1] << 8) | data[ti] + if (data[ti] == tr && data[ti + 1] == tg && data[ti + 2] == tb) bf[qi + 3] = 0 + } + } - return frms -} + if (depth == 16) { + for (var i = 0; i < area; i++) { + var qi = i << 2, + ti = i * 6 + bf32[i] = (255 << 24) | (data[ti + 4] << 16) | (data[ti + 2] << 8) | data[ti] + if (rs(data, ti) == tr && rs(data, ti + 2) == tg && rs(data, ti + 4) == tb) bf[qi + 3] = 0 + } + } + } + } else if (ctype == 3) { + // palette -UPNG.toRGBA8.decodeImage = function (data, w, h, out) { - var area = w * h, - bpp = UPNG.decode._getBPP(out) - var bpl = Math.ceil((w * bpp) / 8) // bytes per line - - var bf = new Uint8Array(area * 4), - bf32 = new Uint32Array(bf.buffer) - var ctype = out.ctype, - depth = out.depth - var rs = UPNG._bin.readUshort - - if (ctype == 6) { - // RGB + alpha - - var qarea = area << 2 - if (depth == 8) { - for (var i = 0; i < qarea; i += 4) { - bf[i] = data[i] - bf[i + 1] = data[i + 1] - bf[i + 2] = data[i + 2] - bf[i + 3] = data[i + 3] + var p = out.tabs['PLTE'], + ap = out.tabs['tRNS'], + tl = ap ? ap.length : 0 + //console.log(p, ap); + if (depth == 1) { + for (var y = 0; y < h; y++) { + var s0 = y * bpl, + t0 = y * w + for (var i = 0; i < w; i++) { + var qi = (t0 + i) << 2, + j = (data[s0 + (i >> 3)] >> (7 - ((i & 7) << 0))) & 1, + cj = 3 * j + bf[qi] = p[cj] + bf[qi + 1] = p[cj + 1] + bf[qi + 2] = p[cj + 2] + bf[qi + 3] = j < tl ? ap[j] : 255 + } + } } - } - if (depth == 16) { - for (var i = 0; i < qarea; i++) { - bf[i] = data[i << 1] + if (depth == 2) { + for (var y = 0; y < h; y++) { + var s0 = y * bpl, + t0 = y * w + for (var i = 0; i < w; i++) { + var qi = (t0 + i) << 2, + j = (data[s0 + (i >> 2)] >> (6 - ((i & 3) << 1))) & 3, + cj = 3 * j + bf[qi] = p[cj] + bf[qi + 1] = p[cj + 1] + bf[qi + 2] = p[cj + 2] + bf[qi + 3] = j < tl ? ap[j] : 255 + } + } } - } - } else if (ctype == 2) { - // RGB - var ts = out.tabs['tRNS'] - if (ts == null) { - if (depth == 8) { - for (var i = 0; i < area; i++) { - var ti = i * 3 - bf32[i] = (255 << 24) | (data[ti + 2] << 16) | (data[ti + 1] << 8) | data[ti] + if (depth == 4) { + for (var y = 0; y < h; y++) { + var s0 = y * bpl, + t0 = y * w + for (var i = 0; i < w; i++) { + var qi = (t0 + i) << 2, + j = (data[s0 + (i >> 1)] >> (4 - ((i & 1) << 2))) & 15, + cj = 3 * j + bf[qi] = p[cj] + bf[qi + 1] = p[cj + 1] + bf[qi + 2] = p[cj + 2] + bf[qi + 3] = j < tl ? ap[j] : 255 + } } } - if (depth == 16) { + if (depth == 8) { for (var i = 0; i < area; i++) { - var ti = i * 6 - bf32[i] = (255 << 24) | (data[ti + 4] << 16) | (data[ti + 2] << 8) | data[ti] + var qi = i << 2, + j = data[i], + cj = 3 * j + bf[qi] = p[cj] + bf[qi + 1] = p[cj + 1] + bf[qi + 2] = p[cj + 2] + bf[qi + 3] = j < tl ? ap[j] : 255 } } - } else { - var tr = ts[0], - tg = ts[1], - tb = ts[2] + } else if (ctype == 4) { + // gray + alpha + if (depth == 8) { for (var i = 0; i < area; i++) { var qi = i << 2, - ti = i * 3 - bf32[i] = (255 << 24) | (data[ti + 2] << 16) | (data[ti + 1] << 8) | data[ti] - if (data[ti] == tr && data[ti + 1] == tg && data[ti + 2] == tb) bf[qi + 3] = 0 + di = i << 1, + gr = data[di] + bf[qi] = gr + bf[qi + 1] = gr + bf[qi + 2] = gr + bf[qi + 3] = data[di + 1] } } if (depth == 16) { for (var i = 0; i < area; i++) { var qi = i << 2, - ti = i * 6 - bf32[i] = (255 << 24) | (data[ti + 4] << 16) | (data[ti + 2] << 8) | data[ti] - if (rs(data, ti) == tr && rs(data, ti + 2) == tg && rs(data, ti + 4) == tb) bf[qi + 3] = 0 + di = i << 2, + gr = data[di] + bf[qi] = gr + bf[qi + 1] = gr + bf[qi + 2] = gr + bf[qi + 3] = data[di + 2] } } - } - } else if (ctype == 3) { - // palette - - var p = out.tabs['PLTE'], - ap = out.tabs['tRNS'], - tl = ap ? ap.length : 0 - //console.log(p, ap); - if (depth == 1) { + } else if (ctype == 0) { + // gray + + var tr = out.tabs['tRNS'] ? out.tabs['tRNS'] : -1 for (var y = 0; y < h; y++) { - var s0 = y * bpl, - t0 = y * w - for (var i = 0; i < w; i++) { - var qi = (t0 + i) << 2, - j = (data[s0 + (i >> 3)] >> (7 - ((i & 7) << 0))) & 1, - cj = 3 * j - bf[qi] = p[cj] - bf[qi + 1] = p[cj + 1] - bf[qi + 2] = p[cj + 2] - bf[qi + 3] = j < tl ? ap[j] : 255 + var off = y * bpl, + to = y * w + if (depth == 1) { + for (var x = 0; x < w; x++) { + var gr = 255 * ((data[off + (x >>> 3)] >>> (7 - (x & 7))) & 1), + al = gr == tr * 255 ? 0 : 255 + bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + } + } else if (depth == 2) { + for (var x = 0; x < w; x++) { + var gr = 85 * ((data[off + (x >>> 2)] >>> (6 - ((x & 3) << 1))) & 3), + al = gr == tr * 85 ? 0 : 255 + bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + } + } else if (depth == 4) { + for (var x = 0; x < w; x++) { + var gr = 17 * ((data[off + (x >>> 1)] >>> (4 - ((x & 1) << 2))) & 15), + al = gr == tr * 17 ? 0 : 255 + bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + } + } else if (depth == 8) { + for (var x = 0; x < w; x++) { + var gr = data[off + x], + al = gr == tr ? 0 : 255 + bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + } + } else if (depth == 16) { + for (var x = 0; x < w; x++) { + var gr = data[off + (x << 1)], + al = rs(data, off + (x << 1)) == tr ? 0 : 255 + bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + } } } } - if (depth == 2) { - for (var y = 0; y < h; y++) { - var s0 = y * bpl, - t0 = y * w - for (var i = 0; i < w; i++) { - var qi = (t0 + i) << 2, - j = (data[s0 + (i >> 2)] >> (6 - ((i & 3) << 1))) & 3, - cj = 3 * j - bf[qi] = p[cj] - bf[qi + 1] = p[cj + 1] - bf[qi + 2] = p[cj + 2] - bf[qi + 3] = j < tl ? ap[j] : 255 + //console.log(Date.now()-time); + return bf + } + + UPNG.decode = function (buff) { + var data = new Uint8Array(buff), + offset = 8, + bin = UPNG._bin, + rUs = bin.readUshort, + rUi = bin.readUint + var out = { tabs: {}, frames: [] } + var dd = new Uint8Array(data.length), + doff = 0 // put all IDAT data into it + var fd, + foff = 0 // frames + var text, keyw, bfr + + var mgck = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] + for (var i = 0; i < 8; i++) if (data[i] != mgck[i]) throw new Error('The input is not a PNG file!') + + while (offset < data.length) { + var len = bin.readUint(data, offset) + offset += 4 + var type = bin.readASCII(data, offset, 4) + offset += 4 + //console.log(type,len); + + if (type == 'IHDR') { + UPNG.decode._IHDR(data, offset, out) + } else if (type == 'CgBI') { + out.tabs[type] = data.slice(offset, offset + 4) + } else if (type == 'IDAT') { + for (var i = 0; i < len; i++) dd[doff + i] = data[offset + i] + doff += len + } else if (type == 'acTL') { + out.tabs[type] = { num_frames: rUi(data, offset), num_plays: rUi(data, offset + 4) } + fd = new Uint8Array(data.length) + } else if (type == 'fcTL') { + if (foff != 0) { + var fr = out.frames[out.frames.length - 1] + fr.data = UPNG.decode._decompress(out, fd.slice(0, foff), fr.rect.width, fr.rect.height) + foff = 0 } - } - } - if (depth == 4) { - for (var y = 0; y < h; y++) { - var s0 = y * bpl, - t0 = y * w - for (var i = 0; i < w; i++) { - var qi = (t0 + i) << 2, - j = (data[s0 + (i >> 1)] >> (4 - ((i & 1) << 2))) & 15, - cj = 3 * j - bf[qi] = p[cj] - bf[qi + 1] = p[cj + 1] - bf[qi + 2] = p[cj + 2] - bf[qi + 3] = j < tl ? ap[j] : 255 + var rct = { + x: rUi(data, offset + 12), + y: rUi(data, offset + 16), + width: rUi(data, offset + 4), + height: rUi(data, offset + 8), + } + var del = rUs(data, offset + 22) + del = rUs(data, offset + 20) / (del == 0 ? 100 : del) + var frm = { rect: rct, delay: Math.round(del * 1000), dispose: data[offset + 24], blend: data[offset + 25] } + //console.log(frm); + out.frames.push(frm) + } else if (type == 'fdAT') { + for (var i = 0; i < len - 4; i++) fd[foff + i] = data[offset + i + 4] + foff += len - 4 + } else if (type == 'pHYs') { + out.tabs[type] = [bin.readUint(data, offset), bin.readUint(data, offset + 4), data[offset + 8]] + } else if (type == 'cHRM') { + out.tabs[type] = [] + for (var i = 0; i < 8; i++) out.tabs[type].push(bin.readUint(data, offset + i * 4)) + } else if (type == 'tEXt' || type == 'zTXt') { + if (out.tabs[type] == null) out.tabs[type] = {} + var nz = bin.nextZero(data, offset) + keyw = bin.readASCII(data, offset, nz - offset) + var tl = offset + len - nz - 1 + if (type == 'tEXt') { + text = bin.readASCII(data, nz + 1, tl) + } else { + bfr = UPNG.decode._inflate(data.slice(nz + 2, nz + 2 + tl)) + text = bin.readUTF8(bfr, 0, bfr.length) } - } - } - if (depth == 8) { - for (var i = 0; i < area; i++) { - var qi = i << 2, - j = data[i], - cj = 3 * j - bf[qi] = p[cj] - bf[qi + 1] = p[cj + 1] - bf[qi + 2] = p[cj + 2] - bf[qi + 3] = j < tl ? ap[j] : 255 - } - } - } else if (ctype == 4) { - // gray + alpha - - if (depth == 8) { - for (var i = 0; i < area; i++) { - var qi = i << 2, - di = i << 1, - gr = data[di] - bf[qi] = gr - bf[qi + 1] = gr - bf[qi + 2] = gr - bf[qi + 3] = data[di + 1] + out.tabs[type][keyw] = text + } else if (type == 'iTXt') { + if (out.tabs[type] == null) out.tabs[type] = {} + var nz = 0, + off = offset + nz = bin.nextZero(data, off) + keyw = bin.readASCII(data, off, nz - off) + off = nz + 1 + var cflag = data[off] + off += 2 + nz = bin.nextZero(data, off) + bin.readASCII(data, off, nz - off) + off = nz + 1 + nz = bin.nextZero(data, off) + bin.readUTF8(data, off, nz - off) + off = nz + 1 + var tl = len - (off - offset) + if (cflag == 0) { + text = bin.readUTF8(data, off, tl) + } else { + bfr = UPNG.decode._inflate(data.slice(off, off + tl)) + text = bin.readUTF8(bfr, 0, bfr.length) + } + + out.tabs[type][keyw] = text + } else if (type == 'PLTE') { + out.tabs[type] = bin.readBytes(data, offset, len) + } else if (type == 'hIST') { + var pl = out.tabs['PLTE'].length / 3 + out.tabs[type] = [] + for (var i = 0; i < pl; i++) out.tabs[type].push(rUs(data, offset + i * 2)) + } else if (type == 'tRNS') { + if (out.ctype == 3) out.tabs[type] = bin.readBytes(data, offset, len) + else if (out.ctype == 0) out.tabs[type] = rUs(data, offset) + else if (out.ctype == 2) out.tabs[type] = [rUs(data, offset), rUs(data, offset + 2), rUs(data, offset + 4)] + //else console.log("tRNS for unsupported color type",out.ctype, len); + } else if (type == 'gAMA') { + out.tabs[type] = bin.readUint(data, offset) / 100000 + } else if (type == 'sRGB') { + out.tabs[type] = data[offset] + } else if (type == 'bKGD') { + if (out.ctype == 0 || out.ctype == 4) { + out.tabs[type] = [rUs(data, offset)] + } else if (out.ctype == 2 || out.ctype == 6) { + out.tabs[type] = [rUs(data, offset), rUs(data, offset + 2), rUs(data, offset + 4)] + } else if (out.ctype == 3) { + out.tabs[type] = data[offset] + } + } else if (type == 'IEND') { + break } + + //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } + offset += len + bin.readUint(data, offset) + offset += 4 } - if (depth == 16) { - for (var i = 0; i < area; i++) { - var qi = i << 2, - di = i << 2, - gr = data[di] - bf[qi] = gr - bf[qi + 1] = gr - bf[qi + 2] = gr - bf[qi + 3] = data[di + 2] - } + if (foff != 0) { + var fr = out.frames[out.frames.length - 1] + fr.data = UPNG.decode._decompress(out, fd.slice(0, foff), fr.rect.width, fr.rect.height) } - } else if (ctype == 0) { - // gray - var tr = out.tabs['tRNS'] ? out.tabs['tRNS'] : -1 - for (var y = 0; y < h; y++) { - var off = y * bpl, - to = y * w - if (depth == 1) { - for (var x = 0; x < w; x++) { - var gr = 255 * ((data[off + (x >>> 3)] >>> (7 - (x & 7))) & 1), - al = gr == tr * 255 ? 0 : 255 - bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr - } - } else if (depth == 2) { - for (var x = 0; x < w; x++) { - var gr = 85 * ((data[off + (x >>> 2)] >>> (6 - ((x & 3) << 1))) & 3), - al = gr == tr * 85 ? 0 : 255 - bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + out.data = UPNG.decode._decompress(out, dd, out.width, out.height) + + delete out.compress + delete out.interlace + delete out.filter + return out + } + + UPNG.decode._decompress = function (out, dd, w, h) { + var bpp = UPNG.decode._getBPP(out), + bpl = Math.ceil((w * bpp) / 8), + buff = new Uint8Array((bpl + 1 + out.interlace) * h) + if (out.tabs['CgBI']) dd = UPNG.inflateRaw(dd, buff) + else dd = UPNG.decode._inflate(dd, buff) + + if (out.interlace == 0) dd = UPNG.decode._filterZero(dd, out, 0, w, h) + else if (out.interlace == 1) dd = UPNG.decode._readInterlace(dd, out) + + return dd + } + + UPNG.decode._inflate = function (data, buff) { + var out = UPNG['inflateRaw'](new Uint8Array(data.buffer, 2, data.length - 6), buff) + return out + } + + UPNG.inflateRaw = (function () { + var H = {} + H.H = {} + H.H.N = function (N, W) { + var R = Uint8Array, + i = 0, + m = 0, + J = 0, + h = 0, + Q = 0, + X = 0, + u = 0, + w = 0, + d = 0, + v, + C + if (N[0] == 3 && N[1] == 0) return W ? W : new R(0) + var V = H.H, + n = V.b, + A = V.e, + l = V.R, + M = V.n, + I = V.A, + e = V.Z, + b = V.m, + Z = W == null + if (Z) W = new R((N.length >>> 2) << 5) + while (i == 0) { + i = n(N, d, 1) + m = n(N, d + 1, 2) + d += 3 + if (m == 0) { + if ((d & 7) != 0) d += 8 - (d & 7) + var D = (d >>> 3) + 4, + q = N[D - 4] | (N[D - 3] << 8) + if (Z) W = H.H.W(W, w + q) + W.set(new R(N.buffer, N.byteOffset + D, q), w) + d = (D + q) << 3 + w += q + continue } - } else if (depth == 4) { - for (var x = 0; x < w; x++) { - var gr = 17 * ((data[off + (x >>> 1)] >>> (4 - ((x & 1) << 2))) & 15), - al = gr == tr * 17 ? 0 : 255 - bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + + if (Z) W = H.H.W(W, w + (1 << 17)) + if (m == 1) { + v = b.J + C = b.h + X = (1 << 9) - 1 + u = (1 << 5) - 1 } - } else if (depth == 8) { - for (var x = 0; x < w; x++) { - var gr = data[off + x], - al = gr == tr ? 0 : 255 - bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + + if (m == 2) { + J = A(N, d, 5) + 257 + h = A(N, d + 5, 5) + 1 + Q = A(N, d + 10, 4) + 4 + d += 14 + var j = 1 + for (var c = 0; c < 38; c += 2) { + b.Q[c] = 0 + b.Q[c + 1] = 0 + } + + for (var c = 0; c < Q; c++) { + var K = A(N, d + c * 3, 3) + b.Q[(b.X[c] << 1) + 1] = K + if (K > j) j = K + } + + d += 3 * Q + M(b.Q, j) + I(b.Q, j, b.u) + v = b.w + C = b.d + d = l(b.u, (1 << j) - 1, J + h, N, d, b.v) + var r = V.V(b.v, 0, J, b.C) + X = (1 << r) - 1 + var S = V.V(b.v, J, h, b.D) + u = (1 << S) - 1 + M(b.C, r) + I(b.C, r, v) + M(b.D, S) + I(b.D, S, C) } - } else if (depth == 16) { - for (var x = 0; x < w; x++) { - var gr = data[off + (x << 1)], - al = rs(data, off + (x << 1)) == tr ? 0 : 255 - bf32[to + x] = (al << 24) | (gr << 16) | (gr << 8) | gr + + while (!0) { + var T = v[e(N, d) & X] + d += T & 15 + var p = T >>> 4 + if (p >>> 8 == 0) { + W[w++] = p + } else if (p == 256) { + break + } else { + var z = w + p - 254 + if (p > 264) { + var _ = b.q[p - 257] + z = w + (_ >>> 3) + A(N, d, _ & 7) + d += _ & 7 + } + + var $ = C[e(N, d) & u] + d += $ & 15 + var s = $ >>> 4, + Y = b.c[s], + a = (Y >>> 4) + n(N, d, Y & 15) + d += Y & 15 + while (w < z) { + W[w] = W[w++ - a] + W[w] = W[w++ - a] + W[w] = W[w++ - a] + W[w] = W[w++ - a] + } + + w = z + } } } + + return W.length == w ? W : W.slice(0, w) } - } - //console.log(Date.now()-time); - return bf -} + H.H.W = function (N, W) { + var R = N.length + if (W <= R) return N + var V = new Uint8Array(R << 1) + V.set(N, 0) + return V + } -UPNG.decode = function (buff) { - var data = new Uint8Array(buff), - offset = 8, - bin = UPNG._bin, - rUs = bin.readUshort, - rUi = bin.readUint - var out = { tabs: {}, frames: [] } - var dd = new Uint8Array(data.length), - doff = 0 // put all IDAT data into it - var fd, - foff = 0 // frames - var text, keyw, bfr - - var mgck = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a] - for (var i = 0; i < 8; i++) if (data[i] != mgck[i]) throw new Error('The input is not a PNG file!') - - while (offset < data.length) { - var len = bin.readUint(data, offset) - offset += 4 - var type = bin.readASCII(data, offset, 4) - offset += 4 - //console.log(type,len); - - if (type == 'IHDR') { - UPNG.decode._IHDR(data, offset, out) - } else if (type == 'CgBI') { - out.tabs[type] = data.slice(offset, offset + 4) - } else if (type == 'IDAT') { - for (var i = 0; i < len; i++) dd[doff + i] = data[offset + i] - doff += len - } else if (type == 'acTL') { - out.tabs[type] = { num_frames: rUi(data, offset), num_plays: rUi(data, offset + 4) } - fd = new Uint8Array(data.length) - } else if (type == 'fcTL') { - if (foff != 0) { - var fr = out.frames[out.frames.length - 1] - fr.data = UPNG.decode._decompress(out, fd.slice(0, foff), fr.rect.width, fr.rect.height) - foff = 0 + H.H.R = function (N, W, R, V, n, A) { + var l = H.H.e, + M = H.H.Z, + I = 0 + while (I < R) { + var e = N[M(V, n) & W] + n += e & 15 + var b = e >>> 4 + if (b <= 15) { + A[I] = b + I++ + } else { + var Z = 0, + m = 0 + if (b == 16) { + m = 3 + l(V, n, 2) + n += 2 + Z = A[I - 1] + } else if (b == 17) { + m = 3 + l(V, n, 3) + n += 3 + } else if (b == 18) { + m = 11 + l(V, n, 7) + n += 7 + } + + var J = I + m + while (I < J) { + A[I] = Z + I++ + } + } } - var rct = { - x: rUi(data, offset + 12), - y: rUi(data, offset + 16), - width: rUi(data, offset + 4), - height: rUi(data, offset + 8), + return n + } + + H.H.V = function (N, W, R, V) { + var n = 0, + A = 0, + l = V.length >>> 1 + while (A < R) { + var M = N[A + W] + V[A << 1] = 0 + V[(A << 1) + 1] = M + if (M > n) n = M + A++ } - var del = rUs(data, offset + 22) - del = rUs(data, offset + 20) / (del == 0 ? 100 : del) - var frm = { rect: rct, delay: Math.round(del * 1000), dispose: data[offset + 24], blend: data[offset + 25] } - //console.log(frm); - out.frames.push(frm) - } else if (type == 'fdAT') { - for (var i = 0; i < len - 4; i++) fd[foff + i] = data[offset + i + 4] - foff += len - 4 - } else if (type == 'pHYs') { - out.tabs[type] = [bin.readUint(data, offset), bin.readUint(data, offset + 4), data[offset + 8]] - } else if (type == 'cHRM') { - out.tabs[type] = [] - for (var i = 0; i < 8; i++) out.tabs[type].push(bin.readUint(data, offset + i * 4)) - } else if (type == 'tEXt' || type == 'zTXt') { - if (out.tabs[type] == null) out.tabs[type] = {} - var nz = bin.nextZero(data, offset) - keyw = bin.readASCII(data, offset, nz - offset) - var tl = offset + len - nz - 1 - if (type == 'tEXt') { - text = bin.readASCII(data, nz + 1, tl) - } else { - bfr = UPNG.decode._inflate(data.slice(nz + 2, nz + 2 + tl)) - text = bin.readUTF8(bfr, 0, bfr.length) + + while (A < l) { + V[A << 1] = 0 + V[(A << 1) + 1] = 0 + A++ } - out.tabs[type][keyw] = text - } else if (type == 'iTXt') { - if (out.tabs[type] == null) out.tabs[type] = {} - var nz = 0, - off = offset - nz = bin.nextZero(data, off) - keyw = bin.readASCII(data, off, nz - off) - off = nz + 1 - var cflag = data[off] - off += 2 - nz = bin.nextZero(data, off) - bin.readASCII(data, off, nz - off) - off = nz + 1 - nz = bin.nextZero(data, off) - bin.readUTF8(data, off, nz - off) - off = nz + 1 - var tl = len - (off - offset) - if (cflag == 0) { - text = bin.readUTF8(data, off, tl) - } else { - bfr = UPNG.decode._inflate(data.slice(off, off + tl)) - text = bin.readUTF8(bfr, 0, bfr.length) + return n + } + + H.H.n = function (N, W) { + var R = H.H.m, + V = N.length, + n, + A, + l, + M, + I, + e = R.j + for (var M = 0; M <= W; M++) e[M] = 0 + for (M = 1; M < V; M += 2) e[N[M]]++ + var b = R.K + n = 0 + e[0] = 0 + for (A = 1; A <= W; A++) { + n = (n + e[A - 1]) << 1 + b[A] = n } - out.tabs[type][keyw] = text - } else if (type == 'PLTE') { - out.tabs[type] = bin.readBytes(data, offset, len) - } else if (type == 'hIST') { - var pl = out.tabs['PLTE'].length / 3 - out.tabs[type] = [] - for (var i = 0; i < pl; i++) out.tabs[type].push(rUs(data, offset + i * 2)) - } else if (type == 'tRNS') { - if (out.ctype == 3) out.tabs[type] = bin.readBytes(data, offset, len) - else if (out.ctype == 0) out.tabs[type] = rUs(data, offset) - else if (out.ctype == 2) out.tabs[type] = [rUs(data, offset), rUs(data, offset + 2), rUs(data, offset + 4)] - //else console.log("tRNS for unsupported color type",out.ctype, len); - } else if (type == 'gAMA') { - out.tabs[type] = bin.readUint(data, offset) / 100000 - } else if (type == 'sRGB') { - out.tabs[type] = data[offset] - } else if (type == 'bKGD') { - if (out.ctype == 0 || out.ctype == 4) { - out.tabs[type] = [rUs(data, offset)] - } else if (out.ctype == 2 || out.ctype == 6) { - out.tabs[type] = [rUs(data, offset), rUs(data, offset + 2), rUs(data, offset + 4)] - } else if (out.ctype == 3) { - out.tabs[type] = data[offset] + for (l = 0; l < V; l += 2) { + I = N[l + 1] + if (I != 0) { + N[l] = b[I] + b[I]++ + } } - } else if (type == 'IEND') { - break } - //else { console.log("unknown chunk type", type, len); out.tabs[type]=data.slice(offset,offset+len); } - offset += len - bin.readUint(data, offset) - offset += 4 - } + H.H.A = function (N, W, R) { + var V = N.length, + n = H.H.m, + A = n.r + for (var l = 0; l < V; l += 2) { + if (N[l + 1] != 0) { + var M = l >> 1, + I = N[l + 1], + e = (M << 4) | I, + b = W - I, + Z = N[l] << b, + m = Z + (1 << b) + while (Z != m) { + var J = A[Z] >>> (15 - W) + R[J] = e + Z++ + } + } + } + } - if (foff != 0) { - var fr = out.frames[out.frames.length - 1] - fr.data = UPNG.decode._decompress(out, fd.slice(0, foff), fr.rect.width, fr.rect.height) - } + H.H.l = function (N, W) { + var R = H.H.m.r, + V = 15 - W + for (var n = 0; n < N.length; n += 2) { + var A = N[n] << (W - N[n + 1]) + N[n] = R[A] >>> V + } + } - out.data = UPNG.decode._decompress(out, dd, out.width, out.height) + H.H.M = function (N, W, R) { + R = R << (W & 7) + var V = W >>> 3 + N[V] |= R + N[V + 1] |= R >>> 8 + } - delete out.compress - delete out.interlace - delete out.filter - return out -} + H.H.I = function (N, W, R) { + R = R << (W & 7) + var V = W >>> 3 + N[V] |= R + N[V + 1] |= R >>> 8 + N[V + 2] |= R >>> 16 + } -UPNG.decode._decompress = function (out, dd, w, h) { - var bpp = UPNG.decode._getBPP(out), - bpl = Math.ceil((w * bpp) / 8), - buff = new Uint8Array((bpl + 1 + out.interlace) * h) - if (out.tabs['CgBI']) dd = UPNG.inflateRaw(dd, buff) - else dd = UPNG.decode._inflate(dd, buff) + H.H.e = function (N, W, R) { + return ((N[W >>> 3] | (N[(W >>> 3) + 1] << 8)) >>> (W & 7)) & ((1 << R) - 1) + } - if (out.interlace == 0) dd = UPNG.decode._filterZero(dd, out, 0, w, h) - else if (out.interlace == 1) dd = UPNG.decode._readInterlace(dd, out) + H.H.b = function (N, W, R) { + return ((N[W >>> 3] | (N[(W >>> 3) + 1] << 8) | (N[(W >>> 3) + 2] << 16)) >>> (W & 7)) & ((1 << R) - 1) + } - return dd -} + H.H.Z = function (N, W) { + return (N[W >>> 3] | (N[(W >>> 3) + 1] << 8) | (N[(W >>> 3) + 2] << 16)) >>> (W & 7) + } -UPNG.decode._inflate = function (data, buff) { - var out = UPNG['inflateRaw'](new Uint8Array(data.buffer, 2, data.length - 6), buff) - return out -} + H.H.i = function (N, W) { + return (N[W >>> 3] | (N[(W >>> 3) + 1] << 8) | (N[(W >>> 3) + 2] << 16) | (N[(W >>> 3) + 3] << 24)) >>> (W & 7) + } -UPNG.inflateRaw = (function () { - var H = {} - H.H = {} - H.H.N = function (N, W) { - var R = Uint8Array, - i = 0, - m = 0, - J = 0, - h = 0, - Q = 0, - X = 0, - u = 0, - w = 0, - d = 0, - v, - C - if (N[0] == 3 && N[1] == 0) return W ? W : new R(0) - var V = H.H, - n = V.b, - A = V.e, - l = V.R, - M = V.n, - I = V.A, - e = V.Z, - b = V.m, - Z = W == null - if (Z) W = new R((N.length >>> 2) << 5) - while (i == 0) { - i = n(N, d, 1) - m = n(N, d + 1, 2) - d += 3 - if (m == 0) { - if ((d & 7) != 0) d += 8 - (d & 7) - var D = (d >>> 3) + 4, - q = N[D - 4] | (N[D - 3] << 8) - if (Z) W = H.H.W(W, w + q) - W.set(new R(N.buffer, N.byteOffset + D, q), w) - d = (D + q) << 3 - w += q - continue + H.H.m = (function () { + var N = Uint16Array, + W = Uint32Array + return { + K: new N(16), + j: new N(16), + X: [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], + S: [ + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 13, + 15, + 17, + 19, + 23, + 27, + 31, + 35, + 43, + 51, + 59, + 67, + 83, + 99, + 115, + 131, + 163, + 195, + 227, + 258, + 999, + 999, + 999, + ], + T: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0], + q: new N(32), + p: [ + 1, + 2, + 3, + 4, + 5, + 7, + 9, + 13, + 17, + 25, + 33, + 49, + 65, + 97, + 129, + 193, + 257, + 385, + 513, + 769, + 1025, + 1537, + 2049, + 3073, + 4097, + 6145, + 8193, + 12289, + 16385, + 24577, + 65535, + 65535, + ], + z: [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0], + c: new W(32), + J: new N(512), + _: [], + h: new N(32), + $: [], + w: new N(32768), + C: [], + v: [], + d: new N(32768), + D: [], + u: new N(512), + Q: [], + r: new N(1 << 15), + s: new W(286), + Y: new W(30), + a: new W(19), + t: new W(15e3), + k: new N(1 << 16), + g: new N(1 << 15), + } + })() + ;(function () { + var N = H.H.m, + W = 1 << 15 + for (var R = 0; R < W; R++) { + var V = R + V = ((V & 2863311530) >>> 1) | ((V & 1431655765) << 1) + V = ((V & 3435973836) >>> 2) | ((V & 858993459) << 2) + V = ((V & 4042322160) >>> 4) | ((V & 252645135) << 4) + V = ((V & 4278255360) >>> 8) | ((V & 16711935) << 8) + N.r[R] = ((V >>> 16) | (V << 16)) >>> 17 } - if (Z) W = H.H.W(W, w + (1 << 17)) - if (m == 1) { - v = b.J - C = b.h - X = (1 << 9) - 1 - u = (1 << 5) - 1 + function n(A, l, M) { + while (l-- != 0) A.push(0, M) } - if (m == 2) { - J = A(N, d, 5) + 257 - h = A(N, d + 5, 5) + 1 - Q = A(N, d + 10, 4) + 4 - d += 14 - var j = 1 - for (var c = 0; c < 38; c += 2) { - b.Q[c] = 0 - b.Q[c + 1] = 0 - } + for (var R = 0; R < 32; R++) { + N.q[R] = (N.S[R] << 3) | N.T[R] + N.c[R] = (N.p[R] << 4) | N.z[R] + } - for (var c = 0; c < Q; c++) { - var K = A(N, d + c * 3, 3) - b.Q[(b.X[c] << 1) + 1] = K - if (K > j) j = K - } + n(N._, 144, 8) + n(N._, 255 - 143, 9) + n(N._, 279 - 255, 7) + n(N._, 287 - 279, 8) + H.H.n(N._, 9) + H.H.A(N._, 9, N.J) + H.H.l(N._, 9) + n(N.$, 32, 5) + H.H.n(N.$, 5) + H.H.A(N.$, 5, N.h) + H.H.l(N.$, 5) + n(N.Q, 19, 0) + n(N.C, 286, 0) + n(N.D, 30, 0) + n(N.v, 320, 0) + })() + + return H.H.N + })() - d += 3 * Q - M(b.Q, j) - I(b.Q, j, b.u) - v = b.w - C = b.d - d = l(b.u, (1 << j) - 1, J + h, N, d, b.v) - var r = V.V(b.v, 0, J, b.C) - X = (1 << r) - 1 - var S = V.V(b.v, J, h, b.D) - u = (1 << S) - 1 - M(b.C, r) - I(b.C, r, v) - M(b.D, S) - I(b.D, S, C) + UPNG.decode._readInterlace = function (data, out) { + var w = out.width, + h = out.height + var bpp = UPNG.decode._getBPP(out), + cbpp = bpp >> 3, + bpl = Math.ceil((w * bpp) / 8) + var img = new Uint8Array(h * bpl) + var di = 0 + + var starting_row = [0, 0, 4, 0, 2, 0, 1] + var starting_col = [0, 4, 0, 2, 0, 1, 0] + var row_increment = [8, 8, 8, 4, 4, 2, 2] + var col_increment = [8, 8, 4, 4, 2, 2, 1] + + var pass = 0 + while (pass < 7) { + var ri = row_increment[pass], + ci = col_increment[pass] + var sw = 0, + sh = 0 + var cr = starting_row[pass] + while (cr < h) { + cr += ri + sh++ } - while (!0) { - var T = v[e(N, d) & X] - d += T & 15 - var p = T >>> 4 - if (p >>> 8 == 0) { - W[w++] = p - } else if (p == 256) { - break - } else { - var z = w + p - 254 - if (p > 264) { - var _ = b.q[p - 257] - z = w + (_ >>> 3) + A(N, d, _ & 7) - d += _ & 7 + var cc = starting_col[pass] + while (cc < w) { + cc += ci + sw++ + } + + var bpll = Math.ceil((sw * bpp) / 8) + UPNG.decode._filterZero(data, out, di, sw, sh) + + var y = 0, + row = starting_row[pass] + var val + + while (row < h) { + var col = starting_col[pass] + var cdi = (di + y * bpll) << 3 + + while (col < w) { + if (bpp == 1) { + val = data[cdi >> 3] + val = (val >> (7 - (cdi & 7))) & 1 + img[row * bpl + (col >> 3)] |= val << (7 - ((col & 7) << 0)) + } + + if (bpp == 2) { + val = data[cdi >> 3] + val = (val >> (6 - (cdi & 7))) & 3 + img[row * bpl + (col >> 2)] |= val << (6 - ((col & 3) << 1)) + } + + if (bpp == 4) { + val = data[cdi >> 3] + val = (val >> (4 - (cdi & 7))) & 15 + img[row * bpl + (col >> 1)] |= val << (4 - ((col & 1) << 2)) } - var $ = C[e(N, d) & u] - d += $ & 15 - var s = $ >>> 4, - Y = b.c[s], - a = (Y >>> 4) + n(N, d, Y & 15) - d += Y & 15 - while (w < z) { - W[w] = W[w++ - a] - W[w] = W[w++ - a] - W[w] = W[w++ - a] - W[w] = W[w++ - a] + if (bpp >= 8) { + var ii = row * bpl + col * cbpp + for (var j = 0; j < cbpp; j++) img[ii + j] = data[(cdi >> 3) + j] } - w = z + cdi += bpp + col += ci } + + y++ + row += ri } + + if (sw * sh != 0) di += sh * (1 + bpll) + pass = pass + 1 } - return W.length == w ? W : W.slice(0, w) + return img } - H.H.W = function (N, W) { - var R = N.length - if (W <= R) return N - var V = new Uint8Array(R << 1) - V.set(N, 0) - return V + UPNG.decode._getBPP = function (out) { + var noc = [1, null, 3, 1, 2, null, 4][out.ctype] + return noc * out.depth } - H.H.R = function (N, W, R, V, n, A) { - var l = H.H.e, - M = H.H.Z, - I = 0 - while (I < R) { - var e = N[M(V, n) & W] - n += e & 15 - var b = e >>> 4 - if (b <= 15) { - A[I] = b - I++ - } else { - var Z = 0, - m = 0 - if (b == 16) { - m = 3 + l(V, n, 2) - n += 2 - Z = A[I - 1] - } else if (b == 17) { - m = 3 + l(V, n, 3) - n += 3 - } else if (b == 18) { - m = 11 + l(V, n, 7) - n += 7 - } + UPNG.decode._filterZero = function (data, out, off, w, h) { + var bpp = UPNG.decode._getBPP(out), + bpl = Math.ceil((w * bpp) / 8), + paeth = UPNG.decode._paeth + bpp = Math.ceil(bpp / 8) - var J = I + m - while (I < J) { - A[I] = Z - I++ + var i, + di, + type = data[off], + x = 0 + + if (type > 1) data[off] = [0, 0, 1][type - 2] + if (type == 3) for (x = bpp; x < bpl; x++) data[x + 1] = (data[x + 1] + (data[x + 1 - bpp] >>> 1)) & 255 + + for (var y = 0; y < h; y++) { + i = off + y * bpl + di = i + y + 1 + type = data[di - 1] + x = 0 + + if (type == 0) { + for (; x < bpl; x++) data[i + x] = data[di + x] + } else if (type == 1) { + for (; x < bpp; x++) data[i + x] = data[di + x] + for (; x < bpl; x++) data[i + x] = data[di + x] + data[i + x - bpp] + } else if (type == 2) { + for (; x < bpl; x++) data[i + x] = data[di + x] + data[i + x - bpl] + } else if (type == 3) { + for (; x < bpp; x++) data[i + x] = data[di + x] + (data[i + x - bpl] >>> 1) + for (; x < bpl; x++) data[i + x] = data[di + x] + ((data[i + x - bpl] + data[i + x - bpp]) >>> 1) + } else { + for (; x < bpp; x++) data[i + x] = data[di + x] + paeth(0, data[i + x - bpl], 0) + for (; x < bpl; x++) { + data[i + x] = data[di + x] + paeth(data[i + x - bpp], data[i + x - bpl], data[i + x - bpp - bpl]) } } } - return n + return data } - H.H.V = function (N, W, R, V) { - var n = 0, - A = 0, - l = V.length >>> 1 - while (A < R) { - var M = N[A + W] - V[A << 1] = 0 - V[(A << 1) + 1] = M - if (M > n) n = M - A++ - } - - while (A < l) { - V[A << 1] = 0 - V[(A << 1) + 1] = 0 - A++ - } - - return n + UPNG.decode._paeth = function (a, b, c) { + var p = a + b - c, + pa = p - a, + pb = p - b, + pc = p - c + if (pa * pa <= pb * pb && pa * pa <= pc * pc) return a + else if (pb * pb <= pc * pc) return b + return c } - H.H.n = function (N, W) { - var R = H.H.m, - V = N.length, - n, - A, - l, - M, - I, - e = R.j - for (var M = 0; M <= W; M++) e[M] = 0 - for (M = 1; M < V; M += 2) e[N[M]]++ - var b = R.K - n = 0 - e[0] = 0 - for (A = 1; A <= W; A++) { - n = (n + e[A - 1]) << 1 - b[A] = n - } + UPNG.decode._IHDR = function (data, offset, out) { + var bin = UPNG._bin + out.width = bin.readUint(data, offset) + offset += 4 + out.height = bin.readUint(data, offset) + offset += 4 + out.depth = data[offset] + offset++ + out.ctype = data[offset] + offset++ + out.compress = data[offset] + offset++ + out.filter = data[offset] + offset++ + out.interlace = data[offset] + offset++ + } - for (l = 0; l < V; l += 2) { - I = N[l + 1] - if (I != 0) { - N[l] = b[I] - b[I]++ + UPNG._bin = { + nextZero: function (data, p) { + while (data[p] != 0) p++ + return p + }, + readUshort: function (buff, p) { + return (buff[p] << 8) | buff[p + 1] + }, + writeUshort: function (buff, p, n) { + buff[p] = (n >> 8) & 255 + buff[p + 1] = n & 255 + }, + readUint: function (buff, p) { + return buff[p] * (256 * 256 * 256) + ((buff[p + 1] << 16) | (buff[p + 2] << 8) | buff[p + 3]) + }, + writeUint: function (buff, p, n) { + buff[p] = (n >> 24) & 255 + buff[p + 1] = (n >> 16) & 255 + buff[p + 2] = (n >> 8) & 255 + buff[p + 3] = n & 255 + }, + readASCII: function (buff, p, l) { + var s = '' + for (var i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]) + return s + }, + writeASCII: function (data, p, s) { + for (var i = 0; i < s.length; i++) data[p + i] = s.charCodeAt(i) + }, + readBytes: function (buff, p, l) { + var arr = [] + for (var i = 0; i < l; i++) arr.push(buff[p + i]) + return arr + }, + pad: function (n) { + return n.length < 2 ? '0' + n : n + }, + readUTF8: function (buff, p, l) { + var s = '', + ns + for (var i = 0; i < l; i++) s += '%' + UPNG._bin.pad(buff[p + i].toString(16)) + try { + ns = decodeURIComponent(s) + } catch (e) { + return UPNG._bin.readASCII(buff, p, l) } - } + + return ns + }, } + UPNG._copyTile = function (sb, sw, sh, tb, tw, th, xoff, yoff, mode) { + var w = Math.min(sw, tw), + h = Math.min(sh, th) + var si = 0, + ti = 0 + for (var y = 0; y < h; y++) { + for (var x = 0; x < w; x++) { + if (xoff >= 0 && yoff >= 0) { + si = (y * sw + x) << 2 + ti = ((yoff + y) * tw + xoff + x) << 2 + } else { + si = ((-yoff + y) * sw - xoff + x) << 2 + ti = (y * tw + x) << 2 + } - H.H.A = function (N, W, R) { - var V = N.length, - n = H.H.m, - A = n.r - for (var l = 0; l < V; l += 2) { - if (N[l + 1] != 0) { - var M = l >> 1, - I = N[l + 1], - e = (M << 4) | I, - b = W - I, - Z = N[l] << b, - m = Z + (1 << b) - while (Z != m) { - var J = A[Z] >>> (15 - W) - R[J] = e - Z++ + if (mode == 0) { + tb[ti] = sb[si] + tb[ti + 1] = sb[si + 1] + tb[ti + 2] = sb[si + 2] + tb[ti + 3] = sb[si + 3] + } else if (mode == 1) { + var fa = sb[si + 3] * (1 / 255), + fr = sb[si] * fa, + fg = sb[si + 1] * fa, + fb = sb[si + 2] * fa + var ba = tb[ti + 3] * (1 / 255), + br = tb[ti] * ba, + bg = tb[ti + 1] * ba, + bb = tb[ti + 2] * ba + + var ifa = 1 - fa, + oa = fa + ba * ifa, + ioa = oa == 0 ? 0 : 1 / oa + tb[ti + 3] = 255 * oa + tb[ti + 0] = (fr + br * ifa) * ioa + tb[ti + 1] = (fg + bg * ifa) * ioa + tb[ti + 2] = (fb + bb * ifa) * ioa + } else if (mode == 2) { + // copy only differences, otherwise zero + + var fa = sb[si + 3], + fr = sb[si], + fg = sb[si + 1], + fb = sb[si + 2] + var ba = tb[ti + 3], + br = tb[ti], + bg = tb[ti + 1], + bb = tb[ti + 2] + if (fa == ba && fr == br && fg == bg && fb == bb) { + tb[ti] = 0 + tb[ti + 1] = 0 + tb[ti + 2] = 0 + tb[ti + 3] = 0 + } else { + tb[ti] = fr + tb[ti + 1] = fg + tb[ti + 2] = fb + tb[ti + 3] = fa + } + } else if (mode == 3) { + // check if can be blended + + var fa = sb[si + 3], + fr = sb[si], + fg = sb[si + 1], + fb = sb[si + 2] + var ba = tb[ti + 3], + br = tb[ti], + bg = tb[ti + 1], + bb = tb[ti + 2] + if (fa == ba && fr == br && fg == bg && fb == bb) continue + //if(fa!=255 && ba!=0) return false; + if (fa < 220 && ba > 20) return false } } } - } - H.H.l = function (N, W) { - var R = H.H.m.r, - V = 15 - W - for (var n = 0; n < N.length; n += 2) { - var A = N[n] << (W - N[n + 1]) - N[n] = R[A] >>> V - } + return true } +} - H.H.M = function (N, W, R) { - R = R << (W & 7) - var V = W >>> 3 - N[V] |= R - N[V + 1] |= R >>> 8 - } +class RGBMLoader extends DataTextureLoader { + constructor(manager) { + super(manager) - H.H.I = function (N, W, R) { - R = R << (W & 7) - var V = W >>> 3 - N[V] |= R - N[V + 1] |= R >>> 8 - N[V + 2] |= R >>> 16 + this.type = HalfFloatType + this.maxRange = 7 // more information about this property at https://iwasbeingirony.blogspot.com/2010/06/difference-between-rgbm-and-rgbd.html } - H.H.e = function (N, W, R) { - return ((N[W >>> 3] | (N[(W >>> 3) + 1] << 8)) >>> (W & 7)) & ((1 << R) - 1) + setDataType(value) { + this.type = value + return this } - H.H.b = function (N, W, R) { - return ((N[W >>> 3] | (N[(W >>> 3) + 1] << 8) | (N[(W >>> 3) + 2] << 16)) >>> (W & 7)) & ((1 << R) - 1) + setMaxRange(value) { + this.maxRange = value + return this } - H.H.Z = function (N, W) { - return (N[W >>> 3] | (N[(W >>> 3) + 1] << 8) | (N[(W >>> 3) + 2] << 16)) >>> (W & 7) - } + loadCubemap(urls, onLoad, onProgress, onError) { + const texture = new CubeTexture() - H.H.i = function (N, W) { - return (N[W >>> 3] | (N[(W >>> 3) + 1] << 8) | (N[(W >>> 3) + 2] << 16) | (N[(W >>> 3) + 3] << 24)) >>> (W & 7) - } + let loaded = 0 - H.H.m = (function () { - var N = Uint16Array, - W = Uint32Array - return { - K: new N(16), - j: new N(16), - X: [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15], - S: [ - 3, - 4, - 5, - 6, - 7, - 8, - 9, - 10, - 11, - 13, - 15, - 17, - 19, - 23, - 27, - 31, - 35, - 43, - 51, - 59, - 67, - 83, - 99, - 115, - 131, - 163, - 195, - 227, - 258, - 999, - 999, - 999, - ], - T: [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0], - q: new N(32), - p: [ - 1, - 2, - 3, - 4, - 5, - 7, - 9, - 13, - 17, - 25, - 33, - 49, - 65, - 97, - 129, - 193, - 257, - 385, - 513, - 769, - 1025, - 1537, - 2049, - 3073, - 4097, - 6145, - 8193, - 12289, - 16385, - 24577, - 65535, - 65535, - ], - z: [0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0], - c: new W(32), - J: new N(512), - _: [], - h: new N(32), - $: [], - w: new N(32768), - C: [], - v: [], - d: new N(32768), - D: [], - u: new N(512), - Q: [], - r: new N(1 << 15), - s: new W(286), - Y: new W(30), - a: new W(19), - t: new W(15e3), - k: new N(1 << 16), - g: new N(1 << 15), - } - })() - ;(function () { - var N = H.H.m, - W = 1 << 15 - for (var R = 0; R < W; R++) { - var V = R - V = ((V & 2863311530) >>> 1) | ((V & 1431655765) << 1) - V = ((V & 3435973836) >>> 2) | ((V & 858993459) << 2) - V = ((V & 4042322160) >>> 4) | ((V & 252645135) << 4) - V = ((V & 4278255360) >>> 8) | ((V & 16711935) << 8) - N.r[R] = ((V >>> 16) | (V << 16)) >>> 17 - } + const scope = this - function n(A, l, M) { - while (l-- != 0) A.push(0, M) - } + function loadTexture(i) { + scope.load( + urls[i], + function (image) { + texture.images[i] = image - for (var R = 0; R < 32; R++) { - N.q[R] = (N.S[R] << 3) | N.T[R] - N.c[R] = (N.p[R] << 4) | N.z[R] - } + loaded++ - n(N._, 144, 8) - n(N._, 255 - 143, 9) - n(N._, 279 - 255, 7) - n(N._, 287 - 279, 8) - H.H.n(N._, 9) - H.H.A(N._, 9, N.J) - H.H.l(N._, 9) - n(N.$, 32, 5) - H.H.n(N.$, 5) - H.H.A(N.$, 5, N.h) - H.H.l(N.$, 5) - n(N.Q, 19, 0) - n(N.C, 286, 0) - n(N.D, 30, 0) - n(N.v, 320, 0) - })() + if (loaded === 6) { + texture.needsUpdate = true - return H.H.N -})() - -UPNG.decode._readInterlace = function (data, out) { - var w = out.width, - h = out.height - var bpp = UPNG.decode._getBPP(out), - cbpp = bpp >> 3, - bpl = Math.ceil((w * bpp) / 8) - var img = new Uint8Array(h * bpl) - var di = 0 - - var starting_row = [0, 0, 4, 0, 2, 0, 1] - var starting_col = [0, 4, 0, 2, 0, 1, 0] - var row_increment = [8, 8, 8, 4, 4, 2, 2] - var col_increment = [8, 8, 4, 4, 2, 2, 1] - - var pass = 0 - while (pass < 7) { - var ri = row_increment[pass], - ci = col_increment[pass] - var sw = 0, - sh = 0 - var cr = starting_row[pass] - while (cr < h) { - cr += ri - sh++ + if (onLoad) onLoad(texture) + } + }, + undefined, + onError, + ) } - var cc = starting_col[pass] - while (cc < w) { - cc += ci - sw++ + for (let i = 0; i < urls.length; ++i) { + loadTexture(i) } - var bpll = Math.ceil((sw * bpp) / 8) - UPNG.decode._filterZero(data, out, di, sw, sh) - - var y = 0, - row = starting_row[pass] - var val - - while (row < h) { - var col = starting_col[pass] - var cdi = (di + y * bpll) << 3 - - while (col < w) { - if (bpp == 1) { - val = data[cdi >> 3] - val = (val >> (7 - (cdi & 7))) & 1 - img[row * bpl + (col >> 3)] |= val << (7 - ((col & 7) << 0)) - } - - if (bpp == 2) { - val = data[cdi >> 3] - val = (val >> (6 - (cdi & 7))) & 3 - img[row * bpl + (col >> 2)] |= val << (6 - ((col & 3) << 1)) - } - - if (bpp == 4) { - val = data[cdi >> 3] - val = (val >> (4 - (cdi & 7))) & 15 - img[row * bpl + (col >> 1)] |= val << (4 - ((col & 1) << 2)) - } - - if (bpp >= 8) { - var ii = row * bpl + col * cbpp - for (var j = 0; j < cbpp; j++) img[ii + j] = data[(cdi >> 3) + j] - } - - cdi += bpp - col += ci - } - - y++ - row += ri - } + texture.type = this.type + texture.format = RGBAFormat + texture.minFilter = LinearFilter + texture.generateMipmaps = false - if (sw * sh != 0) di += sh * (1 + bpll) - pass = pass + 1 + return texture } - return img -} - -UPNG.decode._getBPP = function (out) { - var noc = [1, null, 3, 1, 2, null, 4][out.ctype] - return noc * out.depth -} - -UPNG.decode._filterZero = function (data, out, off, w, h) { - var bpp = UPNG.decode._getBPP(out), - bpl = Math.ceil((w * bpp) / 8), - paeth = UPNG.decode._paeth - bpp = Math.ceil(bpp / 8) - - var i, - di, - type = data[off], - x = 0 - - if (type > 1) data[off] = [0, 0, 1][type - 2] - if (type == 3) for (x = bpp; x < bpl; x++) data[x + 1] = (data[x + 1] + (data[x + 1 - bpp] >>> 1)) & 255 - - for (var y = 0; y < h; y++) { - i = off + y * bpl - di = i + y + 1 - type = data[di - 1] - x = 0 - - if (type == 0) { - for (; x < bpl; x++) data[i + x] = data[di + x] - } else if (type == 1) { - for (; x < bpp; x++) data[i + x] = data[di + x] - for (; x < bpl; x++) data[i + x] = data[di + x] + data[i + x - bpp] - } else if (type == 2) { - for (; x < bpl; x++) data[i + x] = data[di + x] + data[i + x - bpl] - } else if (type == 3) { - for (; x < bpp; x++) data[i + x] = data[di + x] + (data[i + x - bpl] >>> 1) - for (; x < bpl; x++) data[i + x] = data[di + x] + ((data[i + x - bpl] + data[i + x - bpp]) >>> 1) - } else { - for (; x < bpp; x++) data[i + x] = data[di + x] + paeth(0, data[i + x - bpl], 0) - for (; x < bpl; x++) { - data[i + x] = data[di + x] + paeth(data[i + x - bpp], data[i + x - bpl], data[i + x - bpp - bpl]) - } - } - } + parse(buffer) { + init() + const img = UPNG.decode(buffer) + const rgba = UPNG.toRGBA8(img)[0] - return data -} + const data = new Uint8Array(rgba) + const size = img.width * img.height * 4 -UPNG.decode._paeth = function (a, b, c) { - var p = a + b - c, - pa = p - a, - pb = p - b, - pc = p - c - if (pa * pa <= pb * pb && pa * pa <= pc * pc) return a - else if (pb * pb <= pc * pc) return b - return c -} + const output = this.type === HalfFloatType ? new Uint16Array(size) : new Float32Array(size) -UPNG.decode._IHDR = function (data, offset, out) { - var bin = UPNG._bin - out.width = bin.readUint(data, offset) - offset += 4 - out.height = bin.readUint(data, offset) - offset += 4 - out.depth = data[offset] - offset++ - out.ctype = data[offset] - offset++ - out.compress = data[offset] - offset++ - out.filter = data[offset] - offset++ - out.interlace = data[offset] - offset++ -} + // decode RGBM -UPNG._bin = { - nextZero: function (data, p) { - while (data[p] != 0) p++ - return p - }, - readUshort: function (buff, p) { - return (buff[p] << 8) | buff[p + 1] - }, - writeUshort: function (buff, p, n) { - buff[p] = (n >> 8) & 255 - buff[p + 1] = n & 255 - }, - readUint: function (buff, p) { - return buff[p] * (256 * 256 * 256) + ((buff[p + 1] << 16) | (buff[p + 2] << 8) | buff[p + 3]) - }, - writeUint: function (buff, p, n) { - buff[p] = (n >> 24) & 255 - buff[p + 1] = (n >> 16) & 255 - buff[p + 2] = (n >> 8) & 255 - buff[p + 3] = n & 255 - }, - readASCII: function (buff, p, l) { - var s = '' - for (var i = 0; i < l; i++) s += String.fromCharCode(buff[p + i]) - return s - }, - writeASCII: function (data, p, s) { - for (var i = 0; i < s.length; i++) data[p + i] = s.charCodeAt(i) - }, - readBytes: function (buff, p, l) { - var arr = [] - for (var i = 0; i < l; i++) arr.push(buff[p + i]) - return arr - }, - pad: function (n) { - return n.length < 2 ? '0' + n : n - }, - readUTF8: function (buff, p, l) { - var s = '', - ns - for (var i = 0; i < l; i++) s += '%' + UPNG._bin.pad(buff[p + i].toString(16)) - try { - ns = decodeURIComponent(s) - } catch (e) { - return UPNG._bin.readASCII(buff, p, l) - } + for (let i = 0; i < data.length; i += 4) { + const r = data[i + 0] / 255 + const g = data[i + 1] / 255 + const b = data[i + 2] / 255 + const a = data[i + 3] / 255 - return ns - }, -} -UPNG._copyTile = function (sb, sw, sh, tb, tw, th, xoff, yoff, mode) { - var w = Math.min(sw, tw), - h = Math.min(sh, th) - var si = 0, - ti = 0 - for (var y = 0; y < h; y++) { - for (var x = 0; x < w; x++) { - if (xoff >= 0 && yoff >= 0) { - si = (y * sw + x) << 2 - ti = ((yoff + y) * tw + xoff + x) << 2 + if (this.type === HalfFloatType) { + output[i + 0] = DataUtils.toHalfFloat(Math.min(r * a * this.maxRange, 65504)) + output[i + 1] = DataUtils.toHalfFloat(Math.min(g * a * this.maxRange, 65504)) + output[i + 2] = DataUtils.toHalfFloat(Math.min(b * a * this.maxRange, 65504)) + output[i + 3] = DataUtils.toHalfFloat(1) } else { - si = ((-yoff + y) * sw - xoff + x) << 2 - ti = (y * tw + x) << 2 + output[i + 0] = r * a * this.maxRange + output[i + 1] = g * a * this.maxRange + output[i + 2] = b * a * this.maxRange + output[i + 3] = 1 } + } - if (mode == 0) { - tb[ti] = sb[si] - tb[ti + 1] = sb[si + 1] - tb[ti + 2] = sb[si + 2] - tb[ti + 3] = sb[si + 3] - } else if (mode == 1) { - var fa = sb[si + 3] * (1 / 255), - fr = sb[si] * fa, - fg = sb[si + 1] * fa, - fb = sb[si + 2] * fa - var ba = tb[ti + 3] * (1 / 255), - br = tb[ti] * ba, - bg = tb[ti + 1] * ba, - bb = tb[ti + 2] * ba - - var ifa = 1 - fa, - oa = fa + ba * ifa, - ioa = oa == 0 ? 0 : 1 / oa - tb[ti + 3] = 255 * oa - tb[ti + 0] = (fr + br * ifa) * ioa - tb[ti + 1] = (fg + bg * ifa) * ioa - tb[ti + 2] = (fb + bb * ifa) * ioa - } else if (mode == 2) { - // copy only differences, otherwise zero - - var fa = sb[si + 3], - fr = sb[si], - fg = sb[si + 1], - fb = sb[si + 2] - var ba = tb[ti + 3], - br = tb[ti], - bg = tb[ti + 1], - bb = tb[ti + 2] - if (fa == ba && fr == br && fg == bg && fb == bb) { - tb[ti] = 0 - tb[ti + 1] = 0 - tb[ti + 2] = 0 - tb[ti + 3] = 0 - } else { - tb[ti] = fr - tb[ti + 1] = fg - tb[ti + 2] = fb - tb[ti + 3] = fa - } - } else if (mode == 3) { - // check if can be blended - - var fa = sb[si + 3], - fr = sb[si], - fg = sb[si + 1], - fb = sb[si + 2] - var ba = tb[ti + 3], - br = tb[ti], - bg = tb[ti + 1], - bb = tb[ti + 2] - if (fa == ba && fr == br && fg == bg && fb == bb) continue - //if(fa!=255 && ba!=0) return false; - if (fa < 220 && ba > 20) return false - } + return { + width: img.width, + height: img.height, + data: output, + format: RGBAFormat, + type: this.type, + flipY: true, } } - - return true } export { RGBMLoader } diff --git a/src/loaders/STLLoader.d.ts b/src/loaders/STLLoader.d.ts index eaac182d..0f33824f 100644 --- a/src/loaders/STLLoader.d.ts +++ b/src/loaders/STLLoader.d.ts @@ -1,4 +1,5 @@ -import { BufferGeometry, Loader, LoadingManager } from 'three' +import { BufferGeometry, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class STLLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/SVGLoader.d.ts b/src/loaders/SVGLoader.d.ts index a41e5ed7..2ee78038 100644 --- a/src/loaders/SVGLoader.d.ts +++ b/src/loaders/SVGLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, ShapePath, BufferGeometry, Vector3, Shape } from 'three' +import { LoadingManager, ShapePath, BufferGeometry, Vector3, Shape } from 'three' +import { Loader } from '../types/Loader' export interface SVGResultPaths extends ShapePath { userData?: diff --git a/src/loaders/TDSLoader.d.ts b/src/loaders/TDSLoader.d.ts index 1a5fdaaf..05f6e0a8 100644 --- a/src/loaders/TDSLoader.d.ts +++ b/src/loaders/TDSLoader.d.ts @@ -1,4 +1,5 @@ -import { Color, Group, Loader, LoadingManager, Material, Mesh, Texture } from 'three' +import { Color, Group, LoadingManager, Material, Mesh, Texture } from 'three' +import { Loader } from '../types/Loader' export class TDSLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/TTFLoader.d.ts b/src/loaders/TTFLoader.d.ts index f525d51e..93388654 100644 --- a/src/loaders/TTFLoader.d.ts +++ b/src/loaders/TTFLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager } from 'three' +import { LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class TTFLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/TiltLoader.d.ts b/src/loaders/TiltLoader.d.ts index 6a94961e..e9821b7d 100644 --- a/src/loaders/TiltLoader.d.ts +++ b/src/loaders/TiltLoader.d.ts @@ -1,4 +1,5 @@ -import { Loader, LoadingManager, Group } from 'three' +import { LoadingManager, Group } from 'three' +import { Loader } from '../types/Loader' export class TiltLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/TiltLoader.js b/src/loaders/TiltLoader.js index f8d21592..5ce41e4d 100644 --- a/src/loaders/TiltLoader.js +++ b/src/loaders/TiltLoader.js @@ -369,7 +369,9 @@ const common = { const shaders = () => ({ Light: { uniforms: { - mainTex: { value: new TextureLoader().setPath('./textures/tiltbrush/').loader.load('Light.webp') }, + mainTex: { + value: /* @__PURE__ */ new TextureLoader().setPath('./textures/tiltbrush/').loader.load('Light.webp'), + }, alphaTest: { value: 0.067 }, emission_gain: { value: 0.45 }, alpha: { value: 1 }, diff --git a/src/loaders/VOXLoader.d.ts b/src/loaders/VOXLoader.d.ts index 47935ce5..6f08c344 100644 --- a/src/loaders/VOXLoader.d.ts +++ b/src/loaders/VOXLoader.d.ts @@ -1,4 +1,5 @@ -import { Data3DTexture, Mesh, Loader, LoadingManager } from 'three' +import { Texture, Mesh, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export interface Chunk { palette: number[] @@ -23,6 +24,7 @@ export class VOXMesh extends Mesh { constructor(chunk: Chunk) } -export class VOXData3DTexture extends Data3DTexture { +// Data3DTexture +export class VOXData3DTexture extends Texture { constructor(chunk: Chunk) } diff --git a/src/loaders/VRMLLoader.d.ts b/src/loaders/VRMLLoader.d.ts index 1b8b8fc3..7153611f 100644 --- a/src/loaders/VRMLLoader.d.ts +++ b/src/loaders/VRMLLoader.d.ts @@ -1,4 +1,5 @@ -import { Scene, Loader, LoadingManager } from 'three' +import { Scene, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class VRMLLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/VRMLoader.d.ts b/src/loaders/VRMLoader.d.ts new file mode 100644 index 00000000..3528bfe7 --- /dev/null +++ b/src/loaders/VRMLoader.d.ts @@ -0,0 +1,20 @@ +import { LoadingManager } from 'three' +import { Loader } from '../types/Loader' + +import { GLTFLoader, GLTF } from './GLTFLoader' +import { DRACOLoader } from './DRACOLoader' + +export class VRMLoader extends Loader { + constructor(manager?: LoadingManager) + gltfLoader: GLTFLoader + + load( + url: string, + onLoad: (scene: GLTF) => void, + onProgress?: (event: ProgressEvent) => void, + onError?: (event: ErrorEvent) => void, + ): void + loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise + parse(gltf: GLTF, onLoad: (scene: GLTF) => void): void + setDRACOLoader(dracoLoader: DRACOLoader): this +} diff --git a/src/loaders/VTKLoader.d.ts b/src/loaders/VTKLoader.d.ts index d7e3104d..95c7ce44 100644 --- a/src/loaders/VTKLoader.d.ts +++ b/src/loaders/VTKLoader.d.ts @@ -1,4 +1,5 @@ -import { BufferGeometry, Loader, LoadingManager } from 'three' +import { BufferGeometry, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class VTKLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/XLoader.d.ts b/src/loaders/XLoader.d.ts new file mode 100644 index 00000000..c2f7c046 --- /dev/null +++ b/src/loaders/XLoader.d.ts @@ -0,0 +1,20 @@ +import { Mesh, LoadingManager } from 'three' +import { Loader } from '../types/Loader' + +export interface XResult { + animations: object[] + models: Mesh[] +} + +export class XLoader extends Loader { + constructor(manager?: LoadingManager) + + load( + url: string, + onLoad: (object: XResult) => void, + onProgress?: (event: ProgressEvent) => void, + onError?: (event: ErrorEvent) => void, + ): void + loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise + parse(data: ArrayBuffer | string, onLoad: (object: object) => void): object +} diff --git a/src/loaders/XLoader.js b/src/loaders/XLoader.js index 49ff0616..15a38d4a 100644 --- a/src/loaders/XLoader.js +++ b/src/loaders/XLoader.js @@ -20,7 +20,7 @@ import { Vector3, } from 'three' -var XLoader = (function () { +var XLoader = /* @__PURE__ */ (function () { var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function') diff --git a/src/loaders/XYZLoader.d.ts b/src/loaders/XYZLoader.d.ts index 01335f25..10041406 100644 --- a/src/loaders/XYZLoader.d.ts +++ b/src/loaders/XYZLoader.d.ts @@ -1,4 +1,5 @@ -import { BufferGeometry, Loader, LoadingManager } from 'three' +import { BufferGeometry, LoadingManager } from 'three' +import { Loader } from '../types/Loader' export class XYZLoader extends Loader { constructor(manager?: LoadingManager) diff --git a/src/loaders/lwo/IFFParser.js b/src/loaders/lwo/IFFParser.js index a188eb23..68e9b869 100644 --- a/src/loaders/lwo/IFFParser.js +++ b/src/loaders/lwo/IFFParser.js @@ -32,19 +32,16 @@ * **/ -import { LoaderUtils } from 'three' import { LWO2Parser } from './LWO2Parser' import { LWO3Parser } from './LWO3Parser' -function IFFParser() { - this.debugger = new Debugger() - // this.debugger.enable(); // un-comment to log IFF hierarchy. -} - -IFFParser.prototype = { - constructor: IFFParser, +class IFFParser { + constructor() { + this.debugger = new Debugger() + // this.debugger.enable(); // un-comment to log IFF hierarchy. + } - parse: function (buffer) { + parse(buffer) { this.reader = new DataViewReader(buffer) this.tree = { @@ -74,7 +71,7 @@ IFFParser.prototype = { this.debugger.closeForms() return this.tree - }, + } parseTopForm() { this.debugger.offset = this.reader.offset @@ -104,7 +101,7 @@ IFFParser.prototype = { this.debugger.log() return - }, + } /// // FORM PARSING METHODS @@ -281,7 +278,7 @@ IFFParser.prototype = { this.debugger.node = 0 this.debugger.nodeID = type this.debugger.log() - }, + } setupForm(type, length) { if (!this.currentForm) this.currentForm = this.currentNode @@ -298,18 +295,18 @@ IFFParser.prototype = { this.currentForm = this.currentForm[type] } - }, + } skipForm(length) { this.reader.skip(length - 4) - }, + } parseUnknownForm(type, length) { console.warn('LWOLoader: unknown FORM encountered: ' + type, length) printBuffer(this.reader.dv.buffer, this.reader.offset, length - 4) this.reader.skip(length - 4) - }, + } parseSurfaceForm(length) { this.reader.skip(8) // unknown Uint32 x2 @@ -331,7 +328,7 @@ IFFParser.prototype = { this.parentForm = this.tree.materials this.currentForm = surface this.currentFormEnd = this.reader.offset + length - }, + } parseSurfaceLwo2(length) { var name = this.reader.getString() @@ -350,7 +347,7 @@ IFFParser.prototype = { this.parentForm = this.tree.materials this.currentForm = surface this.currentFormEnd = this.reader.offset + length - }, + } parseSubNode(length) { // parse the NRNM CHUNK of the subnode FORM to get @@ -367,7 +364,7 @@ IFFParser.prototype = { this.currentNode = node this.currentFormEnd = this.reader.offset + length - }, + } // collect attributes from all nodes at the top level of a surface parseConnections(length) { @@ -375,7 +372,7 @@ IFFParser.prototype = { this.parentForm = this.currentForm this.currentForm = this.currentSurface.connections - }, + } // surface node attribute data, e.g. specular, roughness etc parseEntryForm(length) { @@ -384,7 +381,7 @@ IFFParser.prototype = { this.currentForm = this.currentNode.attributes this.setupForm(name, length) - }, + } // parse values from material - doesn't match up to other LWO3 data types // sub form of entry form @@ -404,7 +401,7 @@ IFFParser.prototype = { this.reader.skip(24) this.currentForm.value = this.reader.getFloat64Array(3) } - }, + } // holds various data about texture node image state // Data other thanmipMapLevel unknown @@ -412,7 +409,7 @@ IFFParser.prototype = { this.reader.skip(8) // unknown this.currentForm.mipMapLevel = this.reader.getFloat32() - }, + } // LWO2 style image data node OR LWO3 textures defined at top level in editor (not as SURF node) parseImageMap(length) { @@ -426,7 +423,7 @@ IFFParser.prototype = { this.currentForm = map this.reader.skip(10) // unknown, could be an issue if it contains a VX - }, + } parseTextureNodeAttribute(type) { this.reader.skip(28) // FORM + length + VPRM + unknown + Uint32 x2 + float32 @@ -459,12 +456,12 @@ IFFParser.prototype = { } this.reader.skip(2) // unknown - }, + } // ENVL forms are currently ignored parseEnvelope(length) { this.reader.skip(length - 4) // skipping entirely for now - }, + } /// // CHUNK PARSING METHODS @@ -497,7 +494,7 @@ IFFParser.prototype = { } this.tree.textures.push(texture) this.currentForm = texture - }, + } parseClipLwo2(length) { var texture = { @@ -521,12 +518,12 @@ IFFParser.prototype = { this.tree.textures.push(texture) this.currentForm = texture - }, + } parseImage() { this.reader.skip(8) // unknown this.currentForm.fileName = this.reader.getString() - }, + } parseXVAL(type, length) { var endOffset = this.reader.offset + length - 4 @@ -535,7 +532,7 @@ IFFParser.prototype = { this.currentForm[type] = this.reader.getFloat32() this.reader.setOffset(endOffset) // set end offset directly to skip optional envelope - }, + } parseXVAL3(type, length) { var endOffset = this.reader.offset + length - 4 @@ -548,7 +545,7 @@ IFFParser.prototype = { } this.reader.setOffset(endOffset) - }, + } // Tags associated with an object // OTAG { type[ID4], tag-string[S0] } @@ -558,7 +555,7 @@ IFFParser.prototype = { this.tree.objectTags[this.reader.getIDTag()] = { tagString: this.reader.getString(), } - }, + } // Signals the start of a new layer. All the data chunks which follow will be included in this layer until another layer chunk is encountered. // LAYR: number[U2], flags[U2], pivot[VEC12], name[S0], parent[U2] @@ -577,18 +574,18 @@ IFFParser.prototype = { // if we have not reached then end of the layer block, there must be a parent defined this.currentLayer.parent = parsedLength < length ? this.reader.getUint16() : -1 // omitted or -1 for no parent - }, + } // VEC12 * ( F4 + F4 + F4 ) array of x,y,z vectors // Converting from left to right handed coordinate system: // x -> -x and switch material FrontSide -> BackSide parsePoints(length) { this.currentPoints = [] - for (let i = 0; i < length / 4; i += 3) { + for (var i = 0; i < length / 4; i += 3) { // z -> -z to match three.js right handed coords this.currentPoints.push(this.reader.getFloat32(), this.reader.getFloat32(), -this.reader.getFloat32()) } - }, + } // parse VMAP or VMAD // Associates a set of floating-point vectors with a set of points. @@ -643,7 +640,7 @@ IFFParser.prototype = { console.warn('LWOLoader: unknown vertex map type: ' + type) this.reader.skip(remainingLength) } - }, + } parseUVMapping(name, finalOffset, discontinuous) { var uvIndices = [] @@ -674,7 +671,7 @@ IFFParser.prototype = { uvs: uvs, } } - }, + } parseMorphTargets(name, finalOffset, type) { var indices = [] @@ -695,7 +692,7 @@ IFFParser.prototype = { points: points, type: type, } - }, + } // A list of polygons for the current layer. // POLS { type[ID4], ( numvert+flags[U2], vert[VX] # numvert ) * } @@ -715,7 +712,7 @@ IFFParser.prototype = { numverts = numverts & 1023 // remaining ten low order bits are vertex num polygonDimensions.push(numverts) - for (let j = 0; j < numverts; j++) indices.push(this.reader.getVariableLengthIndex()) + for (var j = 0; j < numverts; j++) indices.push(this.reader.getVariableLengthIndex()) } var geometryData = { @@ -730,27 +727,26 @@ IFFParser.prototype = { else if (polygonDimensions[0] === 2) geometryData.type = 'lines' this.currentLayer.geometry = geometryData - }, + } // Lists the tag strings that can be associated with polygons by the PTAG chunk. // TAGS { tag-string[S0] * } parseTagStrings(length) { this.tree.tags = this.reader.getStringArray(length) - }, + } // Associates tags of a given type with polygons in the most recent POLS chunk. // PTAG { type[ID4], ( poly[VX], tag[U2] ) * } parsePolygonTagMapping(length) { var finalOffset = this.reader.offset + length var type = this.reader.getIDTag() - if (type === 'SURF') { - this.parseMaterialIndices(finalOffset) - } else { + if (type === 'SURF') this.parseMaterialIndices(finalOffset) + else { //PART, SMGP, COLR not supported this.reader.skip(length - 4) } - }, + } parseMaterialIndices(finalOffset) { // array holds polygon index followed by material index @@ -762,7 +758,7 @@ IFFParser.prototype = { this.currentLayer.geometry.materialIndices.push(polygonIndex, materialIndex) } - }, + } parseUnknownCHUNK(blockID, length) { console.warn('LWOLoader: unknown chunk type: ' + blockID + ' length: ' + length) @@ -773,20 +769,20 @@ IFFParser.prototype = { var data = this.reader.getString(length) this.currentForm[blockID] = data - }, + } } -function DataViewReader(buffer) { - this.dv = new DataView(buffer) - this.offset = 0 -} +class DataViewReader { + constructor(buffer) { + this.dv = new DataView(buffer) + this.offset = 0 + this._textDecoder = new TextDecoder() + this._bytes = new Uint8Array(buffer) + } -DataViewReader.prototype = { - constructor: DataViewReader, - - size: function () { + size() { return this.dv.buffer.byteLength - }, + } setOffset(offset) { if (offset > 0 && offset < this.dv.buffer.byteLength) { @@ -794,80 +790,80 @@ DataViewReader.prototype = { } else { console.error('LWOLoader: invalid buffer offset') } - }, + } - endOfFile: function () { + endOfFile() { if (this.offset >= this.size()) return true return false - }, + } - skip: function (length) { + skip(length) { this.offset += length - }, + } - getUint8: function () { + getUint8() { var value = this.dv.getUint8(this.offset) this.offset += 1 return value - }, + } - getUint16: function () { + getUint16() { var value = this.dv.getUint16(this.offset) this.offset += 2 return value - }, + } - getInt32: function () { + getInt32() { var value = this.dv.getInt32(this.offset, false) this.offset += 4 return value - }, + } - getUint32: function () { + getUint32() { var value = this.dv.getUint32(this.offset, false) this.offset += 4 return value - }, + } - getUint64: function () { + getUint64() { var low, high high = this.getUint32() low = this.getUint32() return high * 0x100000000 + low - }, + } - getFloat32: function () { + getFloat32() { var value = this.dv.getFloat32(this.offset, false) this.offset += 4 return value - }, + } - getFloat32Array: function (size) { + getFloat32Array(size) { var a = [] - for (let i = 0; i < size; i++) { + for (var i = 0; i < size; i++) { a.push(this.getFloat32()) } return a - }, + } - getFloat64: function () { + getFloat64() { var value = this.dv.getFloat64(this.offset, this.littleEndian) this.offset += 8 return value - }, + } - getFloat64Array: function (size) { + getFloat64Array(size) { var a = [] - for (let i = 0; i < size; i++) { + for (var i = 0; i < size; i++) { a.push(this.getFloat64()) } return a - }, + } // get variable-length index data type // VX ::= index[U2] | (index + 0xFF000000)[U4] @@ -883,63 +879,64 @@ DataViewReader.prototype = { } return firstByte * 256 + this.getUint8() - }, + } // An ID tag is a sequence of 4 bytes containing 7-bit ASCII values getIDTag() { return this.getString(4) - }, + } - getString: function (size) { + getString(size) { if (size === 0) return - // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead - var a = [] + const start = this.offset + + let result + let length if (size) { - for (let i = 0; i < size; i++) { - a[i] = this.getUint8() - } + length = size + result = this._textDecoder.decode(new Uint8Array(this.dv.buffer, start, size)) } else { - var currentChar - var len = 0 + // use 1:1 mapping of buffer to avoid redundant new array creation. + length = this._bytes.indexOf(0, start) - start - while (currentChar !== 0) { - currentChar = this.getUint8() - if (currentChar !== 0) a.push(currentChar) - len++ - } + result = this._textDecoder.decode(new Uint8Array(this.dv.buffer, start, length)) + + // account for null byte in length + length++ - if (!isEven(len + 1)) this.getUint8() // if string with terminating nullbyte is uneven, extra nullbyte is added + // if string with terminating nullbyte is uneven, extra nullbyte is added, skip that too + length += length % 2 } - return LoaderUtils.decodeText(new Uint8Array(a)) - }, + this.skip(length) - getStringArray: function (size) { + return result + } + + getStringArray(size) { var a = this.getString(size) a = a.split('\0') return a.filter(Boolean) // return array with any empty strings removed - }, + } } // ************** DEBUGGER ************** -function Debugger() { - this.active = false - this.depth = 0 - this.formList = [] -} - -Debugger.prototype = { - constructor: Debugger, +class Debugger { + constructor() { + this.active = false + this.depth = 0 + this.formList = [] + } - enable: function () { + enable() { this.active = true - }, + } - log: function () { + log() { if (!this.active) return var nodeType @@ -973,19 +970,19 @@ Debugger.prototype = { } this.skipped = false - }, + } - closeForms: function () { + closeForms() { if (!this.active) return - for (let i = this.formList.length - 1; i >= 0; i--) { + for (var i = this.formList.length - 1; i >= 0; i--) { if (this.offset >= this.formList[i]) { this.depth -= 1 console.log('| '.repeat(this.depth) + '}') this.formList.splice(-1, 1) } } - }, + } } // ************** UTILITY FUNCTIONS ************** @@ -1003,7 +1000,7 @@ function stringOffset(string) { // for testing purposes, dump buffer to console // printBuffer( this.reader.dv.buffer, this.reader.offset, length ); function printBuffer(buffer, from, to) { - console.log(LoaderUtils.decodeText(new Uint8Array(buffer, from, to))) + console.log(new TextDecoder().decode(new Uint8Array(buffer, from, to))) } export { IFFParser } diff --git a/src/loaders/lwo/LWO2Parser.js b/src/loaders/lwo/LWO2Parser.js index ad0c9f6a..ba9f15b5 100644 --- a/src/loaders/lwo/LWO2Parser.js +++ b/src/loaders/lwo/LWO2Parser.js @@ -1,16 +1,14 @@ -function LWO2Parser(IFFParser) { - this.IFF = IFFParser -} - -LWO2Parser.prototype = { - constructor: LWO2Parser, +class LWO2Parser { + constructor(IFFParser) { + this.IFF = IFFParser + } - parseBlock: function () { + parseBlock() { this.IFF.debugger.offset = this.IFF.reader.offset this.IFF.debugger.closeForms() - var blockID = this.IFF.reader.getIDTag() - var length = this.IFF.reader.getUint32() // size of data in bytes + const blockID = this.IFF.reader.getIDTag() + let length = this.IFF.reader.getUint32() // size of data in bytes if (length > this.IFF.reader.dv.byteLength - this.IFF.reader.offset) { this.IFF.reader.offset -= 4 length = this.IFF.reader.getUint16() @@ -99,6 +97,7 @@ LWO2Parser.prototype = { case 'WRPW': // image wrap w ( for cylindrical and spherical projections) case 'WRPH': // image wrap h case 'NMOD': + case 'NSEL': case 'NPRW': case 'NPLA': case 'NODS': @@ -223,14 +222,11 @@ LWO2Parser.prototype = { // Image Map Layer case 'WRAP': - this.IFF.currentForm.wrap = { - w: this.IFF.reader.getUint16(), - h: this.IFF.reader.getUint16(), - } + this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() } break case 'IMAG': - var index = this.IFF.reader.getVariableLengthIndex() + const index = this.IFF.reader.getVariableLengthIndex() this.IFF.currentForm.imageIndex = index break @@ -298,50 +294,38 @@ LWO2Parser.prototype = { // LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format case 'SMAN': - var maxSmoothingAngle = this.IFF.reader.getFloat32() + const maxSmoothingAngle = this.IFF.reader.getFloat32() this.IFF.currentSurface.attributes.smooth = maxSmoothingAngle < 0 ? false : true break // LWO2: Basic Surface Parameters case 'COLR': - this.IFF.currentSurface.attributes.Color = { - value: this.IFF.reader.getFloat32Array(3), - } + this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array(3) } this.IFF.reader.skip(2) // VX: envelope break case 'LUMI': - this.IFF.currentSurface.attributes.Luminosity = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'SPEC': - this.IFF.currentSurface.attributes.Specular = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'DIFF': - this.IFF.currentSurface.attributes.Diffuse = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'REFL': - this.IFF.currentSurface.attributes.Reflection = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'GLOS': - this.IFF.currentSurface.attributes.Glossiness = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break @@ -410,7 +394,7 @@ LWO2Parser.prototype = { if (this.IFF.reader.offset >= this.IFF.currentFormEnd) { this.IFF.currentForm = this.IFF.parentForm } - }, + } } export { LWO2Parser } diff --git a/src/loaders/lwo/LWO3Parser.js b/src/loaders/lwo/LWO3Parser.js index 3bcdca74..37b34eac 100644 --- a/src/loaders/lwo/LWO3Parser.js +++ b/src/loaders/lwo/LWO3Parser.js @@ -1,16 +1,14 @@ -function LWO3Parser(IFFParser) { - this.IFF = IFFParser -} - -LWO3Parser.prototype = { - constructor: LWO3Parser, +class LWO3Parser { + constructor(IFFParser) { + this.IFF = IFFParser + } - parseBlock: function () { + parseBlock() { this.IFF.debugger.offset = this.IFF.reader.offset this.IFF.debugger.closeForms() - var blockID = this.IFF.reader.getIDTag() - var length = this.IFF.reader.getUint32() // size of data in bytes + const blockID = this.IFF.reader.getIDTag() + const length = this.IFF.reader.getUint32() // size of data in bytes this.IFF.debugger.dataOffset = this.IFF.reader.offset this.IFF.debugger.length = length @@ -33,8 +31,8 @@ LWO3Parser.prototype = { case 'NORM': // ENVL FORM skipped - case 'PRE ': - case 'POST': + case 'PRE ': // Pre-loop behavior for the keyframe + case 'POST': // Post-loop behavior for the keyframe case 'KEY ': case 'SPAN': @@ -195,14 +193,11 @@ LWO3Parser.prototype = { // Image Map Layer case 'WRAP': - this.IFF.currentForm.wrap = { - w: this.IFF.reader.getUint16(), - h: this.IFF.reader.getUint16(), - } + this.IFF.currentForm.wrap = { w: this.IFF.reader.getUint16(), h: this.IFF.reader.getUint16() } break case 'IMAG': - var index = this.IFF.reader.getVariableLengthIndex() + const index = this.IFF.reader.getVariableLengthIndex() this.IFF.currentForm.imageIndex = index break @@ -270,50 +265,38 @@ LWO3Parser.prototype = { // LWO2 Spec chunks: these are needed since the SURF FORMs are often in LWO2 format case 'SMAN': - var maxSmoothingAngle = this.IFF.reader.getFloat32() + const maxSmoothingAngle = this.IFF.reader.getFloat32() this.IFF.currentSurface.attributes.smooth = maxSmoothingAngle < 0 ? false : true break // LWO2: Basic Surface Parameters case 'COLR': - this.IFF.currentSurface.attributes.Color = { - value: this.IFF.reader.getFloat32Array(3), - } + this.IFF.currentSurface.attributes.Color = { value: this.IFF.reader.getFloat32Array(3) } this.IFF.reader.skip(2) // VX: envelope break case 'LUMI': - this.IFF.currentSurface.attributes.Luminosity = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Luminosity = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'SPEC': - this.IFF.currentSurface.attributes.Specular = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Specular = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'DIFF': - this.IFF.currentSurface.attributes.Diffuse = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Diffuse = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'REFL': - this.IFF.currentSurface.attributes.Reflection = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Reflection = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break case 'GLOS': - this.IFF.currentSurface.attributes.Glossiness = { - value: this.IFF.reader.getFloat32(), - } + this.IFF.currentSurface.attributes.Glossiness = { value: this.IFF.reader.getFloat32() } this.IFF.reader.skip(2) break @@ -372,7 +355,7 @@ LWO3Parser.prototype = { if (this.IFF.reader.offset >= this.IFF.currentFormEnd) { this.IFF.currentForm = this.IFF.parentForm } - }, + } } export { LWO3Parser } diff --git a/src/math/Capsule.d.ts b/src/math/Capsule.d.ts index 3501ef4b..885a8c69 100644 --- a/src/math/Capsule.d.ts +++ b/src/math/Capsule.d.ts @@ -9,7 +9,7 @@ export class Capsule { set(start: Vector3, end: Vector3, radius: number): this clone(): Capsule copy(capsule: Capsule): this - getCenter(target: number): Vector3 + getCenter(target: Vector3): Vector3 translate(v: Vector3): this checkAABBAxis( p1x: number, diff --git a/src/math/Capsule.js b/src/math/Capsule.js index 7a0bad18..63a0c956 100644 --- a/src/math/Capsule.js +++ b/src/math/Capsule.js @@ -1,133 +1,128 @@ import { Vector3 } from 'three' -var Capsule = (function () { - var _v1 = new Vector3() - var _v2 = new Vector3() - var _v3 = new Vector3() +const _v1 = /* @__PURE__ */ new Vector3() +const _v2 = /* @__PURE__ */ new Vector3() +const _v3 = /* @__PURE__ */ new Vector3() - var EPS = 1e-10 +const EPS = 1e-10 - function Capsule(start, end, radius) { - this.start = start == undefined ? new Vector3(0, 0, 0) : start - this.end = end == undefined ? new Vector3(0, 1, 0) : end - this.radius = radius == undefined ? 1 : radius +class Capsule { + constructor(start = new Vector3(0, 0, 0), end = new Vector3(0, 1, 0), radius = 1) { + this.start = start + this.end = end + this.radius = radius } - Object.assign(Capsule.prototype, { - clone: function () { - return new Capsule(this.start.clone(), this.end.clone(), this.radius) - }, - - set: function (start, end, radius) { - this.start.copy(start) - this.end.copy(end) - this.radius = radius - }, - - copy: function (capsule) { - this.start.copy(capsule.start) - this.end.copy(capsule.end) - this.radius = capsule.radius - }, - - getCenter: function (target) { - return target.copy(this.end).add(this.start).multiplyScalar(0.5) - }, - - translate: function (v) { - this.start.add(v) - this.end.add(v) - }, - - checkAABBAxis: function (p1x, p1y, p2x, p2y, minx, maxx, miny, maxy, radius) { - return ( - (minx - p1x < radius || minx - p2x < radius) && - (p1x - maxx < radius || p2x - maxx < radius) && - (miny - p1y < radius || miny - p2y < radius) && - (p1y - maxy < radius || p2y - maxy < radius) - ) - }, - - intersectsBox: function (box) { - return ( - this.checkAABBAxis( - this.start.x, - this.start.y, - this.end.x, - this.end.y, - box.min.x, - box.max.x, - box.min.y, - box.max.y, - this.radius, - ) && - this.checkAABBAxis( - this.start.x, - this.start.z, - this.end.x, - this.end.z, - box.min.x, - box.max.x, - box.min.z, - box.max.z, - this.radius, - ) && - this.checkAABBAxis( - this.start.y, - this.start.z, - this.end.y, - this.end.z, - box.min.y, - box.max.y, - box.min.z, - box.max.z, - this.radius, - ) + clone() { + return new Capsule(this.start.clone(), this.end.clone(), this.radius) + } + + set(start, end, radius) { + this.start.copy(start) + this.end.copy(end) + this.radius = radius + } + + copy(capsule) { + this.start.copy(capsule.start) + this.end.copy(capsule.end) + this.radius = capsule.radius + } + + getCenter(target) { + return target.copy(this.end).add(this.start).multiplyScalar(0.5) + } + + translate(v) { + this.start.add(v) + this.end.add(v) + } + + checkAABBAxis(p1x, p1y, p2x, p2y, minx, maxx, miny, maxy, radius) { + return ( + (minx - p1x < radius || minx - p2x < radius) && + (p1x - maxx < radius || p2x - maxx < radius) && + (miny - p1y < radius || miny - p2y < radius) && + (p1y - maxy < radius || p2y - maxy < radius) + ) + } + + intersectsBox(box) { + return ( + this.checkAABBAxis( + this.start.x, + this.start.y, + this.end.x, + this.end.y, + box.min.x, + box.max.x, + box.min.y, + box.max.y, + this.radius, + ) && + this.checkAABBAxis( + this.start.x, + this.start.z, + this.end.x, + this.end.z, + box.min.x, + box.max.x, + box.min.z, + box.max.z, + this.radius, + ) && + this.checkAABBAxis( + this.start.y, + this.start.z, + this.end.y, + this.end.z, + box.min.y, + box.max.y, + box.min.z, + box.max.z, + this.radius, ) - }, - - lineLineMinimumPoints: function (line1, line2) { - var r = _v1.copy(line1.end).sub(line1.start) - var s = _v2.copy(line2.end).sub(line2.start) - var w = _v3.copy(line2.start).sub(line1.start) - - var a = r.dot(s), - b = r.dot(r), - c = s.dot(s), - d = s.dot(w), - e = r.dot(w) - - var t1, - t2, - divisor = b * c - a * a - - if (Math.abs(divisor) < EPS) { - var d1 = -d / c - var d2 = (a - d) / c - - if (Math.abs(d1 - 0.5) < Math.abs(d2 - 0.5)) { - t1 = 0 - t2 = d1 - } else { - t1 = 1 - t2 = d2 - } + ) + } + + lineLineMinimumPoints(line1, line2) { + const r = _v1.copy(line1.end).sub(line1.start) + const s = _v2.copy(line2.end).sub(line2.start) + const w = _v3.copy(line2.start).sub(line1.start) + + const a = r.dot(s), + b = r.dot(r), + c = s.dot(s), + d = s.dot(w), + e = r.dot(w) + + let t1, t2 + const divisor = b * c - a * a + + if (Math.abs(divisor) < EPS) { + const d1 = -d / c + const d2 = (a - d) / c + + if (Math.abs(d1 - 0.5) < Math.abs(d2 - 0.5)) { + t1 = 0 + t2 = d1 } else { - t1 = (d * a + e * c) / divisor - t2 = (t1 * a - d) / c + t1 = 1 + t2 = d2 } + } else { + t1 = (d * a + e * c) / divisor + t2 = (t1 * a - d) / c + } - t2 = Math.max(0, Math.min(1, t2)) - t1 = Math.max(0, Math.min(1, t1)) + t2 = Math.max(0, Math.min(1, t2)) + t1 = Math.max(0, Math.min(1, t1)) - var point1 = r.multiplyScalar(t1).add(line1.start) - var point2 = s.multiplyScalar(t2).add(line2.start) + const point1 = r.multiplyScalar(t1).add(line1.start) + const point2 = s.multiplyScalar(t2).add(line2.start) - return [point1, point2] - }, - }) - - return Capsule -})() + return [point1, point2] + } +} export { Capsule } diff --git a/src/math/ColorConverter.js b/src/math/ColorConverter.js index 6c0317e2..714a2c98 100644 --- a/src/math/ColorConverter.js +++ b/src/math/ColorConverter.js @@ -1,7 +1,9 @@ import { MathUtils } from 'three' -var ColorConverter = { - setHSV: function (color, h, s, v) { +const _hsl = {} + +class ColorConverter { + static setHSV(color, h, s, v) { // https://gist.github.com/xpansive/1337890#file-index-js h = MathUtils.euclideanModulo(h, 1) @@ -9,54 +11,40 @@ var ColorConverter = { v = MathUtils.clamp(v, 0, 1) return color.setHSL(h, (s * v) / ((h = (2 - s) * v) < 1 ? h : 2 - h), h * 0.5) - }, - - getHSV: (function () { - var hsl = {} - - return function getHSV(color, target) { - if (target === undefined) { - console.warn('THREE.ColorConverter: .getHSV() target is now required') - target = { h: 0, s: 0, l: 0 } - } + } - color.getHSL(hsl) + static getHSV(color, target) { + color.getHSL(_hsl) - // based on https://gist.github.com/xpansive/1337890#file-index-js - hsl.s *= hsl.l < 0.5 ? hsl.l : 1 - hsl.l + // based on https://gist.github.com/xpansive/1337890#file-index-js + _hsl.s *= _hsl.l < 0.5 ? _hsl.l : 1 - _hsl.l - target.h = hsl.h - target.s = (2 * hsl.s) / (hsl.l + hsl.s) - target.v = hsl.l + hsl.s + target.h = _hsl.h + target.s = (2 * _hsl.s) / (_hsl.l + _hsl.s) + target.v = _hsl.l + _hsl.s - return target - } - })(), + return target + } // where c, m, y, k is between 0 and 1 - setCMYK: function (color, c, m, y, k) { - var r = (1 - c) * (1 - k) - var g = (1 - m) * (1 - k) - var b = (1 - y) * (1 - k) + static setCMYK(color, c, m, y, k) { + const r = (1 - c) * (1 - k) + const g = (1 - m) * (1 - k) + const b = (1 - y) * (1 - k) return color.setRGB(r, g, b) - }, - - getCMYK: function (color, target) { - if (target === undefined) { - console.warn('THREE.ColorConverter: .getCMYK() target is now required') - target = { c: 0, m: 0, y: 0, k: 0 } - } + } - var r = color.r - var g = color.g - var b = color.b + static getCMYK(color, target) { + const r = color.r + const g = color.g + const b = color.b - var k = 1 - Math.max(r, g, b) - var c = (1 - r - k) / (1 - k) - var m = (1 - g - k) / (1 - k) - var y = (1 - b - k) / (1 - k) + const k = 1 - Math.max(r, g, b) + const c = (1 - r - k) / (1 - k) + const m = (1 - g - k) / (1 - k) + const y = (1 - b - k) / (1 - k) target.c = c target.m = m @@ -64,7 +52,7 @@ var ColorConverter = { target.k = k return target - }, + } } export { ColorConverter } diff --git a/src/math/ConvexHull.js b/src/math/ConvexHull.js index 2ce04d27..d7cad8c6 100644 --- a/src/math/ConvexHull.js +++ b/src/math/ConvexHull.js @@ -7,11 +7,11 @@ import { Line3, Plane, Triangle, Vector3 } from 'three' const Visible = 0 const Deleted = 1 -const _v1 = new Vector3() -const _line3 = new Line3() -const _plane = new Plane() -const _closestPoint = new Vector3() -const _triangle = new Triangle() +const _v1 = /* @__PURE__ */ new Vector3() +const _line3 = /* @__PURE__ */ new Line3() +const _plane = /* @__PURE__ */ new Plane() +const _closestPoint = /* @__PURE__ */ new Vector3() +const _triangle = /* @__PURE__ */ new Triangle() class ConvexHull { constructor() { @@ -61,20 +61,15 @@ class ConvexHull { const geometry = node.geometry if (geometry !== undefined) { - if (geometry.isGeometry) { - console.error('THREE.ConvexHull no longer supports Geometry. Use THREE.BufferGeometry instead.') - return - } else if (geometry.isBufferGeometry) { - const attribute = geometry.attributes.position + const attribute = geometry.attributes.position - if (attribute !== undefined) { - for (let i = 0, l = attribute.count; i < l; i++) { - const point = new Vector3() + if (attribute !== undefined) { + for (let i = 0, l = attribute.count; i < l; i++) { + const point = new Vector3() - point.fromBufferAttribute(attribute, i).applyMatrix4(node.matrixWorld) + point.fromBufferAttribute(attribute, i).applyMatrix4(node.matrixWorld) - points.push(point) - } + points.push(point) } } } @@ -98,7 +93,7 @@ class ConvexHull { } intersectRay(ray, target) { - // based on "Fast Ray-Convex Polyhedron Intersection" by Eric Haines, GRAPHICS GEMS II + // based on "Fast Ray-Convex Polyhedron Intersection" by Eric Haines, GRAPHICS GEMS II const faces = this.faces @@ -130,7 +125,7 @@ class ConvexHull { // now categorized plane as front-facing or back-facing if (vD > 0) { - // plane faces away from the ray, so this plane is a back-face + // plane faces away from the ray, so this plane is a back-face tFar = Math.min(t, tFar) } else { @@ -208,7 +203,7 @@ class ConvexHull { return this } - // Removes all the visible vertices that a given face is able to see which are stored in the 'assigned' vertext list + // Removes all the visible vertices that a given face is able to see which are stored in the 'assigned' vertex list removeAllVerticesFromFace(face) { if (face.outside !== null) { @@ -973,4 +968,4 @@ class VertexList { } } -export { ConvexHull } +export { ConvexHull, Face, HalfEdge, VertexNode, VertexList } diff --git a/src/math/ImprovedNoise.js b/src/math/ImprovedNoise.js index f15b4428..f0d6981a 100644 --- a/src/math/ImprovedNoise.js +++ b/src/math/ImprovedNoise.js @@ -1,327 +1,84 @@ -// http://mrl.nyu.edu/~perlin/noise/ +// https://cs.nyu.edu/~perlin/noise/ -var ImprovedNoise = function () { - var p = [ - 151, - 160, - 137, - 91, - 90, - 15, - 131, - 13, - 201, - 95, - 96, - 53, - 194, - 233, - 7, - 225, - 140, - 36, - 103, - 30, - 69, - 142, - 8, - 99, - 37, - 240, - 21, - 10, - 23, - 190, - 6, - 148, - 247, - 120, - 234, - 75, - 0, - 26, - 197, - 62, - 94, - 252, - 219, - 203, - 117, - 35, - 11, - 32, - 57, - 177, - 33, - 88, - 237, - 149, - 56, - 87, - 174, - 20, - 125, - 136, - 171, - 168, - 68, - 175, - 74, - 165, - 71, - 134, - 139, - 48, - 27, - 166, - 77, - 146, - 158, - 231, - 83, - 111, - 229, - 122, - 60, - 211, - 133, - 230, - 220, - 105, - 92, - 41, - 55, - 46, - 245, - 40, - 244, - 102, - 143, - 54, - 65, - 25, - 63, - 161, - 1, - 216, - 80, - 73, - 209, - 76, - 132, - 187, - 208, - 89, - 18, - 169, - 200, - 196, - 135, - 130, - 116, - 188, - 159, - 86, - 164, - 100, - 109, - 198, - 173, - 186, - 3, - 64, - 52, - 217, - 226, - 250, - 124, - 123, - 5, - 202, - 38, - 147, - 118, - 126, - 255, - 82, - 85, - 212, - 207, - 206, - 59, - 227, - 47, - 16, - 58, - 17, - 182, - 189, - 28, - 42, - 223, - 183, - 170, - 213, - 119, - 248, - 152, - 2, - 44, - 154, - 163, - 70, - 221, - 153, - 101, - 155, - 167, - 43, - 172, - 9, - 129, - 22, - 39, - 253, - 19, - 98, - 108, - 110, - 79, - 113, - 224, - 232, - 178, - 185, - 112, - 104, - 218, - 246, - 97, - 228, - 251, - 34, - 242, - 193, - 238, - 210, - 144, - 12, - 191, - 179, - 162, - 241, - 81, - 51, - 145, - 235, - 249, - 14, - 239, - 107, - 49, - 192, - 214, - 31, - 181, - 199, - 106, - 157, - 184, - 84, - 204, - 176, - 115, - 121, - 50, - 45, - 127, - 4, - 150, - 254, - 138, - 236, - 205, - 93, - 222, - 114, - 67, - 29, - 24, - 72, - 243, - 141, - 128, - 195, - 78, - 66, - 215, - 61, - 156, - 180, - ] +function init() { + // prettier-ignore + const _p = [ 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, + 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, + 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, + 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, + 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, 5, + 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, + 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, + 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, + 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 ]; for (let i = 0; i < 256; i++) { - p[256 + i] = p[i] + _p[256 + i] = _p[i] } - function fade(t) { - return t * t * t * (t * (t * 6 - 15) + 10) - } + return _p +} - function lerp(t, a, b) { - return a + t * (b - a) - } +const _p = /* @__PURE__ */ init() - function grad(hash, x, y, z) { - var h = hash & 15 - var u = h < 8 ? x : y, - v = h < 4 ? y : h == 12 || h == 14 ? x : z - return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v) - } +function fade(t) { + return t * t * t * (t * (t * 6 - 15) + 10) +} + +function lerp(t, a, b) { + return a + t * (b - a) +} + +function grad(hash, x, y, z) { + const h = hash & 15 + const u = h < 8 ? x : y, + v = h < 4 ? y : h == 12 || h == 14 ? x : z + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v) +} - return { - noise: function (x, y, z) { - var floorX = Math.floor(x), - floorY = Math.floor(y), - floorZ = Math.floor(z) +class ImprovedNoise { + noise(x, y, z) { + const floorX = Math.floor(x), + floorY = Math.floor(y), + floorZ = Math.floor(z) - var X = floorX & 255, - Y = floorY & 255, - Z = floorZ & 255 + const X = floorX & 255, + Y = floorY & 255, + Z = floorZ & 255 - x -= floorX - y -= floorY - z -= floorZ + x -= floorX + y -= floorY + z -= floorZ - var xMinus1 = x - 1, - yMinus1 = y - 1, - zMinus1 = z - 1 + const xMinus1 = x - 1, + yMinus1 = y - 1, + zMinus1 = z - 1 - var u = fade(x), - v = fade(y), - w = fade(z) + const u = fade(x), + v = fade(y), + w = fade(z) - var A = p[X] + Y, - AA = p[A] + Z, - AB = p[A + 1] + Z, - B = p[X + 1] + Y, - BA = p[B] + Z, - BB = p[B + 1] + Z + const A = _p[X] + Y, + AA = _p[A] + Z, + AB = _p[A + 1] + Z, + B = _p[X + 1] + Y, + BA = _p[B] + Z, + BB = _p[B + 1] + Z - return lerp( - w, - lerp( - v, - lerp(u, grad(p[AA], x, y, z), grad(p[BA], xMinus1, y, z)), - lerp(u, grad(p[AB], x, yMinus1, z), grad(p[BB], xMinus1, yMinus1, z)), - ), - lerp( - v, - lerp(u, grad(p[AA + 1], x, y, zMinus1), grad(p[BA + 1], xMinus1, y, zMinus1)), - lerp(u, grad(p[AB + 1], x, yMinus1, zMinus1), grad(p[BB + 1], xMinus1, yMinus1, zMinus1)), - ), - ) - }, + return lerp( + w, + lerp( + v, + lerp(u, grad(_p[AA], x, y, z), grad(_p[BA], xMinus1, y, z)), + lerp(u, grad(_p[AB], x, yMinus1, z), grad(_p[BB], xMinus1, yMinus1, z)), + ), + lerp( + v, + lerp(u, grad(_p[AA + 1], x, y, zMinus1), grad(_p[BA + 1], xMinus1, y, zMinus1)), + lerp(u, grad(_p[AB + 1], x, yMinus1, zMinus1), grad(_p[BB + 1], xMinus1, yMinus1, zMinus1)), + ), + ) } } diff --git a/src/math/Lut.js b/src/math/Lut.js index 9a45f2b0..a1eadbfa 100644 --- a/src/math/Lut.js +++ b/src/math/Lut.js @@ -1,67 +1,80 @@ -import { Color } from 'three' +import { Color, MathUtils } from 'three' -var Lut = function (colormap, numberofcolors) { - this.lut = [] - this.setColorMap(colormap, numberofcolors) - return this -} +class Lut { + constructor(colormap, count = 32) { + this.isLut = true -Lut.prototype = { - constructor: Lut, + this.lut = [] + this.map = [] + this.n = 0 + this.minV = 0 + this.maxV = 1 - lut: [], - map: [], - n: 256, - minV: 0, - maxV: 1, + this.setColorMap(colormap, count) + } - set: function (value) { - if (value instanceof Lut) { + set(value) { + if (value.isLut === true) { this.copy(value) } return this - }, + } - setMin: function (min) { + setMin(min) { this.minV = min return this - }, + } - setMax: function (max) { + setMax(max) { this.maxV = max return this - }, + } - setColorMap: function (colormap, numberofcolors) { + setColorMap(colormap, count = 32) { this.map = ColorMapKeywords[colormap] || ColorMapKeywords.rainbow - this.n = numberofcolors || 32 + this.n = count - var step = 1.0 / this.n + const step = 1.0 / this.n + const minColor = new Color() + const maxColor = new Color() this.lut.length = 0 - for (let i = 0; i <= 1; i += step) { + + // sample at 0 + + this.lut.push(new Color(this.map[0][1])) + + // sample at 1/n, ..., (n-1)/n + + for (let i = 1; i < count; i++) { + const alpha = i * step + for (let j = 0; j < this.map.length - 1; j++) { - if (i >= this.map[j][0] && i < this.map[j + 1][0]) { - var min = this.map[j][0] - var max = this.map[j + 1][0] + if (alpha > this.map[j][0] && alpha <= this.map[j + 1][0]) { + const min = this.map[j][0] + const max = this.map[j + 1][0] - var minColor = new Color(this.map[j][1]) - var maxColor = new Color(this.map[j + 1][1]) + minColor.setHex(this.map[j][1], 'linear-srgb') + maxColor.setHex(this.map[j + 1][1], 'linear-srgb') - var color = minColor.lerp(maxColor, (i - min) / (max - min)) + const color = new Color().lerpColors(minColor, maxColor, (alpha - min) / (max - min)) this.lut.push(color) } } } + // sample at 1 + + this.lut.push(new Color(this.map[this.map.length - 1][1])) + return this - }, + } - copy: function (lut) { + copy(lut) { this.lut = lut.lut this.map = lut.map this.n = lut.n @@ -69,62 +82,63 @@ Lut.prototype = { this.maxV = lut.maxV return this - }, + } - getColor: function (alpha) { - if (alpha <= this.minV) { - alpha = this.minV - } else if (alpha >= this.maxV) { - alpha = this.maxV - } + getColor(alpha) { + alpha = MathUtils.clamp(alpha, this.minV, this.maxV) alpha = (alpha - this.minV) / (this.maxV - this.minV) - var colorPosition = Math.round(alpha * this.n) - colorPosition == this.n ? (colorPosition -= 1) : colorPosition + const colorPosition = Math.round(alpha * this.n) return this.lut[colorPosition] - }, + } - addColorMap: function (colormapName, arrayOfColors) { - ColorMapKeywords[colormapName] = arrayOfColors - }, + addColorMap(name, arrayOfColors) { + ColorMapKeywords[name] = arrayOfColors - createCanvas: function () { - var canvas = document.createElement('canvas') + return this + } + + createCanvas() { + const canvas = document.createElement('canvas') canvas.width = 1 canvas.height = this.n this.updateCanvas(canvas) return canvas - }, + } + + updateCanvas(canvas) { + const ctx = canvas.getContext('2d', { alpha: false }) - updateCanvas: function (canvas) { - var ctx = canvas.getContext('2d', { alpha: false }) + const imageData = ctx.getImageData(0, 0, 1, this.n) - var imageData = ctx.getImageData(0, 0, 1, this.n) + const data = imageData.data - var data = imageData.data + let k = 0 - var k = 0 + const step = 1.0 / this.n - var step = 1.0 / this.n + const minColor = new Color() + const maxColor = new Color() + const finalColor = new Color() for (let i = 1; i >= 0; i -= step) { for (let j = this.map.length - 1; j >= 0; j--) { if (i < this.map[j][0] && i >= this.map[j - 1][0]) { - var min = this.map[j - 1][0] - var max = this.map[j][0] + const min = this.map[j - 1][0] + const max = this.map[j][0] - var minColor = new Color(this.map[j - 1][1]) - var maxColor = new Color(this.map[j][1]) + minColor.setHex(this.map[j - 1][1], 'linear-srgb') + maxColor.setHex(this.map[j][1], 'linear-srgb') - var color = minColor.lerp(maxColor, (i - min) / (max - min)) + finalColor.lerpColors(minColor, maxColor, (i - min) / (max - min)) - data[k * 4] = Math.round(color.r * 255) - data[k * 4 + 1] = Math.round(color.g * 255) - data[k * 4 + 2] = Math.round(color.b * 255) + data[k * 4] = Math.round(finalColor.r * 255) + data[k * 4 + 1] = Math.round(finalColor.g * 255) + data[k * 4 + 2] = Math.round(finalColor.b * 255) data[k * 4 + 3] = 255 k += 1 @@ -135,10 +149,10 @@ Lut.prototype = { ctx.putImageData(imageData, 0, 0) return canvas - }, + } } -var ColorMapKeywords = { +const ColorMapKeywords = { rainbow: [ [0.0, 0x0000ff], [0.2, 0x00ffff], diff --git a/src/math/MeshSurfaceSampler.d.ts b/src/math/MeshSurfaceSampler.d.ts index 658500eb..2590d103 100644 --- a/src/math/MeshSurfaceSampler.d.ts +++ b/src/math/MeshSurfaceSampler.d.ts @@ -5,11 +5,14 @@ export class MeshSurfaceSampler { geometry: BufferGeometry positionAttribute: Float32Array weightAttribute: string | null + randomFunction: () => number + setRandomGenerator(randomFunction: () => number): this constructor(mesh: Mesh) binarySearch(x: number): number build(): this sample(targetPosition: Vector3, targetNormal?: Vector3, targetColor?: Color): this sampleFace(faceIndex: number, targetPosition: Vector3, targetNormal?: Vector3, targetColor?: Color): this + sampleFaceIndex(): number setWeightAttribute(name: string | null): this } diff --git a/src/math/MeshSurfaceSampler.js b/src/math/MeshSurfaceSampler.js index ce606b4f..51778207 100644 --- a/src/math/MeshSurfaceSampler.js +++ b/src/math/MeshSurfaceSampler.js @@ -10,16 +10,13 @@ import { Triangle, Vector3 } from 'three' * - http://www.joesfer.com/?p=84 * - https://stackoverflow.com/a/4322940/1314762 */ -var MeshSurfaceSampler = (function () { - var _face = new Triangle() - var _color = new Vector3() - function MeshSurfaceSampler(mesh) { - var geometry = mesh.geometry +const _face = /* @__PURE__ */ new Triangle() +const _color = /* @__PURE__ */ new Vector3() - if (!geometry.isBufferGeometry || geometry.attributes.position.itemSize !== 3) { - throw new Error('THREE.MeshSurfaceSampler: Requires BufferGeometry triangle mesh.') - } +class MeshSurfaceSampler { + constructor(mesh) { + let geometry = mesh.geometry if (geometry.index) { console.warn('THREE.MeshSurfaceSampler: Converting geometry to non-indexed BufferGeometry.') @@ -37,135 +34,131 @@ var MeshSurfaceSampler = (function () { this.distribution = null } - MeshSurfaceSampler.prototype = { - constructor: MeshSurfaceSampler, + setWeightAttribute(name) { + this.weightAttribute = name ? this.geometry.getAttribute(name) : null - setWeightAttribute: function (name) { - this.weightAttribute = name ? this.geometry.getAttribute(name) : null + return this + } - return this - }, + build() { + const positionAttribute = this.positionAttribute + const weightAttribute = this.weightAttribute - build: function () { - var positionAttribute = this.positionAttribute - var weightAttribute = this.weightAttribute + const faceWeights = new Float32Array(positionAttribute.count / 3) - var faceWeights = new Float32Array(positionAttribute.count / 3) + // Accumulate weights for each mesh face. - // Accumulate weights for each mesh face. + for (let i = 0; i < positionAttribute.count; i += 3) { + let faceWeight = 1 - for (let i = 0; i < positionAttribute.count; i += 3) { - var faceWeight = 1 + if (weightAttribute) { + faceWeight = weightAttribute.getX(i) + weightAttribute.getX(i + 1) + weightAttribute.getX(i + 2) + } - if (weightAttribute) { - faceWeight = weightAttribute.getX(i) + weightAttribute.getX(i + 1) + weightAttribute.getX(i + 2) - } + _face.a.fromBufferAttribute(positionAttribute, i) + _face.b.fromBufferAttribute(positionAttribute, i + 1) + _face.c.fromBufferAttribute(positionAttribute, i + 2) + faceWeight *= _face.getArea() - _face.a.fromBufferAttribute(positionAttribute, i) - _face.b.fromBufferAttribute(positionAttribute, i + 1) - _face.c.fromBufferAttribute(positionAttribute, i + 2) - faceWeight *= _face.getArea() + faceWeights[i / 3] = faceWeight + } - faceWeights[i / 3] = faceWeight - } + // Store cumulative total face weights in an array, where weight index + // corresponds to face index. - // Store cumulative total face weights in an array, where weight index - // corresponds to face index. + this.distribution = new Float32Array(positionAttribute.count / 3) - this.distribution = new Float32Array(positionAttribute.count / 3) + let cumulativeTotal = 0 - var cumulativeTotal = 0 + for (let i = 0; i < faceWeights.length; i++) { + cumulativeTotal += faceWeights[i] - for (let i = 0; i < faceWeights.length; i++) { - cumulativeTotal += faceWeights[i] + this.distribution[i] = cumulativeTotal + } - this.distribution[i] = cumulativeTotal - } + return this + } + + setRandomGenerator(randomFunction) { + this.randomFunction = randomFunction + return this + } - return this - }, + sample(targetPosition, targetNormal, targetColor) { + const faceIndex = this.sampleFaceIndex() + return this.sampleFace(faceIndex, targetPosition, targetNormal, targetColor) + } - setRandomGenerator: function (randomFunction) { - this.randomFunction = randomFunction - return this - }, + sampleFaceIndex() { + const cumulativeTotal = this.distribution[this.distribution.length - 1] + return this.binarySearch(this.randomFunction() * cumulativeTotal) + } - sample: function (targetPosition, targetNormal, targetColor) { - var cumulativeTotal = this.distribution[this.distribution.length - 1] + binarySearch(x) { + const dist = this.distribution + let start = 0 + let end = dist.length - 1 - var faceIndex = this.binarySearch(this.randomFunction() * cumulativeTotal) + let index = -1 - return this.sampleFace(faceIndex, targetPosition, targetNormal, targetColor) - }, + while (start <= end) { + const mid = Math.ceil((start + end) / 2) - binarySearch: function (x) { - var dist = this.distribution - var start = 0 - var end = dist.length - 1 + if (mid === 0 || (dist[mid - 1] <= x && dist[mid] > x)) { + index = mid - var index = -1 + break + } else if (x < dist[mid]) { + end = mid - 1 + } else { + start = mid + 1 + } + } - while (start <= end) { - var mid = Math.ceil((start + end) / 2) + return index + } - if (mid === 0 || (dist[mid - 1] <= x && dist[mid] > x)) { - index = mid + sampleFace(faceIndex, targetPosition, targetNormal, targetColor) { + let u = this.randomFunction() + let v = this.randomFunction() - break - } else if (x < dist[mid]) { - end = mid - 1 - } else { - start = mid + 1 - } - } + if (u + v > 1) { + u = 1 - u + v = 1 - v + } - return index - }, + _face.a.fromBufferAttribute(this.positionAttribute, faceIndex * 3) + _face.b.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 1) + _face.c.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 2) - sampleFace: function (faceIndex, targetPosition, targetNormal, targetColor) { - var u = this.randomFunction() - var v = this.randomFunction() + targetPosition + .set(0, 0, 0) + .addScaledVector(_face.a, u) + .addScaledVector(_face.b, v) + .addScaledVector(_face.c, 1 - (u + v)) - if (u + v > 1) { - u = 1 - u - v = 1 - v - } + if (targetNormal !== undefined) { + _face.getNormal(targetNormal) + } - _face.a.fromBufferAttribute(this.positionAttribute, faceIndex * 3) - _face.b.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 1) - _face.c.fromBufferAttribute(this.positionAttribute, faceIndex * 3 + 2) + if (targetColor !== undefined && this.colorAttribute !== undefined) { + _face.a.fromBufferAttribute(this.colorAttribute, faceIndex * 3) + _face.b.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 1) + _face.c.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 2) - targetPosition + _color .set(0, 0, 0) .addScaledVector(_face.a, u) .addScaledVector(_face.b, v) .addScaledVector(_face.c, 1 - (u + v)) - if (targetNormal !== undefined) { - _face.getNormal(targetNormal) - } - - if (targetColor !== undefined && this.colorAttribute !== undefined) { - _face.a.fromBufferAttribute(this.colorAttribute, faceIndex * 3) - _face.b.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 1) - _face.c.fromBufferAttribute(this.colorAttribute, faceIndex * 3 + 2) - - _color - .set(0, 0, 0) - .addScaledVector(_face.a, u) - .addScaledVector(_face.b, v) - .addScaledVector(_face.c, 1 - (u + v)) - - targetColor.r = _color.x - targetColor.g = _color.y - targetColor.b = _color.z - } + targetColor.r = _color.x + targetColor.g = _color.y + targetColor.b = _color.z + } - return this - }, + return this } - - return MeshSurfaceSampler -})() +} export { MeshSurfaceSampler } diff --git a/src/math/OBB.js b/src/math/OBB.js index b2414c0a..b82b7514 100644 --- a/src/math/OBB.js +++ b/src/math/OBB.js @@ -2,73 +2,73 @@ import { Box3, MathUtils, Matrix4, Matrix3, Ray, Vector3 } from 'three' // module scope helper variables -var a = { +const a = { c: null, // center - u: [new Vector3(), new Vector3(), new Vector3()], // basis vectors + u: [/* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3()], // basis vectors e: [], // half width } -var b = { +const b = { c: null, // center - u: [new Vector3(), new Vector3(), new Vector3()], // basis vectors + u: [/* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3(), /* @__PURE__ */ new Vector3()], // basis vectors e: [], // half width } -var R = [[], [], []] -var AbsR = [[], [], []] -var t = [] - -var xAxis = new Vector3() -var yAxis = new Vector3() -var zAxis = new Vector3() -var v1 = new Vector3() -var size = new Vector3() -var closestPoint = new Vector3() -var rotationMatrix = new Matrix3() -var aabb = new Box3() -var matrix = new Matrix4() -var inverse = new Matrix4() -var localRay = new Ray() +const R = [[], [], []] +const AbsR = [[], [], []] +const t = [] + +const xAxis = /* @__PURE__ */ new Vector3() +const yAxis = /* @__PURE__ */ new Vector3() +const zAxis = /* @__PURE__ */ new Vector3() +const v1 = /* @__PURE__ */ new Vector3() +const size = /* @__PURE__ */ new Vector3() +const closestPoint = /* @__PURE__ */ new Vector3() +const rotationMatrix = /* @__PURE__ */ new Matrix3() +const aabb = /* @__PURE__ */ new Box3() +const matrix = /* @__PURE__ */ new Matrix4() +const inverse = /* @__PURE__ */ new Matrix4() +const localRay = /* @__PURE__ */ new Ray() // OBB -function OBB(center = new Vector3(), halfSize = new Vector3(), rotation = new Matrix3()) { - this.center = center - this.halfSize = halfSize - this.rotation = rotation -} +class OBB { + constructor(center = new Vector3(), halfSize = new Vector3(), rotation = new Matrix3()) { + this.center = center + this.halfSize = halfSize + this.rotation = rotation + } -Object.assign(OBB.prototype, { - set: function (center, halfSize, rotation) { + set(center, halfSize, rotation) { this.center = center this.halfSize = halfSize this.rotation = rotation return this - }, + } - copy: function (obb) { + copy(obb) { this.center.copy(obb.center) this.halfSize.copy(obb.halfSize) this.rotation.copy(obb.rotation) return this - }, + } - clone: function () { + clone() { return new this.constructor().copy(this) - }, + } - getSize: function (result) { + getSize(result) { return result.copy(this.halfSize).multiplyScalar(2) - }, + } /** * Reference: Closest Point on OBB to Point in Real-Time Collision Detection * by Christer Ericson (chapter 5.1.4) */ - clampPoint: function (point, result) { - var halfSize = this.halfSize + clampPoint(point, result) { + const halfSize = this.halfSize v1.subVectors(point, this.center) this.rotation.extractBasis(xAxis, yAxis, zAxis) @@ -79,19 +79,19 @@ Object.assign(OBB.prototype, { // project the target onto the OBB axes and walk towards that point - var x = MathUtils.clamp(v1.dot(xAxis), -halfSize.x, halfSize.x) + const x = MathUtils.clamp(v1.dot(xAxis), -halfSize.x, halfSize.x) result.add(xAxis.multiplyScalar(x)) - var y = MathUtils.clamp(v1.dot(yAxis), -halfSize.y, halfSize.y) + const y = MathUtils.clamp(v1.dot(yAxis), -halfSize.y, halfSize.y) result.add(yAxis.multiplyScalar(y)) - var z = MathUtils.clamp(v1.dot(zAxis), -halfSize.z, halfSize.z) + const z = MathUtils.clamp(v1.dot(zAxis), -halfSize.z, halfSize.z) result.add(zAxis.multiplyScalar(z)) return result - }, + } - containsPoint: function (point) { + containsPoint(point) { v1.subVectors(point, this.center) this.rotation.extractBasis(xAxis, yAxis, zAxis) @@ -102,13 +102,13 @@ Object.assign(OBB.prototype, { Math.abs(v1.dot(yAxis)) <= this.halfSize.y && Math.abs(v1.dot(zAxis)) <= this.halfSize.z ) - }, + } - intersectsBox3: function (box3) { + intersectsBox3(box3) { return this.intersectsOBB(obb.fromBox3(box3)) - }, + } - intersectsSphere: function (sphere) { + intersectsSphere(sphere) { // find the point on the OBB closest to the sphere center this.clampPoint(sphere.center, closestPoint) @@ -116,14 +116,14 @@ Object.assign(OBB.prototype, { // if that point is inside the sphere, the OBB and sphere intersect return closestPoint.distanceToSquared(sphere.center) <= sphere.radius * sphere.radius - }, + } /** * Reference: OBB-OBB Intersection in Real-Time Collision Detection * by Christer Ericson (chapter 4.4.1) * */ - intersectsOBB: function (obb, epsilon = Number.EPSILON) { + intersectsOBB(obb, epsilon = Number.EPSILON) { // prepare data structures (the code uses the same nomenclature like the reference) a.c = this.center @@ -166,7 +166,7 @@ Object.assign(OBB.prototype, { } } - var ra, rb + let ra, rb // test axes L = A0, L = A1, L = A2 @@ -241,13 +241,13 @@ Object.assign(OBB.prototype, { // since no separating axis is found, the OBBs must be intersecting return true - }, + } /** * Reference: Testing Box Against Plane in Real-Time Collision Detection * by Christer Ericson (chapter 5.2.3) */ - intersectsPlane: function (plane) { + intersectsPlane(plane) { this.rotation.extractBasis(xAxis, yAxis, zAxis) // compute the projection interval radius of this OBB onto L(t) = this->center + t * p.normal; @@ -264,13 +264,13 @@ Object.assign(OBB.prototype, { // Intersection occurs when distance d falls within [-r,+r] interval return Math.abs(d) <= r - }, + } /** * Performs a ray/OBB intersection test and stores the intersection point * to the given 3D vector. If no intersection is detected, *null* is returned. */ - intersectRay: function (ray, result) { + intersectRay(ray, result) { // the idea is to perform the intersection test in the local space // of the OBB. @@ -279,7 +279,7 @@ Object.assign(OBB.prototype, { // create a 4x4 transformation matrix - matrix4FromRotationMatrix(matrix, this.rotation) + matrix.setFromMatrix3(this.rotation) matrix.setPosition(this.center) // transform ray to the local space of the OBB @@ -296,17 +296,17 @@ Object.assign(OBB.prototype, { } else { return null } - }, + } /** * Performs a ray/OBB intersection test. Returns either true or false if * there is a intersection or not. */ - intersectsRay: function (ray) { + intersectsRay(ray) { return this.intersectRay(ray, v1) !== null - }, + } - fromBox3: function (box3) { + fromBox3(box3) { box3.getCenter(this.center) box3.getSize(this.halfSize).multiplyScalar(0.5) @@ -314,27 +314,27 @@ Object.assign(OBB.prototype, { this.rotation.identity() return this - }, + } - equals: function (obb) { + equals(obb) { return obb.center.equals(this.center) && obb.halfSize.equals(this.halfSize) && obb.rotation.equals(this.rotation) - }, + } - applyMatrix4: function (matrix) { - var e = matrix.elements + applyMatrix4(matrix) { + const e = matrix.elements - var sx = v1.set(e[0], e[1], e[2]).length() - var sy = v1.set(e[4], e[5], e[6]).length() - var sz = v1.set(e[8], e[9], e[10]).length() + let sx = v1.set(e[0], e[1], e[2]).length() + const sy = v1.set(e[4], e[5], e[6]).length() + const sz = v1.set(e[8], e[9], e[10]).length() - var det = matrix.determinant() + const det = matrix.determinant() if (det < 0) sx = -sx rotationMatrix.setFromMatrix4(matrix) - var invSX = 1 / sx - var invSY = 1 / sy - var invSZ = 1 / sz + const invSX = 1 / sx + const invSY = 1 / sy + const invSZ = 1 / sz rotationMatrix.elements[0] *= invSX rotationMatrix.elements[1] *= invSX @@ -358,34 +358,9 @@ Object.assign(OBB.prototype, { this.center.add(v1) return this - }, -}) - -function matrix4FromRotationMatrix(matrix4, matrix3) { - var e = matrix4.elements - var me = matrix3.elements - - e[0] = me[0] - e[1] = me[1] - e[2] = me[2] - e[3] = 0 - - e[4] = me[3] - e[5] = me[4] - e[6] = me[5] - e[7] = 0 - - e[8] = me[6] - e[9] = me[7] - e[10] = me[8] - e[11] = 0 - - e[12] = 0 - e[13] = 0 - e[14] = 0 - e[15] = 1 + } } -var obb = new OBB() +const obb = /* @__PURE__ */ new OBB() export { OBB } diff --git a/src/math/Octree.js b/src/math/Octree.js index f638a810..9882264b 100644 --- a/src/math/Octree.js +++ b/src/math/Octree.js @@ -1,367 +1,354 @@ import { Box3, Line3, Plane, Sphere, Triangle, Vector3 } from 'three' import { Capsule } from '../math/Capsule' -var Octree = (function () { - var _v1 = new Vector3() - var _v2 = new Vector3() - var _plane = new Plane() - var _line1 = new Line3() - var _line2 = new Line3() - var _sphere = new Sphere() - var _capsule = new Capsule() - - function Octree(box) { +const _v1 = /* @__PURE__ */ new Vector3() +const _v2 = /* @__PURE__ */ new Vector3() +const _plane = /* @__PURE__ */ new Plane() +const _line1 = /* @__PURE__ */ new Line3() +const _line2 = /* @__PURE__ */ new Line3() +const _sphere = /* @__PURE__ */ new Sphere() +const _capsule = /* @__PURE__ */ new Capsule() + +class Octree { + constructor(box) { this.triangles = [] this.box = box this.subTrees = [] } - Object.assign(Octree.prototype, { - addTriangle: function (triangle) { - if (!this.bounds) this.bounds = new Box3() + addTriangle(triangle) { + if (!this.bounds) this.bounds = new Box3() - this.bounds.min.x = Math.min(this.bounds.min.x, triangle.a.x, triangle.b.x, triangle.c.x) - this.bounds.min.y = Math.min(this.bounds.min.y, triangle.a.y, triangle.b.y, triangle.c.y) - this.bounds.min.z = Math.min(this.bounds.min.z, triangle.a.z, triangle.b.z, triangle.c.z) - this.bounds.max.x = Math.max(this.bounds.max.x, triangle.a.x, triangle.b.x, triangle.c.x) - this.bounds.max.y = Math.max(this.bounds.max.y, triangle.a.y, triangle.b.y, triangle.c.y) - this.bounds.max.z = Math.max(this.bounds.max.z, triangle.a.z, triangle.b.z, triangle.c.z) + this.bounds.min.x = Math.min(this.bounds.min.x, triangle.a.x, triangle.b.x, triangle.c.x) + this.bounds.min.y = Math.min(this.bounds.min.y, triangle.a.y, triangle.b.y, triangle.c.y) + this.bounds.min.z = Math.min(this.bounds.min.z, triangle.a.z, triangle.b.z, triangle.c.z) + this.bounds.max.x = Math.max(this.bounds.max.x, triangle.a.x, triangle.b.x, triangle.c.x) + this.bounds.max.y = Math.max(this.bounds.max.y, triangle.a.y, triangle.b.y, triangle.c.y) + this.bounds.max.z = Math.max(this.bounds.max.z, triangle.a.z, triangle.b.z, triangle.c.z) - this.triangles.push(triangle) + this.triangles.push(triangle) - return this - }, + return this + } - calcBox: function () { - this.box = this.bounds.clone() + calcBox() { + this.box = this.bounds.clone() - // offset small ammount to account for regular grid - this.box.min.x -= 0.01 - this.box.min.y -= 0.01 - this.box.min.z -= 0.01 + // offset small amount to account for regular grid + this.box.min.x -= 0.01 + this.box.min.y -= 0.01 + this.box.min.z -= 0.01 - return this - }, + return this + } - split: function (level) { - if (!this.box) return + split(level) { + if (!this.box) return - var subTrees = [], - halfsize = _v2.copy(this.box.max).sub(this.box.min).multiplyScalar(0.5), - box, - v, - triangle + const subTrees = [] + const halfsize = _v2.copy(this.box.max).sub(this.box.min).multiplyScalar(0.5) - for (let x = 0; x < 2; x++) { - for (let y = 0; y < 2; y++) { - for (let z = 0; z < 2; z++) { - box = new Box3() - v = _v1.set(x, y, z) + for (let x = 0; x < 2; x++) { + for (let y = 0; y < 2; y++) { + for (let z = 0; z < 2; z++) { + const box = new Box3() + const v = _v1.set(x, y, z) - box.min.copy(this.box.min).add(v.multiply(halfsize)) - box.max.copy(box.min).add(halfsize) + box.min.copy(this.box.min).add(v.multiply(halfsize)) + box.max.copy(box.min).add(halfsize) - subTrees.push(new Octree(box)) - } + subTrees.push(new Octree(box)) } } + } + + let triangle - while ((triangle = this.triangles.pop())) { - for (let i = 0; i < subTrees.length; i++) { - if (subTrees[i].box.intersectsTriangle(triangle)) { - subTrees[i].triangles.push(triangle) - } + while ((triangle = this.triangles.pop())) { + for (let i = 0; i < subTrees.length; i++) { + if (subTrees[i].box.intersectsTriangle(triangle)) { + subTrees[i].triangles.push(triangle) } } + } - for (let i = 0; i < subTrees.length; i++) { - var len = subTrees[i].triangles.length + for (let i = 0; i < subTrees.length; i++) { + const len = subTrees[i].triangles.length - if (len > 8 && level < 16) { - subTrees[i].split(level + 1) - } + if (len > 8 && level < 16) { + subTrees[i].split(level + 1) + } - if (len != 0) { - this.subTrees.push(subTrees[i]) - } + if (len !== 0) { + this.subTrees.push(subTrees[i]) } + } - return this - }, + return this + } - build: function () { - this.calcBox() - this.split(0) + build() { + this.calcBox() + this.split(0) - return this - }, + return this + } - getRayTriangles: function (ray, triangles) { - for (let i = 0; i < this.subTrees.length; i++) { - var subTree = this.subTrees[i] - if (!ray.intersectsBox(subTree.box)) continue + getRayTriangles(ray, triangles) { + for (let i = 0; i < this.subTrees.length; i++) { + const subTree = this.subTrees[i] + if (!ray.intersectsBox(subTree.box)) continue - if (subTree.triangles.length > 0) { - for (let j = 0; j < subTree.triangles.length; j++) { - if (triangles.indexOf(subTree.triangles[j]) === -1) triangles.push(subTree.triangles[j]) - } - } else { - subTree.getRayTriangles(ray, triangles) + if (subTree.triangles.length > 0) { + for (let j = 0; j < subTree.triangles.length; j++) { + if (triangles.indexOf(subTree.triangles[j]) === -1) triangles.push(subTree.triangles[j]) } + } else { + subTree.getRayTriangles(ray, triangles) } + } - return triangles - }, + return triangles + } - triangleCapsuleIntersect: function (capsule, triangle) { - var point1, point2, line1, line2 + triangleCapsuleIntersect(capsule, triangle) { + triangle.getPlane(_plane) - triangle.getPlane(_plane) + const d1 = _plane.distanceToPoint(capsule.start) - capsule.radius + const d2 = _plane.distanceToPoint(capsule.end) - capsule.radius - var d1 = _plane.distanceToPoint(capsule.start) - capsule.radius - var d2 = _plane.distanceToPoint(capsule.end) - capsule.radius + if ((d1 > 0 && d2 > 0) || (d1 < -capsule.radius && d2 < -capsule.radius)) { + return false + } - if ((d1 > 0 && d2 > 0) || (d1 < -capsule.radius && d2 < -capsule.radius)) { - return false - } + const delta = Math.abs(d1 / (Math.abs(d1) + Math.abs(d2))) + const intersectPoint = _v1.copy(capsule.start).lerp(capsule.end, delta) - var delta = Math.abs(d1 / (Math.abs(d1) + Math.abs(d2))) - var intersectPoint = _v1.copy(capsule.start).lerp(capsule.end, delta) + if (triangle.containsPoint(intersectPoint)) { + return { normal: _plane.normal.clone(), point: intersectPoint.clone(), depth: Math.abs(Math.min(d1, d2)) } + } - if (triangle.containsPoint(intersectPoint)) { - return { - normal: _plane.normal.clone(), - point: intersectPoint.clone(), - depth: Math.abs(Math.min(d1, d2)), - } - } + const r2 = capsule.radius * capsule.radius - var r2 = capsule.radius * capsule.radius + const line1 = _line1.set(capsule.start, capsule.end) - line1 = _line1.set(capsule.start, capsule.end) + const lines = [ + [triangle.a, triangle.b], + [triangle.b, triangle.c], + [triangle.c, triangle.a], + ] - var lines = [ - [triangle.a, triangle.b], - [triangle.b, triangle.c], - [triangle.c, triangle.a], - ] + for (let i = 0; i < lines.length; i++) { + const line2 = _line2.set(lines[i][0], lines[i][1]) - for (let i = 0; i < lines.length; i++) { - line2 = _line2.set(lines[i][0], lines[i][1]) - ;[point1, point2] = capsule.lineLineMinimumPoints(line1, line2) + const [point1, point2] = capsule.lineLineMinimumPoints(line1, line2) - if (point1.distanceToSquared(point2) < r2) { - return { - normal: point1.clone().sub(point2).normalize(), - point: point2.clone(), - depth: capsule.radius - point1.distanceTo(point2), - } + if (point1.distanceToSquared(point2) < r2) { + return { + normal: point1.clone().sub(point2).normalize(), + point: point2.clone(), + depth: capsule.radius - point1.distanceTo(point2), } } + } - return false - }, + return false + } - triangleSphereIntersect: function (sphere, triangle) { - triangle.getPlane(_plane) + triangleSphereIntersect(sphere, triangle) { + triangle.getPlane(_plane) - if (!sphere.intersectsPlane(_plane)) return false + if (!sphere.intersectsPlane(_plane)) return false - var depth = Math.abs(_plane.distanceToSphere(sphere)) - var r2 = sphere.radius * sphere.radius - depth * depth + const depth = Math.abs(_plane.distanceToSphere(sphere)) + const r2 = sphere.radius * sphere.radius - depth * depth - var plainPoint = _plane.projectPoint(sphere.center, _v1) + const plainPoint = _plane.projectPoint(sphere.center, _v1) - if (triangle.containsPoint(sphere.center)) { - return { - normal: _plane.normal.clone(), - point: plainPoint.clone(), - depth: Math.abs(_plane.distanceToSphere(sphere)), - } + if (triangle.containsPoint(sphere.center)) { + return { + normal: _plane.normal.clone(), + point: plainPoint.clone(), + depth: Math.abs(_plane.distanceToSphere(sphere)), } + } - var lines = [ - [triangle.a, triangle.b], - [triangle.b, triangle.c], - [triangle.c, triangle.a], - ] + const lines = [ + [triangle.a, triangle.b], + [triangle.b, triangle.c], + [triangle.c, triangle.a], + ] - for (let i = 0; i < lines.length; i++) { - _line1.set(lines[i][0], lines[i][1]) - _line1.closestPointToPoint(plainPoint, true, _v2) + for (let i = 0; i < lines.length; i++) { + _line1.set(lines[i][0], lines[i][1]) + _line1.closestPointToPoint(plainPoint, true, _v2) - var d = _v2.distanceToSquared(sphere.center) + const d = _v2.distanceToSquared(sphere.center) - if (d < r2) { - return { - normal: sphere.center.clone().sub(_v2).normalize(), - point: _v2.clone(), - depth: sphere.radius - Math.sqrt(d), - } + if (d < r2) { + return { + normal: sphere.center.clone().sub(_v2).normalize(), + point: _v2.clone(), + depth: sphere.radius - Math.sqrt(d), } } + } - return false - }, + return false + } - getSphereTriangles: function (sphere, triangles) { - for (let i = 0; i < this.subTrees.length; i++) { - var subTree = this.subTrees[i] + getSphereTriangles(sphere, triangles) { + for (let i = 0; i < this.subTrees.length; i++) { + const subTree = this.subTrees[i] - if (!sphere.intersectsBox(subTree.box)) continue + if (!sphere.intersectsBox(subTree.box)) continue - if (subTree.triangles.length > 0) { - for (let j = 0; j < subTree.triangles.length; j++) { - if (triangles.indexOf(subTree.triangles[j]) === -1) triangles.push(subTree.triangles[j]) - } - } else { - subTree.getSphereTriangles(sphere, triangles) + if (subTree.triangles.length > 0) { + for (let j = 0; j < subTree.triangles.length; j++) { + if (triangles.indexOf(subTree.triangles[j]) === -1) triangles.push(subTree.triangles[j]) } + } else { + subTree.getSphereTriangles(sphere, triangles) } - }, + } + } - getCapsuleTriangles: function (capsule, triangles) { - for (let i = 0; i < this.subTrees.length; i++) { - var subTree = this.subTrees[i] + getCapsuleTriangles(capsule, triangles) { + for (let i = 0; i < this.subTrees.length; i++) { + const subTree = this.subTrees[i] - if (!capsule.intersectsBox(subTree.box)) continue + if (!capsule.intersectsBox(subTree.box)) continue - if (subTree.triangles.length > 0) { - for (let j = 0; j < subTree.triangles.length; j++) { - if (triangles.indexOf(subTree.triangles[j]) === -1) triangles.push(subTree.triangles[j]) - } - } else { - subTree.getCapsuleTriangles(capsule, triangles) + if (subTree.triangles.length > 0) { + for (let j = 0; j < subTree.triangles.length; j++) { + if (triangles.indexOf(subTree.triangles[j]) === -1) triangles.push(subTree.triangles[j]) } + } else { + subTree.getCapsuleTriangles(capsule, triangles) } - }, + } + } - sphereIntersect(sphere) { - _sphere.copy(sphere) + sphereIntersect(sphere) { + _sphere.copy(sphere) - var triangles = [], - result, - hit = false + const triangles = [] + let result, + hit = false - this.getSphereTriangles(sphere, triangles) + this.getSphereTriangles(sphere, triangles) - for (let i = 0; i < triangles.length; i++) { - if ((result = this.triangleSphereIntersect(_sphere, triangles[i]))) { - hit = true + for (let i = 0; i < triangles.length; i++) { + if ((result = this.triangleSphereIntersect(_sphere, triangles[i]))) { + hit = true - _sphere.center.add(result.normal.multiplyScalar(result.depth)) - } + _sphere.center.add(result.normal.multiplyScalar(result.depth)) } + } - if (hit) { - var collisionVector = _sphere.center.clone().sub(sphere.center) - var depth = collisionVector.length() + if (hit) { + const collisionVector = _sphere.center.clone().sub(sphere.center) + const depth = collisionVector.length() - return { normal: collisionVector.normalize(), depth: depth } - } + return { normal: collisionVector.normalize(), depth: depth } + } - return false - }, + return false + } - capsuleIntersect: function (capsule) { - _capsule.copy(capsule) + capsuleIntersect(capsule) { + _capsule.copy(capsule) - var triangles = [], - result, - hit = false + const triangles = [] + let result, + hit = false - this.getCapsuleTriangles(_capsule, triangles) + this.getCapsuleTriangles(_capsule, triangles) - for (let i = 0; i < triangles.length; i++) { - if ((result = this.triangleCapsuleIntersect(_capsule, triangles[i]))) { - hit = true + for (let i = 0; i < triangles.length; i++) { + if ((result = this.triangleCapsuleIntersect(_capsule, triangles[i]))) { + hit = true - _capsule.translate(result.normal.multiplyScalar(result.depth)) - } + _capsule.translate(result.normal.multiplyScalar(result.depth)) } + } - if (hit) { - var collisionVector = _capsule.getCenter(new Vector3()).sub(capsule.getCenter(_v1)) - var depth = collisionVector.length() + if (hit) { + const collisionVector = _capsule.getCenter(new Vector3()).sub(capsule.getCenter(_v1)) + const depth = collisionVector.length() - return { normal: collisionVector.normalize(), depth: depth } - } + return { normal: collisionVector.normalize(), depth: depth } + } - return false - }, + return false + } - rayIntersect: function (ray) { - if (ray.direction.length() === 0) return + rayIntersect(ray) { + if (ray.direction.length() === 0) return - var triangles = [], - triangle, - position, - distance = 1e100, - result + const triangles = [] + let triangle, + position, + distance = 1e100 - this.getRayTriangles(ray, triangles) + this.getRayTriangles(ray, triangles) - for (let i = 0; i < triangles.length; i++) { - result = ray.intersectTriangle(triangles[i].a, triangles[i].b, triangles[i].c, true, _v1) + for (let i = 0; i < triangles.length; i++) { + const result = ray.intersectTriangle(triangles[i].a, triangles[i].b, triangles[i].c, true, _v1) - if (result) { - var newdistance = result.sub(ray.origin).length() + if (result) { + const newdistance = result.sub(ray.origin).length() - if (distance > newdistance) { - position = result.clone().add(ray.origin) - distance = newdistance - triangle = triangles[i] - } + if (distance > newdistance) { + position = result.clone().add(ray.origin) + distance = newdistance + triangle = triangles[i] } } + } - return distance < 1e100 ? { distance: distance, triangle: triangle, position: position } : false - }, - - fromGraphNode: function (group) { - group.traverse((obj) => { - if (obj.type === 'Mesh') { - obj.updateMatrix() - obj.updateWorldMatrix() + return distance < 1e100 ? { distance: distance, triangle: triangle, position: position } : false + } - var geometry, - isTemp = false + fromGraphNode(group) { + group.updateWorldMatrix(true, true) - if (obj.geometry.index) { - isTemp = true - geometry = obj.geometry.clone().toNonIndexed() - } else { - geometry = obj.geometry - } + group.traverse((obj) => { + if (obj.isMesh === true) { + let geometry, + isTemp = false - var positions = geometry.attributes.position.array - var transform = obj.matrixWorld + if (obj.geometry.index !== null) { + isTemp = true + geometry = obj.geometry.toNonIndexed() + } else { + geometry = obj.geometry + } - for (let i = 0; i < positions.length; i += 9) { - var v1 = new Vector3(positions[i], positions[i + 1], positions[i + 2]) - var v2 = new Vector3(positions[i + 3], positions[i + 4], positions[i + 5]) - var v3 = new Vector3(positions[i + 6], positions[i + 7], positions[i + 8]) + const positionAttribute = geometry.getAttribute('position') - v1.applyMatrix4(transform) - v2.applyMatrix4(transform) - v3.applyMatrix4(transform) + for (let i = 0; i < positionAttribute.count; i += 3) { + const v1 = new Vector3().fromBufferAttribute(positionAttribute, i) + const v2 = new Vector3().fromBufferAttribute(positionAttribute, i + 1) + const v3 = new Vector3().fromBufferAttribute(positionAttribute, i + 2) - this.addTriangle(new Triangle(v1, v2, v3)) - } + v1.applyMatrix4(obj.matrixWorld) + v2.applyMatrix4(obj.matrixWorld) + v3.applyMatrix4(obj.matrixWorld) - if (isTemp) { - geometry.dispose() - } + this.addTriangle(new Triangle(v1, v2, v3)) } - }) - this.build() + if (isTemp) { + geometry.dispose() + } + } + }) - return this - }, - }) + this.build() - return Octree -})() + return this + } +} export { Octree } diff --git a/src/misc/ConvexObjectBreaker.js b/src/misc/ConvexObjectBreaker.js index 6ff540e9..48f92287 100644 --- a/src/misc/ConvexObjectBreaker.js +++ b/src/misc/ConvexObjectBreaker.js @@ -14,7 +14,7 @@ import { ConvexGeometry } from '../geometries/ConvexGeometry' * * Requisites for the object: * - * - Mesh object must have a BufferGeometry (not Geometry) and a Material + * - Mesh object must have a buffer geometry and a material * * - Vertex normals must be planar (not smoothed) * @@ -30,51 +30,47 @@ import { ConvexGeometry } from '../geometries/ConvexGeometry' * */ -var ConvexObjectBreaker = function (minSizeForBreak, smallDelta) { - this.minSizeForBreak = minSizeForBreak || 1.4 - this.smallDelta = smallDelta || 0.0001 - - this.tempLine1 = new Line3() - this.tempPlane1 = new Plane() - this.tempPlane2 = new Plane() - this.tempPlane_Cut = new Plane() - this.tempCM1 = new Vector3() - this.tempCM2 = new Vector3() - this.tempVector3 = new Vector3() - this.tempVector3_2 = new Vector3() - this.tempVector3_3 = new Vector3() - this.tempVector3_P0 = new Vector3() - this.tempVector3_P1 = new Vector3() - this.tempVector3_P2 = new Vector3() - this.tempVector3_N0 = new Vector3() - this.tempVector3_N1 = new Vector3() - this.tempVector3_AB = new Vector3() - this.tempVector3_CB = new Vector3() - this.tempResultObjects = { object1: null, object2: null } - - this.segments = [] - var n = 30 * 30 - for (let i = 0; i < n; i++) this.segments[i] = false -} - -ConvexObjectBreaker.prototype = { - constructor: ConvexObjectBreaker, +const _v1 = /* @__PURE__ */ new Vector3() + +class ConvexObjectBreaker { + constructor(minSizeForBreak = 1.4, smallDelta = 0.0001) { + this.minSizeForBreak = minSizeForBreak + this.smallDelta = smallDelta + + this.tempLine1 = new Line3() + this.tempPlane1 = new Plane() + this.tempPlane2 = new Plane() + this.tempPlane_Cut = new Plane() + this.tempCM1 = new Vector3() + this.tempCM2 = new Vector3() + this.tempVector3 = new Vector3() + this.tempVector3_2 = new Vector3() + this.tempVector3_3 = new Vector3() + this.tempVector3_P0 = new Vector3() + this.tempVector3_P1 = new Vector3() + this.tempVector3_P2 = new Vector3() + this.tempVector3_N0 = new Vector3() + this.tempVector3_N1 = new Vector3() + this.tempVector3_AB = new Vector3() + this.tempVector3_CB = new Vector3() + this.tempResultObjects = { object1: null, object2: null } + + this.segments = [] + const n = 30 * 30 + for (let i = 0; i < n; i++) this.segments[i] = false + } - prepareBreakableObject: function (object, mass, velocity, angularVelocity, breakable) { - // object is a Object3d (normally a Mesh), must have a BufferGeometry, and it must be convex. + prepareBreakableObject(object, mass, velocity, angularVelocity, breakable) { + // object is a Object3d (normally a Mesh), must have a buffer geometry, and it must be convex. // Its material property is propagated to its children (sub-pieces) // mass must be > 0 - if (!object.geometry.isBufferGeometry) { - console.error('THREE.ConvexObjectBreaker.prepareBreakableObject(): Parameter object must have a BufferGeometry.') - } - - var userData = object.userData + const userData = object.userData userData.mass = mass userData.velocity = velocity.clone() userData.angularVelocity = angularVelocity.clone() userData.breakable = breakable - }, + } /* * @param {int} maxRadialIterations Iterations for radial cuts. @@ -82,18 +78,18 @@ ConvexObjectBreaker.prototype = { * * Returns the array of pieces */ - subdivideByImpact: function (object, pointOfImpact, normal, maxRadialIterations, maxRandomIterations) { - var debris = [] + subdivideByImpact(object, pointOfImpact, normal, maxRadialIterations, maxRandomIterations) { + const debris = [] - var tempPlane1 = this.tempPlane1 - var tempPlane2 = this.tempPlane2 + const tempPlane1 = this.tempPlane1 + const tempPlane2 = this.tempPlane2 this.tempVector3.addVectors(pointOfImpact, normal) tempPlane1.setFromCoplanarPoints(pointOfImpact, object.position, this.tempVector3) - var maxTotalIterations = maxRandomIterations + maxRadialIterations + const maxTotalIterations = maxRandomIterations + maxRadialIterations - var scope = this + const scope = this function subdivideRadial(subObject, startAngle, endAngle, numIterations) { if (Math.random() < numIterations * 0.05 || numIterations > maxTotalIterations) { @@ -102,7 +98,7 @@ ConvexObjectBreaker.prototype = { return } - var angle = Math.PI + let angle = Math.PI if (numIterations === 0) { tempPlane2.normal.copy(tempPlane1.normal) @@ -131,8 +127,8 @@ ConvexObjectBreaker.prototype = { // Perform the cut scope.cutByPlane(subObject, tempPlane2, scope.tempResultObjects) - var obj1 = scope.tempResultObjects.object1 - var obj2 = scope.tempResultObjects.object2 + const obj1 = scope.tempResultObjects.object1 + const obj2 = scope.tempResultObjects.object2 if (obj1) { subdivideRadial(obj1, startAngle, angle, numIterations + 1) @@ -146,22 +142,22 @@ ConvexObjectBreaker.prototype = { subdivideRadial(object, 0, 2 * Math.PI, 0) return debris - }, + } - cutByPlane: function (object, plane, output) { + cutByPlane(object, plane, output) { // Returns breakable objects in output.object1 and output.object2 members, the resulting 2 pieces of the cut. // object2 can be null if the plane doesn't cut the object. // object1 can be null only in case of internal error // Returned value is number of pieces, 0 for error. - var geometry = object.geometry - var coords = geometry.attributes.position.array - var normals = geometry.attributes.normal.array + const geometry = object.geometry + const coords = geometry.attributes.position.array + const normals = geometry.attributes.normal.array - var numPoints = coords.length / 3 - var numFaces = numPoints / 3 + const numPoints = coords.length / 3 + let numFaces = numPoints / 3 - var indices = geometry.getIndex() + let indices = geometry.getIndex() if (indices) { indices = indices.array @@ -171,43 +167,43 @@ ConvexObjectBreaker.prototype = { function getVertexIndex(faceIdx, vert) { // vert = 0, 1 or 2. - var idx = faceIdx * 3 + vert + const idx = faceIdx * 3 + vert return indices ? indices[idx] : idx } - var points1 = [] - var points2 = [] + const points1 = [] + const points2 = [] - var delta = this.smallDelta + const delta = this.smallDelta // Reset segments mark - var numPointPairs = numPoints * numPoints + const numPointPairs = numPoints * numPoints for (let i = 0; i < numPointPairs; i++) this.segments[i] = false - var p0 = this.tempVector3_P0 - var p1 = this.tempVector3_P1 - var n0 = this.tempVector3_N0 - var n1 = this.tempVector3_N1 + const p0 = this.tempVector3_P0 + const p1 = this.tempVector3_P1 + const n0 = this.tempVector3_N0 + const n1 = this.tempVector3_N1 // Iterate through the faces to mark edges shared by coplanar faces for (let i = 0; i < numFaces - 1; i++) { - var a1 = getVertexIndex(i, 0) - var b1 = getVertexIndex(i, 1) - var c1 = getVertexIndex(i, 2) + const a1 = getVertexIndex(i, 0) + const b1 = getVertexIndex(i, 1) + const c1 = getVertexIndex(i, 2) // Assuming all 3 vertices have the same normal n0.set(normals[a1], normals[a1] + 1, normals[a1] + 2) for (let j = i + 1; j < numFaces; j++) { - var a2 = getVertexIndex(j, 0) - var b2 = getVertexIndex(j, 1) - var c2 = getVertexIndex(j, 2) + const a2 = getVertexIndex(j, 0) + const b2 = getVertexIndex(j, 1) + const c2 = getVertexIndex(j, 2) // Assuming all 3 vertices have the same normal n1.set(normals[a2], normals[a2] + 1, normals[a2] + 2) - var coplanar = 1 - n0.dot(n1) < delta + const coplanar = 1 - n0.dot(n1) < delta if (coplanar) { if (a1 === a2 || a1 === b2 || a1 === c2) { @@ -227,21 +223,21 @@ ConvexObjectBreaker.prototype = { } // Transform the plane to object local space - var localPlane = this.tempPlane_Cut + const localPlane = this.tempPlane_Cut object.updateMatrix() ConvexObjectBreaker.transformPlaneToLocalSpace(plane, object.matrix, localPlane) // Iterate through the faces adding points to both pieces for (let i = 0; i < numFaces; i++) { - var va = getVertexIndex(i, 0) - var vb = getVertexIndex(i, 1) - var vc = getVertexIndex(i, 2) + const va = getVertexIndex(i, 0) + const vb = getVertexIndex(i, 1) + const vc = getVertexIndex(i, 2) for (let segment = 0; segment < 3; segment++) { - var i0 = segment === 0 ? va : segment === 1 ? vb : vc - var i1 = segment === 0 ? vb : segment === 1 ? vc : va + const i0 = segment === 0 ? va : segment === 1 ? vb : vc + const i1 = segment === 0 ? vb : segment === 1 ? vc : va - var segmentState = this.segments[i0 * numPoints + i1] + const segmentState = this.segments[i0 * numPoints + i1] if (segmentState) continue // The segment already has been processed in another face @@ -253,9 +249,9 @@ ConvexObjectBreaker.prototype = { p1.set(coords[3 * i1], coords[3 * i1 + 1], coords[3 * i1 + 2]) // mark: 1 for negative side, 2 for positive side, 3 for coplanar point - var mark0 = 0 + let mark0 = 0 - var d = localPlane.distanceToPoint(p0) + let d = localPlane.distanceToPoint(p0) if (d > delta) { mark0 = 2 @@ -270,9 +266,9 @@ ConvexObjectBreaker.prototype = { } // mark: 1 for negative side, 2 for positive side, 3 for coplanar point - var mark1 = 0 + let mark1 = 0 - var d = localPlane.distanceToPoint(p1) + d = localPlane.distanceToPoint(p1) if (d > delta) { mark1 = 2 @@ -292,10 +288,10 @@ ConvexObjectBreaker.prototype = { this.tempLine1.start.copy(p0) this.tempLine1.end.copy(p1) - var intersection = new Vector3() + let intersection = new Vector3() intersection = localPlane.intersectLine(this.tempLine1, intersection) - if (intersection === undefined) { + if (intersection === null) { // Shouldn't happen console.error('Internal error: segment does not intersect plane.') output.segmentedObject1 = null @@ -310,19 +306,19 @@ ConvexObjectBreaker.prototype = { } // Calculate debris mass (very fast and imprecise): - var newMass = object.userData.mass * 0.5 + const newMass = object.userData.mass * 0.5 // Calculate debris Center of Mass (again fast and imprecise) this.tempCM1.set(0, 0, 0) - var radius1 = 0 - var numPoints1 = points1.length + let radius1 = 0 + const numPoints1 = points1.length if (numPoints1 > 0) { for (let i = 0; i < numPoints1; i++) this.tempCM1.add(points1[i]) this.tempCM1.divideScalar(numPoints1) for (let i = 0; i < numPoints1; i++) { - var p = points1[i] + const p = points1[i] p.sub(this.tempCM1) radius1 = Math.max(radius1, p.x, p.y, p.z) } @@ -331,14 +327,14 @@ ConvexObjectBreaker.prototype = { } this.tempCM2.set(0, 0, 0) - var radius2 = 0 - var numPoints2 = points2.length + let radius2 = 0 + const numPoints2 = points2.length if (numPoints2 > 0) { for (let i = 0; i < numPoints2; i++) this.tempCM2.add(points2[i]) this.tempCM2.divideScalar(numPoints2) for (let i = 0; i < numPoints2; i++) { - var p = points2[i] + const p = points2[i] p.sub(this.tempCM2) radius2 = Math.max(radius2, p.x, p.y, p.z) } @@ -346,10 +342,10 @@ ConvexObjectBreaker.prototype = { this.tempCM2.add(object.position) } - var object1 = null - var object2 = null + let object1 = null + let object2 = null - var numObjects = 0 + let numObjects = 0 if (numPoints1 > 4) { object1 = new Mesh(new ConvexGeometry(points1), object.material) @@ -387,74 +383,70 @@ ConvexObjectBreaker.prototype = { output.object2 = object2 return numObjects - }, -} - -ConvexObjectBreaker.transformFreeVector = function (v, m) { - // input: - // vector interpreted as a free vector - // THREE.Matrix4 orthogonal matrix (matrix without scale) + } - var x = v.x, - y = v.y, - z = v.z - var e = m.elements + static transformFreeVector(v, m) { + // input: + // vector interpreted as a free vector + // THREE.Matrix4 orthogonal matrix (matrix without scale) - v.x = e[0] * x + e[4] * y + e[8] * z - v.y = e[1] * x + e[5] * y + e[9] * z - v.z = e[2] * x + e[6] * y + e[10] * z + const x = v.x, + y = v.y, + z = v.z + const e = m.elements - return v -} + v.x = e[0] * x + e[4] * y + e[8] * z + v.y = e[1] * x + e[5] * y + e[9] * z + v.z = e[2] * x + e[6] * y + e[10] * z -ConvexObjectBreaker.transformFreeVectorInverse = function (v, m) { - // input: - // vector interpreted as a free vector - // THREE.Matrix4 orthogonal matrix (matrix without scale) + return v + } - var x = v.x, - y = v.y, - z = v.z - var e = m.elements + static transformFreeVectorInverse(v, m) { + // input: + // vector interpreted as a free vector + // THREE.Matrix4 orthogonal matrix (matrix without scale) - v.x = e[0] * x + e[1] * y + e[2] * z - v.y = e[4] * x + e[5] * y + e[6] * z - v.z = e[8] * x + e[9] * y + e[10] * z + const x = v.x, + y = v.y, + z = v.z + const e = m.elements - return v -} + v.x = e[0] * x + e[1] * y + e[2] * z + v.y = e[4] * x + e[5] * y + e[6] * z + v.z = e[8] * x + e[9] * y + e[10] * z -ConvexObjectBreaker.transformTiedVectorInverse = function (v, m) { - // input: - // vector interpreted as a tied (ordinary) vector - // THREE.Matrix4 orthogonal matrix (matrix without scale) + return v + } - var x = v.x, - y = v.y, - z = v.z - var e = m.elements + static transformTiedVectorInverse(v, m) { + // input: + // vector interpreted as a tied (ordinary) vector + // THREE.Matrix4 orthogonal matrix (matrix without scale) - v.x = e[0] * x + e[1] * y + e[2] * z - e[12] - v.y = e[4] * x + e[5] * y + e[6] * z - e[13] - v.z = e[8] * x + e[9] * y + e[10] * z - e[14] + const x = v.x, + y = v.y, + z = v.z + const e = m.elements - return v -} + v.x = e[0] * x + e[1] * y + e[2] * z - e[12] + v.y = e[4] * x + e[5] * y + e[6] * z - e[13] + v.z = e[8] * x + e[9] * y + e[10] * z - e[14] -ConvexObjectBreaker.transformPlaneToLocalSpace = (function () { - var v1 = new Vector3() + return v + } - return function transformPlaneToLocalSpace(plane, m, resultPlane) { + static transformPlaneToLocalSpace(plane, m, resultPlane) { resultPlane.normal.copy(plane.normal) resultPlane.constant = plane.constant - var referencePoint = ConvexObjectBreaker.transformTiedVectorInverse(plane.coplanarPoint(v1), m) + const referencePoint = ConvexObjectBreaker.transformTiedVectorInverse(plane.coplanarPoint(_v1), m) ConvexObjectBreaker.transformFreeVectorInverse(resultPlane.normal, m) // recalculate constant (like in setFromNormalAndCoplanarPoint) resultPlane.constant = -referencePoint.dot(resultPlane.normal) } -})() +} export { ConvexObjectBreaker } diff --git a/src/misc/GPUComputationRenderer.d.ts b/src/misc/GPUComputationRenderer.d.ts index 521a007e..4f42019f 100644 --- a/src/misc/GPUComputationRenderer.d.ts +++ b/src/misc/GPUComputationRenderer.d.ts @@ -49,4 +49,5 @@ export class GPUComputationRenderer { createTexture(): DataTexture renderTexture(input: Texture, output: Texture): void doRenderTarget(material: Material, output: WebGLRenderTarget): void + dispose(): void } diff --git a/src/misc/GPUComputationRenderer.js b/src/misc/GPUComputationRenderer.js index eb0bd8c4..a5ad1e7a 100644 --- a/src/misc/GPUComputationRenderer.js +++ b/src/misc/GPUComputationRenderer.js @@ -5,6 +5,7 @@ import { FloatType, Mesh, NearestFilter, + NoToneMapping, PlaneGeometry, RGBAFormat, Scene, @@ -40,16 +41,16 @@ import { * // Initialization... * * // Create computation renderer - * var gpuCompute = new GPUComputationRenderer( 1024, 1024, renderer ); + * const gpuCompute = new GPUComputationRenderer( 1024, 1024, renderer ); * * // Create initial state float textures - * var pos0 = gpuCompute.createTexture(); - * var vel0 = gpuCompute.createTexture(); + * const pos0 = gpuCompute.createTexture(); + * const vel0 = gpuCompute.createTexture(); * // and fill in here the texture data... * * // Add texture variables - * var velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, pos0 ); - * var posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, vel0 ); + * const velVar = gpuCompute.addVariable( "textureVelocity", fragmentShaderVel, pos0 ); + * const posVar = gpuCompute.addVariable( "texturePosition", fragmentShaderPos, vel0 ); * * // Add variable dependencies * gpuCompute.setVariableDependencies( velVar, [ velVar, posVar ] ); @@ -59,7 +60,7 @@ import { * velVar.material.uniforms.time = { value: 0.0 }; * * // Check for completeness - * var error = gpuCompute.init(); + * const error = gpuCompute.init(); * if ( error !== null ) { * console.error( error ); * } @@ -81,19 +82,19 @@ import { * Also, you can use utility functions to create ShaderMaterial and perform computations (rendering between textures) * Note that the shaders can have multiple input textures. * - * var myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { value: null } } ); - * var myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { value: null } } ); + * const myFilter1 = gpuCompute.createShaderMaterial( myFilterFragmentShader1, { theTexture: { value: null } } ); + * const myFilter2 = gpuCompute.createShaderMaterial( myFilterFragmentShader2, { theTexture: { value: null } } ); * - * var inputTexture = gpuCompute.createTexture(); + * const inputTexture = gpuCompute.createTexture(); * * // Fill in here inputTexture... * * myFilter1.uniforms.theTexture.value = inputTexture; * - * var myRenderTarget = gpuCompute.createRenderTarget(); + * const myRenderTarget = gpuCompute.createRenderTarget(); * myFilter2.uniforms.theTexture.value = myRenderTarget.texture; * - * var outputRenderTarget = gpuCompute.createRenderTarget(); + * const outputRenderTarget = gpuCompute.createRenderTarget(); * * // Now use the output texture where you want: * myMaterial.uniforms.map.value = outputRenderTarget.texture; @@ -109,248 +110,290 @@ import { * @param {WebGLRenderer} renderer The renderer */ -var GPUComputationRenderer = function (sizeX, sizeY, renderer) { - this.variables = [] +class GPUComputationRenderer { + constructor(sizeX, sizeY, renderer) { + this.variables = [] - this.currentTextureIndex = 0 + this.currentTextureIndex = 0 - var dataType = FloatType + let dataType = FloatType - var scene = new Scene() + const scene = new Scene() - var camera = new Camera() - camera.position.z = 1 + const camera = new Camera() + camera.position.z = 1 - var passThruUniforms = { - passThruTexture: { value: null }, - } + const passThruUniforms = { + passThruTexture: { value: null }, + } - var passThruShader = createShaderMaterial(getPassThroughFragmentShader(), passThruUniforms) + const passThruShader = createShaderMaterial(getPassThroughFragmentShader(), passThruUniforms) - var mesh = new Mesh(new PlaneGeometry(2, 2), passThruShader) - scene.add(mesh) + const mesh = new Mesh(new PlaneGeometry(2, 2), passThruShader) + scene.add(mesh) - this.setDataType = function (type) { - dataType = type - return this - } - - this.addVariable = function (variableName, computeFragmentShader, initialValueTexture) { - var material = this.createShaderMaterial(computeFragmentShader) - - var variable = { - name: variableName, - initialValueTexture: initialValueTexture, - material: material, - dependencies: null, - renderTargets: [], - wrapS: null, - wrapT: null, - minFilter: NearestFilter, - magFilter: NearestFilter, + this.setDataType = function (type) { + dataType = type + return this } - this.variables.push(variable) - - return variable - } + this.addVariable = function (variableName, computeFragmentShader, initialValueTexture) { + const material = this.createShaderMaterial(computeFragmentShader) + + const variable = { + name: variableName, + initialValueTexture: initialValueTexture, + material: material, + dependencies: null, + renderTargets: [], + wrapS: null, + wrapT: null, + minFilter: NearestFilter, + magFilter: NearestFilter, + } - this.setVariableDependencies = function (variable, dependencies) { - variable.dependencies = dependencies - } + this.variables.push(variable) - this.init = function () { - if (renderer.capabilities.isWebGL2 === false && renderer.extensions.has('OES_texture_float') === false) { - return 'No OES_texture_float support for float textures.' + return variable } - if (renderer.capabilities.maxVertexTextures === 0) { - return 'No support for vertex shader textures.' + this.setVariableDependencies = function (variable, dependencies) { + variable.dependencies = dependencies } - for (let i = 0; i < this.variables.length; i++) { - var variable = this.variables[i] - - // Creates rendertargets and initialize them with input texture - variable.renderTargets[0] = this.createRenderTarget( - sizeX, - sizeY, - variable.wrapS, - variable.wrapT, - variable.minFilter, - variable.magFilter, - ) - variable.renderTargets[1] = this.createRenderTarget( - sizeX, - sizeY, - variable.wrapS, - variable.wrapT, - variable.minFilter, - variable.magFilter, - ) - this.renderTexture(variable.initialValueTexture, variable.renderTargets[0]) - this.renderTexture(variable.initialValueTexture, variable.renderTargets[1]) - - // Adds dependencies uniforms to the ShaderMaterial - var material = variable.material - var uniforms = material.uniforms - - if (variable.dependencies !== null) { - for (let d = 0; d < variable.dependencies.length; d++) { - var depVar = variable.dependencies[d] - - if (depVar.name !== variable.name) { - // Checks if variable exists - var found = false - for (let j = 0; j < this.variables.length; j++) { - if (depVar.name === this.variables[j].name) { - found = true - break + this.init = function () { + if (renderer.capabilities.isWebGL2 === false && renderer.extensions.has('OES_texture_float') === false) { + return 'No OES_texture_float support for float textures.' + } + + if (renderer.capabilities.maxVertexTextures === 0) { + return 'No support for vertex shader textures.' + } + + for (let i = 0; i < this.variables.length; i++) { + const variable = this.variables[i] + + // Creates rendertargets and initialize them with input texture + variable.renderTargets[0] = this.createRenderTarget( + sizeX, + sizeY, + variable.wrapS, + variable.wrapT, + variable.minFilter, + variable.magFilter, + ) + variable.renderTargets[1] = this.createRenderTarget( + sizeX, + sizeY, + variable.wrapS, + variable.wrapT, + variable.minFilter, + variable.magFilter, + ) + this.renderTexture(variable.initialValueTexture, variable.renderTargets[0]) + this.renderTexture(variable.initialValueTexture, variable.renderTargets[1]) + + // Adds dependencies uniforms to the ShaderMaterial + const material = variable.material + const uniforms = material.uniforms + + if (variable.dependencies !== null) { + for (let d = 0; d < variable.dependencies.length; d++) { + const depVar = variable.dependencies[d] + + if (depVar.name !== variable.name) { + // Checks if variable exists + let found = false + + for (let j = 0; j < this.variables.length; j++) { + if (depVar.name === this.variables[j].name) { + found = true + break + } } - } - if (!found) { - return 'Variable dependency not found. Variable=' + variable.name + ', dependency=' + depVar.name + if (!found) { + return 'Variable dependency not found. Variable=' + variable.name + ', dependency=' + depVar.name + } } + + uniforms[depVar.name] = { value: null } + + material.fragmentShader = '\nuniform sampler2D ' + depVar.name + ';\n' + material.fragmentShader } + } + } - uniforms[depVar.name] = { value: null } + this.currentTextureIndex = 0 - material.fragmentShader = '\nuniform sampler2D ' + depVar.name + ';\n' + material.fragmentShader + return null + } + + this.compute = function () { + const currentTextureIndex = this.currentTextureIndex + const nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0 + + for (let i = 0, il = this.variables.length; i < il; i++) { + const variable = this.variables[i] + + // Sets texture dependencies uniforms + if (variable.dependencies !== null) { + const uniforms = variable.material.uniforms + + for (let d = 0, dl = variable.dependencies.length; d < dl; d++) { + const depVar = variable.dependencies[d] + + uniforms[depVar.name].value = depVar.renderTargets[currentTextureIndex].texture + } } + + // Performs the computation for this variable + this.doRenderTarget(variable.material, variable.renderTargets[nextTextureIndex]) } + + this.currentTextureIndex = nextTextureIndex } - this.currentTextureIndex = 0 + this.getCurrentRenderTarget = function (variable) { + return variable.renderTargets[this.currentTextureIndex] + } - return null - } + this.getAlternateRenderTarget = function (variable) { + return variable.renderTargets[this.currentTextureIndex === 0 ? 1 : 0] + } + + this.dispose = function () { + mesh.geometry.dispose() + mesh.material.dispose() - this.compute = function () { - var currentTextureIndex = this.currentTextureIndex - var nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0 + const variables = this.variables - for (let i = 0, il = this.variables.length; i < il; i++) { - var variable = this.variables[i] + for (let i = 0; i < variables.length; i++) { + const variable = variables[i] - // Sets texture dependencies uniforms - if (variable.dependencies !== null) { - var uniforms = variable.material.uniforms - for (let d = 0, dl = variable.dependencies.length; d < dl; d++) { - var depVar = variable.dependencies[d] + if (variable.initialValueTexture) variable.initialValueTexture.dispose() - uniforms[depVar.name].value = depVar.renderTargets[currentTextureIndex].texture + const renderTargets = variable.renderTargets + + for (let j = 0; j < renderTargets.length; j++) { + const renderTarget = renderTargets[j] + renderTarget.dispose() } } + } - // Performs the computation for this variable - this.doRenderTarget(variable.material, variable.renderTargets[nextTextureIndex]) + function addResolutionDefine(materialShader) { + materialShader.defines.resolution = 'vec2( ' + sizeX.toFixed(1) + ', ' + sizeY.toFixed(1) + ' )' } - this.currentTextureIndex = nextTextureIndex - } + this.addResolutionDefine = addResolutionDefine - this.getCurrentRenderTarget = function (variable) { - return variable.renderTargets[this.currentTextureIndex] - } + // The following functions can be used to compute things manually - this.getAlternateRenderTarget = function (variable) { - return variable.renderTargets[this.currentTextureIndex === 0 ? 1 : 0] - } - - function addResolutionDefine(materialShader) { - materialShader.defines.resolution = 'vec2( ' + sizeX.toFixed(1) + ', ' + sizeY.toFixed(1) + ' )' - } + function createShaderMaterial(computeFragmentShader, uniforms) { + uniforms = uniforms || {} - this.addResolutionDefine = addResolutionDefine + const material = new ShaderMaterial({ + uniforms: uniforms, + vertexShader: getPassThroughVertexShader(), + fragmentShader: computeFragmentShader, + }) - // The following functions can be used to compute things manually + addResolutionDefine(material) - function createShaderMaterial(computeFragmentShader, uniforms) { - uniforms = uniforms || {} + return material + } - var material = new ShaderMaterial({ - uniforms: uniforms, - vertexShader: getPassThroughVertexShader(), - fragmentShader: computeFragmentShader, - }) + this.createShaderMaterial = createShaderMaterial - addResolutionDefine(material) + this.createRenderTarget = function (sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter) { + sizeXTexture = sizeXTexture || sizeX + sizeYTexture = sizeYTexture || sizeY - return material - } + wrapS = wrapS || ClampToEdgeWrapping + wrapT = wrapT || ClampToEdgeWrapping - this.createShaderMaterial = createShaderMaterial + minFilter = minFilter || NearestFilter + magFilter = magFilter || NearestFilter - this.createRenderTarget = function (sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter) { - sizeXTexture = sizeXTexture || sizeX - sizeYTexture = sizeYTexture || sizeY + const renderTarget = new WebGLRenderTarget(sizeXTexture, sizeYTexture, { + wrapS: wrapS, + wrapT: wrapT, + minFilter: minFilter, + magFilter: magFilter, + format: RGBAFormat, + type: dataType, + depthBuffer: false, + }) - wrapS = wrapS || ClampToEdgeWrapping - wrapT = wrapT || ClampToEdgeWrapping + return renderTarget + } - minFilter = minFilter || NearestFilter - magFilter = magFilter || NearestFilter + this.createTexture = function () { + const data = new Float32Array(sizeX * sizeY * 4) + const texture = new DataTexture(data, sizeX, sizeY, RGBAFormat, FloatType) + texture.needsUpdate = true + return texture + } - var renderTarget = new WebGLRenderTarget(sizeXTexture, sizeYTexture, { - wrapS: wrapS, - wrapT: wrapT, - minFilter: minFilter, - magFilter: magFilter, - format: RGBAFormat, - type: dataType, - depthBuffer: false, - }) + this.renderTexture = function (input, output) { + // Takes a texture, and render out in rendertarget + // input = Texture + // output = RenderTarget - return renderTarget - } + passThruUniforms.passThruTexture.value = input - this.createTexture = function () { - var data = new Float32Array(sizeX * sizeY * 4) - return new DataTexture(data, sizeX, sizeY, RGBAFormat, FloatType) - } + this.doRenderTarget(passThruShader, output) - this.renderTexture = function (input, output) { - // Takes a texture, and render out in rendertarget - // input = Texture - // output = RenderTarget + passThruUniforms.passThruTexture.value = null + } - passThruUniforms.passThruTexture.value = input + this.doRenderTarget = function (material, output) { + const currentRenderTarget = renderer.getRenderTarget() - this.doRenderTarget(passThruShader, output) + const currentXrEnabled = renderer.xr.enabled + const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate + const currentOutputColorSpace = renderer.outputColorSpace + const currentToneMapping = renderer.toneMapping - passThruUniforms.passThruTexture.value = null - } + renderer.xr.enabled = false // Avoid camera modification + renderer.shadowMap.autoUpdate = false // Avoid re-computing shadows + if ('outputColorSpace' in renderer) renderer.outputColorSpace = 'srgb-linear' + else renderer.encoding = 3000 // LinearEncoding + renderer.toneMapping = NoToneMapping - this.doRenderTarget = function (material, output) { - var currentRenderTarget = renderer.getRenderTarget() + mesh.material = material + renderer.setRenderTarget(output) + renderer.render(scene, camera) + mesh.material = passThruShader - mesh.material = material - renderer.setRenderTarget(output) - renderer.render(scene, camera) - mesh.material = passThruShader + renderer.xr.enabled = currentXrEnabled + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate + renderer.outputColorSpace = currentOutputColorSpace + renderer.toneMapping = currentToneMapping - renderer.setRenderTarget(currentRenderTarget) - } + renderer.setRenderTarget(currentRenderTarget) + } - // Shaders + // Shaders - function getPassThroughVertexShader() { - return 'void main() {\n' + '\n' + ' gl_Position = vec4( position, 1.0 );\n' + '\n' + '}\n' - } + function getPassThroughVertexShader() { + return 'void main() {\n' + '\n' + ' gl_Position = vec4( position, 1.0 );\n' + '\n' + '}\n' + } - function getPassThroughFragmentShader() { - return ( - 'uniform sampler2D passThruTexture;\n' + - '\n' + - 'void main() {\n' + - '\n' + - ' vec2 uv = gl_FragCoord.xy / resolution.xy;\n' + - '\n' + - ' gl_FragColor = texture2D( passThruTexture, uv );\n' + - '\n' + - '}\n' - ) + function getPassThroughFragmentShader() { + return ( + 'uniform sampler2D passThruTexture;\n' + + '\n' + + 'void main() {\n' + + '\n' + + ' vec2 uv = gl_FragCoord.xy / resolution.xy;\n' + + '\n' + + ' gl_FragColor = texture2D( passThruTexture, uv );\n' + + '\n' + + '}\n' + ) + } } } diff --git a/src/misc/Gyroscope.js b/src/misc/Gyroscope.js index 2c7890de..570f40d3 100644 --- a/src/misc/Gyroscope.js +++ b/src/misc/Gyroscope.js @@ -1,22 +1,19 @@ import { Object3D, Quaternion, Vector3 } from 'three' -var Gyroscope = function () { - Object3D.call(this) -} - -Gyroscope.prototype = Object.create(Object3D.prototype) -Gyroscope.prototype.constructor = Gyroscope +const _translationObject = /* @__PURE__ */ new Vector3() +const _quaternionObject = /* @__PURE__ */ new Quaternion() +const _scaleObject = /* @__PURE__ */ new Vector3() -Gyroscope.prototype.updateMatrixWorld = (function () { - var translationObject = new Vector3() - var quaternionObject = new Quaternion() - var scaleObject = new Vector3() +const _translationWorld = /* @__PURE__ */ new Vector3() +const _quaternionWorld = /* @__PURE__ */ new Quaternion() +const _scaleWorld = /* @__PURE__ */ new Vector3() - var translationWorld = new Vector3() - var quaternionWorld = new Quaternion() - var scaleWorld = new Vector3() +class Gyroscope extends Object3D { + constructor() { + super() + } - return function updateMatrixWorld(force) { + updateMatrixWorld(force) { this.matrixAutoUpdate && this.updateMatrix() // update matrixWorld @@ -25,10 +22,10 @@ Gyroscope.prototype.updateMatrixWorld = (function () { if (this.parent !== null) { this.matrixWorld.multiplyMatrices(this.parent.matrixWorld, this.matrix) - this.matrixWorld.decompose(translationWorld, quaternionWorld, scaleWorld) - this.matrix.decompose(translationObject, quaternionObject, scaleObject) + this.matrixWorld.decompose(_translationWorld, _quaternionWorld, _scaleWorld) + this.matrix.decompose(_translationObject, _quaternionObject, _scaleObject) - this.matrixWorld.compose(translationWorld, quaternionObject, scaleWorld) + this.matrixWorld.compose(_translationWorld, _quaternionObject, _scaleWorld) } else { this.matrixWorld.copy(this.matrix) } @@ -44,6 +41,6 @@ Gyroscope.prototype.updateMatrixWorld = (function () { this.children[i].updateMatrixWorld(force) } } -})() +} export { Gyroscope } diff --git a/src/misc/MD2Character.js b/src/misc/MD2Character.js index e57b0232..3bef4d7b 100644 --- a/src/misc/MD2Character.js +++ b/src/misc/MD2Character.js @@ -1,34 +1,88 @@ import { AnimationMixer, Box3, Mesh, MeshLambertMaterial, Object3D, TextureLoader, UVMapping } from 'three' import { MD2Loader } from '../loaders/MD2Loader' -var MD2Character = function () { - var scope = this +class MD2Character { + constructor() { + this.scale = 1 + this.animationFPS = 6 - this.scale = 1 - this.animationFPS = 6 + this.root = new Object3D() - this.root = new Object3D() + this.meshBody = null + this.meshWeapon = null - this.meshBody = null - this.meshWeapon = null + this.skinsBody = [] + this.skinsWeapon = [] - this.skinsBody = [] - this.skinsWeapon = [] + this.weapons = [] - this.weapons = [] + this.activeAnimation = null - this.activeAnimation = null + this.mixer = null - this.mixer = null + this.onLoadComplete = function () {} - this.onLoadComplete = function () {} + this.loadCounter = 0 + } + + loadParts(config) { + const scope = this + + function createPart(geometry, skinMap) { + const materialWireframe = new MeshLambertMaterial({ + color: 0xffaa00, + wireframe: true, + morphTargets: true, + morphNormals: true, + }) + const materialTexture = new MeshLambertMaterial({ + color: 0xffffff, + wireframe: false, + map: skinMap, + morphTargets: true, + morphNormals: true, + }) + + // + + const mesh = new Mesh(geometry, materialTexture) + mesh.rotation.y = -Math.PI / 2 + + mesh.castShadow = true + mesh.receiveShadow = true + + // + + mesh.materialTexture = materialTexture + mesh.materialWireframe = materialWireframe + + return mesh + } + + function loadTextures(baseUrl, textureUrls) { + const textureLoader = new TextureLoader() + const textures = [] - this.loadCounter = 0 + for (let i = 0; i < textureUrls.length; i++) { + textures[i] = textureLoader.load(baseUrl + textureUrls[i], checkLoadingComplete) + textures[i].mapping = UVMapping + textures[i].name = textureUrls[i] + if ('colorSpace' in textures[i]) textures[i].colorSpace = 'srgb' + else textures[i].encoding = 3001 // sRGBEncoding + } + + return textures + } + + function checkLoadingComplete() { + scope.loadCounter -= 1 + + if (scope.loadCounter === 0) scope.onLoadComplete() + } - this.loadParts = function (config) { this.loadCounter = config.weapons.length * 2 + config.skins.length + 1 - var weaponsTextures = [] + const weaponsTextures = [] for (let i = 0; i < config.weapons.length; i++) weaponsTextures[i] = config.weapons[i][1] // SKINS @@ -37,15 +91,15 @@ var MD2Character = function () { // BODY - var loader = new MD2Loader() + const loader = new MD2Loader() loader.load(config.baseUrl + config.body, function (geo) { - var boundingBox = new Box3() + const boundingBox = new Box3() boundingBox.setFromBufferAttribute(geo.attributes.position) scope.root.position.y = -scope.scale * boundingBox.min.y - var mesh = createPart(geo, scope.skinsBody[0]) + const mesh = createPart(geo, scope.skinsBody[0]) mesh.scale.set(scope.scale, scope.scale, scope.scale) scope.root.add(mesh) @@ -62,9 +116,9 @@ var MD2Character = function () { // WEAPONS - var generateCallback = function (index, name) { + const generateCallback = function (index, name) { return function (geo) { - var mesh = createPart(geo, scope.skinsWeapon[index]) + const mesh = createPart(geo, scope.skinsWeapon[index]) mesh.scale.set(scope.scale, scope.scale, scope.scale) mesh.visible = false @@ -84,7 +138,7 @@ var MD2Character = function () { } } - this.setPlaybackRate = function (rate) { + setPlaybackRate(rate) { if (rate !== 0) { this.mixer.timeScale = 1 / rate } else { @@ -92,7 +146,7 @@ var MD2Character = function () { } } - this.setWireframe = function (wireframeEnabled) { + setWireframe(wireframeEnabled) { if (wireframeEnabled) { if (this.meshBody) this.meshBody.material = this.meshBody.materialWireframe if (this.meshWeapon) this.meshWeapon.material = this.meshWeapon.materialWireframe @@ -102,54 +156,54 @@ var MD2Character = function () { } } - this.setSkin = function (index) { + setSkin(index) { if (this.meshBody && this.meshBody.material.wireframe === false) { this.meshBody.material.map = this.skinsBody[index] } } - this.setWeapon = function (index) { + setWeapon(index) { for (let i = 0; i < this.weapons.length; i++) this.weapons[i].visible = false - var activeWeapon = this.weapons[index] + const activeWeapon = this.weapons[index] if (activeWeapon) { activeWeapon.visible = true this.meshWeapon = activeWeapon - scope.syncWeaponAnimation() + this.syncWeaponAnimation() } } - this.setAnimation = function (clipName) { + setAnimation(clipName) { if (this.meshBody) { if (this.meshBody.activeAction) { this.meshBody.activeAction.stop() this.meshBody.activeAction = null } - var action = this.mixer.clipAction(clipName, this.meshBody) + const action = this.mixer.clipAction(clipName, this.meshBody) if (action) { this.meshBody.activeAction = action.play() } } - scope.activeClipName = clipName + this.activeClipName = clipName - scope.syncWeaponAnimation() + this.syncWeaponAnimation() } - this.syncWeaponAnimation = function () { - var clipName = scope.activeClipName + syncWeaponAnimation() { + const clipName = this.activeClipName - if (scope.meshWeapon) { + if (this.meshWeapon) { if (this.meshWeapon.activeAction) { this.meshWeapon.activeAction.stop() this.meshWeapon.activeAction = null } - var action = this.mixer.clipAction(clipName, this.meshWeapon) + const action = this.mixer.clipAction(clipName, this.meshWeapon) if (action) { this.meshWeapon.activeAction = action.syncWith(this.meshBody.activeAction).play() @@ -157,61 +211,9 @@ var MD2Character = function () { } } - this.update = function (delta) { + update(delta) { if (this.mixer) this.mixer.update(delta) } - - function loadTextures(baseUrl, textureUrls) { - var textureLoader = new TextureLoader() - var textures = [] - - for (let i = 0; i < textureUrls.length; i++) { - textures[i] = textureLoader.load(baseUrl + textureUrls[i], checkLoadingComplete) - textures[i].mapping = UVMapping - textures[i].name = textureUrls[i] - if ('colorSpace' in textures[i]) textures[i].colorSpace = 'srgb' - else textures[i].encoding = 3001 // sRGBEncoding - } - - return textures - } - - function createPart(geometry, skinMap) { - var materialWireframe = new MeshLambertMaterial({ - color: 0xffaa00, - wireframe: true, - morphTargets: true, - morphNormals: true, - }) - var materialTexture = new MeshLambertMaterial({ - color: 0xffffff, - wireframe: false, - map: skinMap, - morphTargets: true, - morphNormals: true, - }) - - // - - var mesh = new Mesh(geometry, materialTexture) - mesh.rotation.y = -Math.PI / 2 - - mesh.castShadow = true - mesh.receiveShadow = true - - // - - mesh.materialTexture = materialTexture - mesh.materialWireframe = materialWireframe - - return mesh - } - - function checkLoadingComplete() { - scope.loadCounter -= 1 - - if (scope.loadCounter === 0) scope.onLoadComplete() - } } export { MD2Character } diff --git a/src/misc/MD2CharacterComplex.js b/src/misc/MD2CharacterComplex.js index ad6b191f..83bcef5a 100644 --- a/src/misc/MD2CharacterComplex.js +++ b/src/misc/MD2CharacterComplex.js @@ -2,87 +2,87 @@ import { Box3, MathUtils, MeshLambertMaterial, Object3D, TextureLoader, UVMappin import { MD2Loader } from '../loaders/MD2Loader' import { MorphBlendMesh } from '../misc/MorphBlendMesh' -var MD2CharacterComplex = function () { - var scope = this +class MD2CharacterComplex { + constructor() { + this.scale = 1 - this.scale = 1 + // animation parameters - // animation parameters + this.animationFPS = 6 + this.transitionFrames = 15 - this.animationFPS = 6 - this.transitionFrames = 15 + // movement model parameters - // movement model parameters + this.maxSpeed = 275 + this.maxReverseSpeed = -275 - this.maxSpeed = 275 - this.maxReverseSpeed = -275 + this.frontAcceleration = 600 + this.backAcceleration = 600 - this.frontAcceleration = 600 - this.backAcceleration = 600 + this.frontDecceleration = 600 - this.frontDecceleration = 600 + this.angularSpeed = 2.5 - this.angularSpeed = 2.5 + // rig - // rig + this.root = new Object3D() - this.root = new Object3D() + this.meshBody = null + this.meshWeapon = null - this.meshBody = null - this.meshWeapon = null + this.controls = null - this.controls = null + // skins - // skins + this.skinsBody = [] + this.skinsWeapon = [] - this.skinsBody = [] - this.skinsWeapon = [] + this.weapons = [] - this.weapons = [] + this.currentSkin = undefined - this.currentSkin = undefined - - // + // - this.onLoadComplete = function () {} + this.onLoadComplete = function () {} - // internals + // internals - this.meshes = [] - this.animations = {} + this.meshes = [] + this.animations = {} - this.loadCounter = 0 + this.loadCounter = 0 - // internal movement control variables + // internal movement control variables - this.speed = 0 - this.bodyOrientation = 0 + this.speed = 0 + this.bodyOrientation = 0 - this.walkSpeed = this.maxSpeed - this.crouchSpeed = this.maxSpeed * 0.5 + this.walkSpeed = this.maxSpeed + this.crouchSpeed = this.maxSpeed * 0.5 - // internal animation parameters + // internal animation parameters - this.activeAnimation = null - this.oldAnimation = null + this.activeAnimation = null + this.oldAnimation = null - // API + // API + } - this.enableShadows = function (enable) { + enableShadows(enable) { for (let i = 0; i < this.meshes.length; i++) { this.meshes[i].castShadow = enable this.meshes[i].receiveShadow = enable } } - this.setVisible = function (enable) { + setVisible(enable) { for (let i = 0; i < this.meshes.length; i++) { this.meshes[i].visible = enable this.meshes[i].visible = enable } } - this.shareParts = function (original) { + shareParts(original) { this.animations = original.animations this.walkSpeed = original.walkSpeed this.crouchSpeed = original.crouchSpeed @@ -92,7 +92,7 @@ var MD2CharacterComplex = function () { // BODY - var mesh = createPart(original.meshBody.geometry, this.skinsBody[0]) + const mesh = this._createPart(original.meshBody.geometry, this.skinsBody[0]) mesh.scale.set(this.scale, this.scale, this.scale) this.root.position.y = original.root.position.y @@ -105,7 +105,7 @@ var MD2CharacterComplex = function () { // WEAPONS for (let i = 0; i < original.weapons.length; i++) { - var meshWeapon = createPart(original.weapons[i].geometry, this.skinsWeapon[i]) + const meshWeapon = this._createPart(original.weapons[i].geometry, this.skinsWeapon[i]) meshWeapon.scale.set(this.scale, this.scale, this.scale) meshWeapon.visible = false @@ -120,14 +120,36 @@ var MD2CharacterComplex = function () { } } - this.loadParts = function (config) { + loadParts(config) { + const scope = this + + function loadTextures(baseUrl, textureUrls) { + const textureLoader = new TextureLoader() + const textures = [] + + for (let i = 0; i < textureUrls.length; i++) { + textures[i] = textureLoader.load(baseUrl + textureUrls[i], checkLoadingComplete) + textures[i].mapping = UVMapping + textures[i].name = textureUrls[i] + if ('colorSpace' in textures[i]) textures[i].colorSpace = 'srgb' + else textures[i].encoding = 3001 // sRGBEncoding + } + + return textures + } + + function checkLoadingComplete() { + scope.loadCounter -= 1 + if (scope.loadCounter === 0) scope.onLoadComplete() + } + this.animations = config.animations this.walkSpeed = config.walkSpeed this.crouchSpeed = config.crouchSpeed this.loadCounter = config.weapons.length * 2 + config.skins.length + 1 - var weaponsTextures = [] + const weaponsTextures = [] for (let i = 0; i < config.weapons.length; i++) weaponsTextures[i] = config.weapons[i][1] // SKINS @@ -137,15 +159,15 @@ var MD2CharacterComplex = function () { // BODY - var loader = new MD2Loader() + const loader = new MD2Loader() loader.load(config.baseUrl + config.body, function (geo) { - var boundingBox = new Box3() + const boundingBox = new Box3() boundingBox.setFromBufferAttribute(geo.attributes.position) scope.root.position.y = -scope.scale * boundingBox.min.y - var mesh = createPart(geo, scope.skinsBody[0]) + const mesh = scope._createPart(geo, scope.skinsBody[0]) mesh.scale.set(scope.scale, scope.scale, scope.scale) scope.root.add(mesh) @@ -158,9 +180,9 @@ var MD2CharacterComplex = function () { // WEAPONS - var generateCallback = function (index, name) { + const generateCallback = function (index, name) { return function (geo) { - var mesh = createPart(geo, scope.skinsWeapon[index]) + const mesh = scope._createPart(geo, scope.skinsWeapon[index]) mesh.scale.set(scope.scale, scope.scale, scope.scale) mesh.visible = false @@ -181,12 +203,12 @@ var MD2CharacterComplex = function () { } } - this.setPlaybackRate = function (rate) { + setPlaybackRate(rate) { if (this.meshBody) this.meshBody.duration = this.meshBody.baseDuration / rate if (this.meshWeapon) this.meshWeapon.duration = this.meshWeapon.baseDuration / rate } - this.setWireframe = function (wireframeEnabled) { + setWireframe(wireframeEnabled) { if (wireframeEnabled) { if (this.meshBody) this.meshBody.material = this.meshBody.materialWireframe if (this.meshWeapon) this.meshWeapon.material = this.meshWeapon.materialWireframe @@ -196,17 +218,17 @@ var MD2CharacterComplex = function () { } } - this.setSkin = function (index) { + setSkin(index) { if (this.meshBody && this.meshBody.material.wireframe === false) { this.meshBody.material.map = this.skinsBody[index] this.currentSkin = index } } - this.setWeapon = function (index) { + setWeapon(index) { for (let i = 0; i < this.weapons.length; i++) this.weapons[i].visible = false - var activeWeapon = this.weapons[index] + const activeWeapon = this.weapons[index] if (activeWeapon) { activeWeapon.visible = true @@ -219,7 +241,7 @@ var MD2CharacterComplex = function () { } } - this.setAnimation = function (animationName) { + setAnimation(animationName) { if (animationName === this.activeAnimation || !animationName) return if (this.meshBody) { @@ -238,7 +260,7 @@ var MD2CharacterComplex = function () { } } - this.update = function (delta) { + update(delta) { if (this.controls) this.updateMovementModel(delta) if (this.animations) { @@ -247,8 +269,8 @@ var MD2CharacterComplex = function () { } } - this.updateAnimations = function (delta) { - var mix = 1 + updateAnimations(delta) { + let mix = 1 if (this.blendCounter > 0) { mix = (this.transitionFrames - this.blendCounter) / this.transitionFrames @@ -270,11 +292,11 @@ var MD2CharacterComplex = function () { } } - this.updateBehaviors = function () { - var controls = this.controls - var animations = this.animations + updateBehaviors() { + const controls = this.controls + const animations = this.animations - var moveAnimation, idleAnimation + let moveAnimation, idleAnimation // crouch vs stand @@ -347,8 +369,12 @@ var MD2CharacterComplex = function () { } } - this.updateMovementModel = function (delta) { - var controls = this.controls + updateMovementModel(delta) { + function exponentialEaseOut(k) { + return k === 1 ? 1 : -Math.pow(2, -10 * k) + 1 + } + + const controls = this.controls // speed based on controls @@ -357,17 +383,15 @@ var MD2CharacterComplex = function () { this.maxReverseSpeed = -this.maxSpeed - if (controls.moveForward) { + if (controls.moveForward) this.speed = MathUtils.clamp(this.speed + delta * this.frontAcceleration, this.maxReverseSpeed, this.maxSpeed) - } - if (controls.moveBackward) { + if (controls.moveBackward) this.speed = MathUtils.clamp(this.speed - delta * this.backAcceleration, this.maxReverseSpeed, this.maxSpeed) - } // orientation based on controls // (don't just stand while turning) - var dir = 1 + const dir = 1 if (controls.moveLeft) { this.bodyOrientation += delta * this.angularSpeed @@ -391,17 +415,17 @@ var MD2CharacterComplex = function () { if (!(controls.moveForward || controls.moveBackward)) { if (this.speed > 0) { - var k = exponentialEaseOut(this.speed / this.maxSpeed) + const k = exponentialEaseOut(this.speed / this.maxSpeed) this.speed = MathUtils.clamp(this.speed - k * delta * this.frontDecceleration, 0, this.maxSpeed) } else { - var k = exponentialEaseOut(this.speed / this.maxReverseSpeed) + const k = exponentialEaseOut(this.speed / this.maxReverseSpeed) this.speed = MathUtils.clamp(this.speed + k * delta * this.backAcceleration, this.maxReverseSpeed, 0) } } // displacement - var forwardDelta = this.speed * delta + const forwardDelta = this.speed * delta this.root.position.x += Math.sin(this.bodyOrientation) * forwardDelta this.root.position.z += Math.cos(this.bodyOrientation) * forwardDelta @@ -411,31 +435,16 @@ var MD2CharacterComplex = function () { this.root.rotation.y = this.bodyOrientation } - // internal helpers - - function loadTextures(baseUrl, textureUrls) { - var textureLoader = new TextureLoader() - var textures = [] - - for (let i = 0; i < textureUrls.length; i++) { - textures[i] = textureLoader.load(baseUrl + textureUrls[i], checkLoadingComplete) - textures[i].mapping = UVMapping - textures[i].name = textureUrls[i] - if ('colorSpace' in textures[i]) textures[i].colorSpace = 'srgb' - else textures[i].encoding = 3001 // sRGBEncoding - } + // internal - return textures - } - - function createPart(geometry, skinMap) { - var materialWireframe = new MeshLambertMaterial({ + _createPart(geometry, skinMap) { + const materialWireframe = new MeshLambertMaterial({ color: 0xffaa00, wireframe: true, morphTargets: true, morphNormals: true, }) - var materialTexture = new MeshLambertMaterial({ + const materialTexture = new MeshLambertMaterial({ color: 0xffffff, wireframe: false, map: skinMap, @@ -445,7 +454,7 @@ var MD2CharacterComplex = function () { // - var mesh = new MorphBlendMesh(geometry, materialTexture) + const mesh = new MorphBlendMesh(geometry, materialTexture) mesh.rotation.y = -Math.PI / 2 // @@ -455,19 +464,10 @@ var MD2CharacterComplex = function () { // - mesh.autoCreateAnimations(scope.animationFPS) + mesh.autoCreateAnimations(this.animationFPS) return mesh } - - function checkLoadingComplete() { - scope.loadCounter -= 1 - if (scope.loadCounter === 0) scope.onLoadComplete() - } - - function exponentialEaseOut(k) { - return k === 1 ? 1 : -Math.pow(2, -10 * k) + 1 - } } export { MD2CharacterComplex } diff --git a/src/misc/MorphAnimMesh.d.ts b/src/misc/MorphAnimMesh.d.ts index 5db01883..1e8c2864 100644 --- a/src/misc/MorphAnimMesh.d.ts +++ b/src/misc/MorphAnimMesh.d.ts @@ -9,5 +9,5 @@ export class MorphAnimMesh extends Mesh { setDirectionBackward(): void playAnimation(label: string, fps: number): void updateAnimation(delta: number): void - copy(source: MorphAnimMesh): this + copy(source: MorphAnimMesh, recursive?: boolean): this } diff --git a/src/misc/MorphAnimMesh.js b/src/misc/MorphAnimMesh.js index 31930d20..39a0302b 100644 --- a/src/misc/MorphAnimMesh.js +++ b/src/misc/MorphAnimMesh.js @@ -1,52 +1,51 @@ import { AnimationClip, AnimationMixer, Mesh } from 'three' -var MorphAnimMesh = function (geometry, material) { - Mesh.call(this, geometry, material) +class MorphAnimMesh extends Mesh { + constructor(geometry, material) { + super(geometry, material) - this.type = 'MorphAnimMesh' + this.type = 'MorphAnimMesh' - this.mixer = new AnimationMixer(this) - this.activeAction = null -} - -MorphAnimMesh.prototype = Object.create(Mesh.prototype) -MorphAnimMesh.prototype.constructor = MorphAnimMesh - -MorphAnimMesh.prototype.setDirectionForward = function () { - this.mixer.timeScale = 1.0 -} - -MorphAnimMesh.prototype.setDirectionBackward = function () { - this.mixer.timeScale = -1.0 -} - -MorphAnimMesh.prototype.playAnimation = function (label, fps) { - if (this.activeAction) { - this.activeAction.stop() + this.mixer = new AnimationMixer(this) this.activeAction = null } - var clip = AnimationClip.findByName(this, label) + setDirectionForward() { + this.mixer.timeScale = 1.0 + } - if (clip) { - var action = this.mixer.clipAction(clip) - action.timeScale = (clip.tracks.length * fps) / clip.duration - this.activeAction = action.play() - } else { - throw new Error('THREE.MorphAnimMesh: animations[' + label + '] undefined in .playAnimation()') + setDirectionBackward() { + this.mixer.timeScale = -1.0 } -} -MorphAnimMesh.prototype.updateAnimation = function (delta) { - this.mixer.update(delta) -} + playAnimation(label, fps) { + if (this.activeAction) { + this.activeAction.stop() + this.activeAction = null + } + + const clip = AnimationClip.findByName(this, label) + + if (clip) { + const action = this.mixer.clipAction(clip) + action.timeScale = (clip.tracks.length * fps) / clip.duration + this.activeAction = action.play() + } else { + throw new Error('THREE.MorphAnimMesh: animations[' + label + '] undefined in .playAnimation()') + } + } -MorphAnimMesh.prototype.copy = function (source) { - Mesh.prototype.copy.call(this, source) + updateAnimation(delta) { + this.mixer.update(delta) + } + + copy(source, recursive) { + super.copy(source, recursive) - this.mixer = new AnimationMixer(this) + this.mixer = new AnimationMixer(this) - return this + return this + } } export { MorphAnimMesh } diff --git a/src/misc/MorphBlendMesh.js b/src/misc/MorphBlendMesh.js index f28fa7c2..9bb80d7b 100644 --- a/src/misc/MorphBlendMesh.js +++ b/src/misc/MorphBlendMesh.js @@ -1,32 +1,30 @@ import { MathUtils, Mesh } from 'three' -var MorphBlendMesh = function (geometry, material) { - Mesh.call(this, geometry, material) +class MorphBlendMesh extends Mesh { + constructor(geometry, material) { + super(geometry, material) - this.animationsMap = {} - this.animationsList = [] + this.animationsMap = {} + this.animationsList = [] - // prepare default animation - // (all frames played together in 1 second) + // prepare default animation + // (all frames played together in 1 second) - var numFrames = Object.keys(this.morphTargetDictionary).length + const numFrames = Object.keys(this.morphTargetDictionary).length - var name = '__default' + const name = '__default' - var startFrame = 0 - var endFrame = numFrames - 1 + const startFrame = 0 + const endFrame = numFrames - 1 - var fps = numFrames / 1 + const fps = numFrames / 1 - this.createAnimation(name, startFrame, endFrame, fps) - this.setAnimationWeight(name, 1) -} - -MorphBlendMesh.prototype = Object.assign(Object.create(Mesh.prototype), { - constructor: MorphBlendMesh, + this.createAnimation(name, startFrame, endFrame, fps) + this.setAnimationWeight(name, 1) + } - createAnimation: function (name, start, end, fps) { - var animation = { + createAnimation(name, start, end, fps) { + const animation = { start: start, end: end, @@ -50,25 +48,26 @@ MorphBlendMesh.prototype = Object.assign(Object.create(Mesh.prototype), { this.animationsMap[name] = animation this.animationsList.push(animation) - }, + } - autoCreateAnimations: function (fps) { - var pattern = /([a-z]+)_?(\d+)/i + autoCreateAnimations(fps) { + const pattern = /([a-z]+)_?(\d+)/i - var firstAnimation, - frameRanges = {} + let firstAnimation - var i = 0 + const frameRanges = {} - for (let key in this.morphTargetDictionary) { - var chunks = key.match(pattern) + let i = 0 + + for (const key in this.morphTargetDictionary) { + const chunks = key.match(pattern) if (chunks && chunks.length > 1) { - var name = chunks[1] + const name = chunks[1] if (!frameRanges[name]) frameRanges[name] = { start: Infinity, end: -Infinity } - var range = frameRanges[name] + const range = frameRanges[name] if (i < range.start) range.start = i if (i > range.end) range.end = i @@ -79,92 +78,92 @@ MorphBlendMesh.prototype = Object.assign(Object.create(Mesh.prototype), { i++ } - for (let name in frameRanges) { - var range = frameRanges[name] + for (const name in frameRanges) { + const range = frameRanges[name] this.createAnimation(name, range.start, range.end, fps) } this.firstAnimation = firstAnimation - }, + } - setAnimationDirectionForward: function (name) { - var animation = this.animationsMap[name] + setAnimationDirectionForward(name) { + const animation = this.animationsMap[name] if (animation) { animation.direction = 1 animation.directionBackwards = false } - }, + } - setAnimationDirectionBackward: function (name) { - var animation = this.animationsMap[name] + setAnimationDirectionBackward(name) { + const animation = this.animationsMap[name] if (animation) { animation.direction = -1 animation.directionBackwards = true } - }, + } - setAnimationFPS: function (name, fps) { - var animation = this.animationsMap[name] + setAnimationFPS(name, fps) { + const animation = this.animationsMap[name] if (animation) { animation.fps = fps animation.duration = (animation.end - animation.start) / animation.fps } - }, + } - setAnimationDuration: function (name, duration) { - var animation = this.animationsMap[name] + setAnimationDuration(name, duration) { + const animation = this.animationsMap[name] if (animation) { animation.duration = duration animation.fps = (animation.end - animation.start) / animation.duration } - }, + } - setAnimationWeight: function (name, weight) { - var animation = this.animationsMap[name] + setAnimationWeight(name, weight) { + const animation = this.animationsMap[name] if (animation) { animation.weight = weight } - }, + } - setAnimationTime: function (name, time) { - var animation = this.animationsMap[name] + setAnimationTime(name, time) { + const animation = this.animationsMap[name] if (animation) { animation.time = time } - }, + } - getAnimationTime: function (name) { - var time = 0 + getAnimationTime(name) { + let time = 0 - var animation = this.animationsMap[name] + const animation = this.animationsMap[name] if (animation) { time = animation.time } return time - }, + } - getAnimationDuration: function (name) { - var duration = -1 + getAnimationDuration(name) { + let duration = -1 - var animation = this.animationsMap[name] + const animation = this.animationsMap[name] if (animation) { duration = animation.duration } return duration - }, + } - playAnimation: function (name) { - var animation = this.animationsMap[name] + playAnimation(name) { + const animation = this.animationsMap[name] if (animation) { animation.time = 0 @@ -172,23 +171,23 @@ MorphBlendMesh.prototype = Object.assign(Object.create(Mesh.prototype), { } else { console.warn('THREE.MorphBlendMesh: animation[' + name + '] undefined in .playAnimation()') } - }, + } - stopAnimation: function (name) { - var animation = this.animationsMap[name] + stopAnimation(name) { + const animation = this.animationsMap[name] if (animation) { animation.active = false } - }, + } - update: function (delta) { + update(delta) { for (let i = 0, il = this.animationsList.length; i < il; i++) { - var animation = this.animationsList[i] + const animation = this.animationsList[i] if (!animation.active) continue - var frameTime = animation.duration / animation.length + const frameTime = animation.duration / animation.length animation.time += animation.direction * delta @@ -212,8 +211,9 @@ MorphBlendMesh.prototype = Object.assign(Object.create(Mesh.prototype), { if (animation.time < 0) animation.time += animation.duration } - var keyframe = animation.start + MathUtils.clamp(Math.floor(animation.time / frameTime), 0, animation.length - 1) - var weight = animation.weight + const keyframe = + animation.start + MathUtils.clamp(Math.floor(animation.time / frameTime), 0, animation.length - 1) + const weight = animation.weight if (keyframe !== animation.currentFrame) { this.morphTargetInfluences[animation.lastFrame] = 0 @@ -225,7 +225,7 @@ MorphBlendMesh.prototype = Object.assign(Object.create(Mesh.prototype), { animation.currentFrame = keyframe } - var mix = (animation.time % frameTime) / frameTime + let mix = (animation.time % frameTime) / frameTime if (animation.directionBackwards) mix = 1 - mix @@ -236,7 +236,7 @@ MorphBlendMesh.prototype = Object.assign(Object.create(Mesh.prototype), { this.morphTargetInfluences[animation.currentFrame] = weight } } - }, -}) + } +} export { MorphBlendMesh } diff --git a/src/misc/ProgressiveLightmap.d.ts b/src/misc/ProgressiveLightmap.d.ts index 53c065b0..4c3d45e9 100644 --- a/src/misc/ProgressiveLightmap.d.ts +++ b/src/misc/ProgressiveLightmap.d.ts @@ -5,7 +5,6 @@ import { MeshBasicMaterial, MeshPhongMaterial, Object3D, - PlaneBufferGeometry, PlaneGeometry, Texture, Vector3, @@ -43,7 +42,7 @@ export class ProgressiveLightMap { uv_boxes: UVBoxes[] - blurringPlane: Mesh + blurringPlane: Mesh labelMaterial: MeshBasicMaterial labelPlane: PlaneGeometry diff --git a/src/misc/RollerCoaster.js b/src/misc/RollerCoaster.js index 7e7d5168..d1e5dece 100644 --- a/src/misc/RollerCoaster.js +++ b/src/misc/RollerCoaster.js @@ -1,485 +1,489 @@ -import { BufferAttribute, BufferGeometry, Quaternion, Raycaster, Vector3 } from 'three' +import { BufferAttribute, BufferGeometry, Color, Quaternion, Raycaster, Vector3 } from 'three' -var RollerCoasterGeometry = function (curve, divisions) { - BufferGeometry.call(this) +class RollerCoasterGeometry extends BufferGeometry { + constructor(curve, divisions) { + super() - var vertices = [] - var normals = [] - var colors = [] + const vertices = [] + const normals = [] + const colors = [] - var color1 = [1, 1, 1] - var color2 = [1, 1, 0] + const color1 = [1, 1, 1] + const color2 = [1, 1, 0] - var up = new Vector3(0, 1, 0) - var forward = new Vector3() - var right = new Vector3() + const up = new Vector3(0, 1, 0) + const forward = new Vector3() + const right = new Vector3() - var quaternion = new Quaternion() - var prevQuaternion = new Quaternion() - prevQuaternion.setFromAxisAngle(up, Math.PI / 2) + const quaternion = new Quaternion() + const prevQuaternion = new Quaternion() + prevQuaternion.setFromAxisAngle(up, Math.PI / 2) - var point = new Vector3() - var prevPoint = new Vector3() - prevPoint.copy(curve.getPointAt(0)) + const point = new Vector3() + const prevPoint = new Vector3() + prevPoint.copy(curve.getPointAt(0)) - // shapes + // shapes - var step = [ - new Vector3(-0.225, 0, 0), - new Vector3(0, -0.05, 0), - new Vector3(0, -0.175, 0), + const step = [ + new Vector3(-0.225, 0, 0), + new Vector3(0, -0.05, 0), + new Vector3(0, -0.175, 0), - new Vector3(0, -0.05, 0), - new Vector3(0.225, 0, 0), - new Vector3(0, -0.175, 0), - ] + new Vector3(0, -0.05, 0), + new Vector3(0.225, 0, 0), + new Vector3(0, -0.175, 0), + ] - var PI2 = Math.PI * 2 + const PI2 = Math.PI * 2 - var sides = 5 - var tube1 = [] + let sides = 5 + const tube1 = [] - for (let i = 0; i < sides; i++) { - var angle = (i / sides) * PI2 - tube1.push(new Vector3(Math.sin(angle) * 0.06, Math.cos(angle) * 0.06, 0)) - } + for (let i = 0; i < sides; i++) { + const angle = (i / sides) * PI2 + tube1.push(new Vector3(Math.sin(angle) * 0.06, Math.cos(angle) * 0.06, 0)) + } - var sides = 6 - var tube2 = [] + sides = 6 + const tube2 = [] - for (let i = 0; i < sides; i++) { - var angle = (i / sides) * PI2 - tube2.push(new Vector3(Math.sin(angle) * 0.025, Math.cos(angle) * 0.025, 0)) - } + for (let i = 0; i < sides; i++) { + const angle = (i / sides) * PI2 + tube2.push(new Vector3(Math.sin(angle) * 0.025, Math.cos(angle) * 0.025, 0)) + } - var vector = new Vector3() - var normal = new Vector3() + const vector = new Vector3() + const normal = new Vector3() - function drawShape(shape, color) { - normal.set(0, 0, -1).applyQuaternion(quaternion) + function drawShape(shape, color) { + normal.set(0, 0, -1).applyQuaternion(quaternion) - for (let j = 0; j < shape.length; j++) { - vector.copy(shape[j]) - vector.applyQuaternion(quaternion) - vector.add(point) + for (let j = 0; j < shape.length; j++) { + vector.copy(shape[j]) + vector.applyQuaternion(quaternion) + vector.add(point) - vertices.push(vector.x, vector.y, vector.z) - normals.push(normal.x, normal.y, normal.z) - colors.push(color[0], color[1], color[2]) - } + vertices.push(vector.x, vector.y, vector.z) + normals.push(normal.x, normal.y, normal.z) + colors.push(color[0], color[1], color[2]) + } - normal.set(0, 0, 1).applyQuaternion(quaternion) + normal.set(0, 0, 1).applyQuaternion(quaternion) - for (let j = shape.length - 1; j >= 0; j--) { - vector.copy(shape[j]) - vector.applyQuaternion(quaternion) - vector.add(point) + for (let j = shape.length - 1; j >= 0; j--) { + vector.copy(shape[j]) + vector.applyQuaternion(quaternion) + vector.add(point) - vertices.push(vector.x, vector.y, vector.z) - normals.push(normal.x, normal.y, normal.z) - colors.push(color[0], color[1], color[2]) + vertices.push(vector.x, vector.y, vector.z) + normals.push(normal.x, normal.y, normal.z) + colors.push(color[0], color[1], color[2]) + } } - } - var vector1 = new Vector3() - var vector2 = new Vector3() - var vector3 = new Vector3() - var vector4 = new Vector3() + const vector1 = new Vector3() + const vector2 = new Vector3() + const vector3 = new Vector3() + const vector4 = new Vector3() - var normal1 = new Vector3() - var normal2 = new Vector3() - var normal3 = new Vector3() - var normal4 = new Vector3() + const normal1 = new Vector3() + const normal2 = new Vector3() + const normal3 = new Vector3() + const normal4 = new Vector3() - function extrudeShape(shape, offset, color) { - for (let j = 0, jl = shape.length; j < jl; j++) { - var point1 = shape[j] - var point2 = shape[(j + 1) % jl] + function extrudeShape(shape, offset, color) { + for (let j = 0, jl = shape.length; j < jl; j++) { + const point1 = shape[j] + const point2 = shape[(j + 1) % jl] - vector1.copy(point1).add(offset) - vector1.applyQuaternion(quaternion) - vector1.add(point) + vector1.copy(point1).add(offset) + vector1.applyQuaternion(quaternion) + vector1.add(point) - vector2.copy(point2).add(offset) - vector2.applyQuaternion(quaternion) - vector2.add(point) + vector2.copy(point2).add(offset) + vector2.applyQuaternion(quaternion) + vector2.add(point) - vector3.copy(point2).add(offset) - vector3.applyQuaternion(prevQuaternion) - vector3.add(prevPoint) + vector3.copy(point2).add(offset) + vector3.applyQuaternion(prevQuaternion) + vector3.add(prevPoint) - vector4.copy(point1).add(offset) - vector4.applyQuaternion(prevQuaternion) - vector4.add(prevPoint) + vector4.copy(point1).add(offset) + vector4.applyQuaternion(prevQuaternion) + vector4.add(prevPoint) - vertices.push(vector1.x, vector1.y, vector1.z) - vertices.push(vector2.x, vector2.y, vector2.z) - vertices.push(vector4.x, vector4.y, vector4.z) + vertices.push(vector1.x, vector1.y, vector1.z) + vertices.push(vector2.x, vector2.y, vector2.z) + vertices.push(vector4.x, vector4.y, vector4.z) - vertices.push(vector2.x, vector2.y, vector2.z) - vertices.push(vector3.x, vector3.y, vector3.z) - vertices.push(vector4.x, vector4.y, vector4.z) + vertices.push(vector2.x, vector2.y, vector2.z) + vertices.push(vector3.x, vector3.y, vector3.z) + vertices.push(vector4.x, vector4.y, vector4.z) - // + // - normal1.copy(point1) - normal1.applyQuaternion(quaternion) - normal1.normalize() + normal1.copy(point1) + normal1.applyQuaternion(quaternion) + normal1.normalize() - normal2.copy(point2) - normal2.applyQuaternion(quaternion) - normal2.normalize() + normal2.copy(point2) + normal2.applyQuaternion(quaternion) + normal2.normalize() - normal3.copy(point2) - normal3.applyQuaternion(prevQuaternion) - normal3.normalize() + normal3.copy(point2) + normal3.applyQuaternion(prevQuaternion) + normal3.normalize() - normal4.copy(point1) - normal4.applyQuaternion(prevQuaternion) - normal4.normalize() + normal4.copy(point1) + normal4.applyQuaternion(prevQuaternion) + normal4.normalize() - normals.push(normal1.x, normal1.y, normal1.z) - normals.push(normal2.x, normal2.y, normal2.z) - normals.push(normal4.x, normal4.y, normal4.z) + normals.push(normal1.x, normal1.y, normal1.z) + normals.push(normal2.x, normal2.y, normal2.z) + normals.push(normal4.x, normal4.y, normal4.z) - normals.push(normal2.x, normal2.y, normal2.z) - normals.push(normal3.x, normal3.y, normal3.z) - normals.push(normal4.x, normal4.y, normal4.z) + normals.push(normal2.x, normal2.y, normal2.z) + normals.push(normal3.x, normal3.y, normal3.z) + normals.push(normal4.x, normal4.y, normal4.z) - colors.push(color[0], color[1], color[2]) - colors.push(color[0], color[1], color[2]) - colors.push(color[0], color[1], color[2]) + colors.push(color[0], color[1], color[2]) + colors.push(color[0], color[1], color[2]) + colors.push(color[0], color[1], color[2]) - colors.push(color[0], color[1], color[2]) - colors.push(color[0], color[1], color[2]) - colors.push(color[0], color[1], color[2]) + colors.push(color[0], color[1], color[2]) + colors.push(color[0], color[1], color[2]) + colors.push(color[0], color[1], color[2]) + } } - } - var offset = new Vector3() + const offset = new Vector3() - for (let i = 1; i <= divisions; i++) { - point.copy(curve.getPointAt(i / divisions)) + for (let i = 1; i <= divisions; i++) { + point.copy(curve.getPointAt(i / divisions)) - up.set(0, 1, 0) + up.set(0, 1, 0) - forward.subVectors(point, prevPoint).normalize() - right.crossVectors(up, forward).normalize() - up.crossVectors(forward, right) + forward.subVectors(point, prevPoint).normalize() + right.crossVectors(up, forward).normalize() + up.crossVectors(forward, right) - var angle = Math.atan2(forward.x, forward.z) + const angle = Math.atan2(forward.x, forward.z) - quaternion.setFromAxisAngle(up, angle) + quaternion.setFromAxisAngle(up, angle) - if (i % 2 === 0) { - drawShape(step, color2) - } + if (i % 2 === 0) { + drawShape(step, color2) + } - extrudeShape(tube1, offset.set(0, -0.125, 0), color2) - extrudeShape(tube2, offset.set(0.2, 0, 0), color1) - extrudeShape(tube2, offset.set(-0.2, 0, 0), color1) + extrudeShape(tube1, offset.set(0, -0.125, 0), color2) + extrudeShape(tube2, offset.set(0.2, 0, 0), color1) + extrudeShape(tube2, offset.set(-0.2, 0, 0), color1) - prevPoint.copy(point) - prevQuaternion.copy(quaternion) - } + prevPoint.copy(point) + prevQuaternion.copy(quaternion) + } - // console.log( vertices.length ); + // console.log( vertices.length ); - this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) - this.setAttribute('normal', new BufferAttribute(new Float32Array(normals), 3)) - this.setAttribute('color', new BufferAttribute(new Float32Array(colors), 3)) + this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) + this.setAttribute('normal', new BufferAttribute(new Float32Array(normals), 3)) + this.setAttribute('color', new BufferAttribute(new Float32Array(colors), 3)) + } } -RollerCoasterGeometry.prototype = Object.create(BufferGeometry.prototype) +class RollerCoasterLiftersGeometry extends BufferGeometry { + constructor(curve, divisions) { + super() -var RollerCoasterLiftersGeometry = function (curve, divisions) { - BufferGeometry.call(this) + const vertices = [] + const normals = [] - var vertices = [] - var normals = [] + const quaternion = new Quaternion() - var quaternion = new Quaternion() + const up = new Vector3(0, 1, 0) - var up = new Vector3(0, 1, 0) + const point = new Vector3() + const tangent = new Vector3() - var point = new Vector3() - var tangent = new Vector3() + // shapes - // shapes + const tube1 = [new Vector3(0, 0.05, -0.05), new Vector3(0, 0.05, 0.05), new Vector3(0, -0.05, 0)] - var tube1 = [new Vector3(0, 0.05, -0.05), new Vector3(0, 0.05, 0.05), new Vector3(0, -0.05, 0)] + const tube2 = [new Vector3(-0.05, 0, 0.05), new Vector3(-0.05, 0, -0.05), new Vector3(0.05, 0, 0)] - var tube2 = [new Vector3(-0.05, 0, 0.05), new Vector3(-0.05, 0, -0.05), new Vector3(0.05, 0, 0)] + const tube3 = [new Vector3(0.05, 0, -0.05), new Vector3(0.05, 0, 0.05), new Vector3(-0.05, 0, 0)] - var tube3 = [new Vector3(0.05, 0, -0.05), new Vector3(0.05, 0, 0.05), new Vector3(-0.05, 0, 0)] + const vector1 = new Vector3() + const vector2 = new Vector3() + const vector3 = new Vector3() + const vector4 = new Vector3() - var vector1 = new Vector3() - var vector2 = new Vector3() - var vector3 = new Vector3() - var vector4 = new Vector3() + const normal1 = new Vector3() + const normal2 = new Vector3() + const normal3 = new Vector3() + const normal4 = new Vector3() - var normal1 = new Vector3() - var normal2 = new Vector3() - var normal3 = new Vector3() - var normal4 = new Vector3() + function extrudeShape(shape, fromPoint, toPoint) { + for (let j = 0, jl = shape.length; j < jl; j++) { + const point1 = shape[j] + const point2 = shape[(j + 1) % jl] - function extrudeShape(shape, fromPoint, toPoint) { - for (let j = 0, jl = shape.length; j < jl; j++) { - var point1 = shape[j] - var point2 = shape[(j + 1) % jl] + vector1.copy(point1) + vector1.applyQuaternion(quaternion) + vector1.add(fromPoint) - vector1.copy(point1) - vector1.applyQuaternion(quaternion) - vector1.add(fromPoint) + vector2.copy(point2) + vector2.applyQuaternion(quaternion) + vector2.add(fromPoint) - vector2.copy(point2) - vector2.applyQuaternion(quaternion) - vector2.add(fromPoint) + vector3.copy(point2) + vector3.applyQuaternion(quaternion) + vector3.add(toPoint) - vector3.copy(point2) - vector3.applyQuaternion(quaternion) - vector3.add(toPoint) + vector4.copy(point1) + vector4.applyQuaternion(quaternion) + vector4.add(toPoint) - vector4.copy(point1) - vector4.applyQuaternion(quaternion) - vector4.add(toPoint) - - vertices.push(vector1.x, vector1.y, vector1.z) - vertices.push(vector2.x, vector2.y, vector2.z) - vertices.push(vector4.x, vector4.y, vector4.z) + vertices.push(vector1.x, vector1.y, vector1.z) + vertices.push(vector2.x, vector2.y, vector2.z) + vertices.push(vector4.x, vector4.y, vector4.z) - vertices.push(vector2.x, vector2.y, vector2.z) - vertices.push(vector3.x, vector3.y, vector3.z) - vertices.push(vector4.x, vector4.y, vector4.z) + vertices.push(vector2.x, vector2.y, vector2.z) + vertices.push(vector3.x, vector3.y, vector3.z) + vertices.push(vector4.x, vector4.y, vector4.z) - // + // - normal1.copy(point1) - normal1.applyQuaternion(quaternion) - normal1.normalize() + normal1.copy(point1) + normal1.applyQuaternion(quaternion) + normal1.normalize() - normal2.copy(point2) - normal2.applyQuaternion(quaternion) - normal2.normalize() + normal2.copy(point2) + normal2.applyQuaternion(quaternion) + normal2.normalize() - normal3.copy(point2) - normal3.applyQuaternion(quaternion) - normal3.normalize() + normal3.copy(point2) + normal3.applyQuaternion(quaternion) + normal3.normalize() - normal4.copy(point1) - normal4.applyQuaternion(quaternion) - normal4.normalize() + normal4.copy(point1) + normal4.applyQuaternion(quaternion) + normal4.normalize() - normals.push(normal1.x, normal1.y, normal1.z) - normals.push(normal2.x, normal2.y, normal2.z) - normals.push(normal4.x, normal4.y, normal4.z) + normals.push(normal1.x, normal1.y, normal1.z) + normals.push(normal2.x, normal2.y, normal2.z) + normals.push(normal4.x, normal4.y, normal4.z) - normals.push(normal2.x, normal2.y, normal2.z) - normals.push(normal3.x, normal3.y, normal3.z) - normals.push(normal4.x, normal4.y, normal4.z) + normals.push(normal2.x, normal2.y, normal2.z) + normals.push(normal3.x, normal3.y, normal3.z) + normals.push(normal4.x, normal4.y, normal4.z) + } } - } - var fromPoint = new Vector3() - var toPoint = new Vector3() + const fromPoint = new Vector3() + const toPoint = new Vector3() - for (let i = 1; i <= divisions; i++) { - point.copy(curve.getPointAt(i / divisions)) - tangent.copy(curve.getTangentAt(i / divisions)) + for (let i = 1; i <= divisions; i++) { + point.copy(curve.getPointAt(i / divisions)) + tangent.copy(curve.getTangentAt(i / divisions)) - var angle = Math.atan2(tangent.x, tangent.z) + const angle = Math.atan2(tangent.x, tangent.z) - quaternion.setFromAxisAngle(up, angle) + quaternion.setFromAxisAngle(up, angle) - // + // - if (point.y > 10) { - fromPoint.set(-0.75, -0.35, 0) - fromPoint.applyQuaternion(quaternion) - fromPoint.add(point) + if (point.y > 10) { + fromPoint.set(-0.75, -0.35, 0) + fromPoint.applyQuaternion(quaternion) + fromPoint.add(point) - toPoint.set(0.75, -0.35, 0) - toPoint.applyQuaternion(quaternion) - toPoint.add(point) + toPoint.set(0.75, -0.35, 0) + toPoint.applyQuaternion(quaternion) + toPoint.add(point) - extrudeShape(tube1, fromPoint, toPoint) + extrudeShape(tube1, fromPoint, toPoint) - fromPoint.set(-0.7, -0.3, 0) - fromPoint.applyQuaternion(quaternion) - fromPoint.add(point) + fromPoint.set(-0.7, -0.3, 0) + fromPoint.applyQuaternion(quaternion) + fromPoint.add(point) - toPoint.set(-0.7, -point.y, 0) - toPoint.applyQuaternion(quaternion) - toPoint.add(point) + toPoint.set(-0.7, -point.y, 0) + toPoint.applyQuaternion(quaternion) + toPoint.add(point) - extrudeShape(tube2, fromPoint, toPoint) + extrudeShape(tube2, fromPoint, toPoint) - fromPoint.set(0.7, -0.3, 0) - fromPoint.applyQuaternion(quaternion) - fromPoint.add(point) + fromPoint.set(0.7, -0.3, 0) + fromPoint.applyQuaternion(quaternion) + fromPoint.add(point) - toPoint.set(0.7, -point.y, 0) - toPoint.applyQuaternion(quaternion) - toPoint.add(point) + toPoint.set(0.7, -point.y, 0) + toPoint.applyQuaternion(quaternion) + toPoint.add(point) - extrudeShape(tube3, fromPoint, toPoint) - } else { - fromPoint.set(0, -0.2, 0) - fromPoint.applyQuaternion(quaternion) - fromPoint.add(point) + extrudeShape(tube3, fromPoint, toPoint) + } else { + fromPoint.set(0, -0.2, 0) + fromPoint.applyQuaternion(quaternion) + fromPoint.add(point) - toPoint.set(0, -point.y, 0) - toPoint.applyQuaternion(quaternion) - toPoint.add(point) + toPoint.set(0, -point.y, 0) + toPoint.applyQuaternion(quaternion) + toPoint.add(point) - extrudeShape(tube3, fromPoint, toPoint) + extrudeShape(tube3, fromPoint, toPoint) + } } - } - this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) - this.setAttribute('normal', new BufferAttribute(new Float32Array(normals), 3)) + this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) + this.setAttribute('normal', new BufferAttribute(new Float32Array(normals), 3)) + } } -RollerCoasterLiftersGeometry.prototype = Object.create(BufferGeometry.prototype) +class RollerCoasterShadowGeometry extends BufferGeometry { + constructor(curve, divisions) { + super() -var RollerCoasterShadowGeometry = function (curve, divisions) { - BufferGeometry.call(this) + const vertices = [] - var vertices = [] + const up = new Vector3(0, 1, 0) + const forward = new Vector3() - var up = new Vector3(0, 1, 0) - var forward = new Vector3() + const quaternion = new Quaternion() + const prevQuaternion = new Quaternion() + prevQuaternion.setFromAxisAngle(up, Math.PI / 2) - var quaternion = new Quaternion() - var prevQuaternion = new Quaternion() - prevQuaternion.setFromAxisAngle(up, Math.PI / 2) + const point = new Vector3() - var point = new Vector3() + const prevPoint = new Vector3() + prevPoint.copy(curve.getPointAt(0)) + prevPoint.y = 0 - var prevPoint = new Vector3() - prevPoint.copy(curve.getPointAt(0)) - prevPoint.y = 0 + const vector1 = new Vector3() + const vector2 = new Vector3() + const vector3 = new Vector3() + const vector4 = new Vector3() - var vector1 = new Vector3() - var vector2 = new Vector3() - var vector3 = new Vector3() - var vector4 = new Vector3() + for (let i = 1; i <= divisions; i++) { + point.copy(curve.getPointAt(i / divisions)) + point.y = 0 - for (let i = 1; i <= divisions; i++) { - point.copy(curve.getPointAt(i / divisions)) - point.y = 0 + forward.subVectors(point, prevPoint) - forward.subVectors(point, prevPoint) + const angle = Math.atan2(forward.x, forward.z) - var angle = Math.atan2(forward.x, forward.z) + quaternion.setFromAxisAngle(up, angle) - quaternion.setFromAxisAngle(up, angle) + vector1.set(-0.3, 0, 0) + vector1.applyQuaternion(quaternion) + vector1.add(point) - vector1.set(-0.3, 0, 0) - vector1.applyQuaternion(quaternion) - vector1.add(point) + vector2.set(0.3, 0, 0) + vector2.applyQuaternion(quaternion) + vector2.add(point) - vector2.set(0.3, 0, 0) - vector2.applyQuaternion(quaternion) - vector2.add(point) + vector3.set(0.3, 0, 0) + vector3.applyQuaternion(prevQuaternion) + vector3.add(prevPoint) - vector3.set(0.3, 0, 0) - vector3.applyQuaternion(prevQuaternion) - vector3.add(prevPoint) + vector4.set(-0.3, 0, 0) + vector4.applyQuaternion(prevQuaternion) + vector4.add(prevPoint) - vector4.set(-0.3, 0, 0) - vector4.applyQuaternion(prevQuaternion) - vector4.add(prevPoint) + vertices.push(vector1.x, vector1.y, vector1.z) + vertices.push(vector2.x, vector2.y, vector2.z) + vertices.push(vector4.x, vector4.y, vector4.z) - vertices.push(vector1.x, vector1.y, vector1.z) - vertices.push(vector2.x, vector2.y, vector2.z) - vertices.push(vector4.x, vector4.y, vector4.z) + vertices.push(vector2.x, vector2.y, vector2.z) + vertices.push(vector3.x, vector3.y, vector3.z) + vertices.push(vector4.x, vector4.y, vector4.z) - vertices.push(vector2.x, vector2.y, vector2.z) - vertices.push(vector3.x, vector3.y, vector3.z) - vertices.push(vector4.x, vector4.y, vector4.z) + prevPoint.copy(point) + prevQuaternion.copy(quaternion) + } - prevPoint.copy(point) - prevQuaternion.copy(quaternion) + this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) } - - this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) } -RollerCoasterShadowGeometry.prototype = Object.create(BufferGeometry.prototype) +class SkyGeometry extends BufferGeometry { + constructor() { + super() -var SkyGeometry = function () { - BufferGeometry.call(this) + const vertices = [] - var vertices = [] + for (let i = 0; i < 100; i++) { + const x = Math.random() * 800 - 400 + const y = Math.random() * 50 + 50 + const z = Math.random() * 800 - 400 - for (let i = 0; i < 100; i++) { - var x = Math.random() * 800 - 400 - var y = Math.random() * 50 + 50 - var z = Math.random() * 800 - 400 + const size = Math.random() * 40 + 20 - var size = Math.random() * 40 + 20 + vertices.push(x - size, y, z - size) + vertices.push(x + size, y, z - size) + vertices.push(x - size, y, z + size) - vertices.push(x - size, y, z - size) - vertices.push(x + size, y, z - size) - vertices.push(x - size, y, z + size) + vertices.push(x + size, y, z - size) + vertices.push(x + size, y, z + size) + vertices.push(x - size, y, z + size) + } - vertices.push(x + size, y, z - size) - vertices.push(x + size, y, z + size) - vertices.push(x - size, y, z + size) + this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) } - - this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) } -SkyGeometry.prototype = Object.create(BufferGeometry.prototype) +class TreesGeometry extends BufferGeometry { + constructor(landscape) { + super() + + const vertices = [] + const colors = [] -var TreesGeometry = function (landscape) { - BufferGeometry.call(this) + const raycaster = new Raycaster() + raycaster.ray.direction.set(0, -1, 0) - var vertices = [] - var colors = [] + const _color = new Color() - var raycaster = new Raycaster() - raycaster.ray.direction.set(0, -1, 0) + for (let i = 0; i < 2000; i++) { + const x = Math.random() * 500 - 250 + const z = Math.random() * 500 - 250 - for (let i = 0; i < 2000; i++) { - var x = Math.random() * 500 - 250 - var z = Math.random() * 500 - 250 + raycaster.ray.origin.set(x, 50, z) - raycaster.ray.origin.set(x, 50, z) + const intersections = raycaster.intersectObject(landscape) - var intersections = raycaster.intersectObject(landscape) + if (intersections.length === 0) continue - if (intersections.length === 0) continue + const y = intersections[0].point.y - var y = intersections[0].point.y + const height = Math.random() * 5 + 0.5 - var height = Math.random() * 5 + 0.5 + let angle = Math.random() * Math.PI * 2 - var angle = Math.random() * Math.PI * 2 + vertices.push(x + Math.sin(angle), y, z + Math.cos(angle)) + vertices.push(x, y + height, z) + vertices.push(x + Math.sin(angle + Math.PI), y, z + Math.cos(angle + Math.PI)) - vertices.push(x + Math.sin(angle), y, z + Math.cos(angle)) - vertices.push(x, y + height, z) - vertices.push(x + Math.sin(angle + Math.PI), y, z + Math.cos(angle + Math.PI)) + angle += Math.PI / 2 - angle += Math.PI / 2 + vertices.push(x + Math.sin(angle), y, z + Math.cos(angle)) + vertices.push(x, y + height, z) + vertices.push(x + Math.sin(angle + Math.PI), y, z + Math.cos(angle + Math.PI)) - vertices.push(x + Math.sin(angle), y, z + Math.cos(angle)) - vertices.push(x, y + height, z) - vertices.push(x + Math.sin(angle + Math.PI), y, z + Math.cos(angle + Math.PI)) + const random = Math.random() * 0.1 - var random = Math.random() * 0.1 + for (let j = 0; j < 6; j++) { + _color.setRGB(0.2 + random, 0.4 + random, 0, 'srgb') - for (let j = 0; j < 6; j++) { - colors.push(0.2 + random, 0.4 + random, 0) + colors.push(_color.r, _color.g, _color.b) + } } - } - this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) - this.setAttribute('color', new BufferAttribute(new Float32Array(colors), 3)) + this.setAttribute('position', new BufferAttribute(new Float32Array(vertices), 3)) + this.setAttribute('color', new BufferAttribute(new Float32Array(colors), 3)) + } } -TreesGeometry.prototype = Object.create(BufferGeometry.prototype) - export { RollerCoasterGeometry, RollerCoasterLiftersGeometry, RollerCoasterShadowGeometry, SkyGeometry, TreesGeometry } diff --git a/src/misc/Volume.d.ts b/src/misc/Volume.d.ts index 5a225e0d..983735ea 100644 --- a/src/misc/Volume.d.ts +++ b/src/misc/Volume.d.ts @@ -1,6 +1,6 @@ import { Matrix3 } from 'three' -import { VolumeSlice } from './VolumeSlice.js' +import { VolumeSlice } from './VolumeSlice' export class Volume { constructor(xLength?: number, yLength?: number, zLength?: number, type?: string, arrayBuffer?: ArrayLike) diff --git a/src/misc/Volume.js b/src/misc/Volume.js index fb8ecbd9..ac3c7be8 100644 --- a/src/misc/Volume.js +++ b/src/misc/Volume.js @@ -13,165 +13,171 @@ import { VolumeSlice } from '../misc/VolumeSlice' * @param {string} type The type of data (uint8, uint16, ...) * @param {ArrayBuffer} arrayBuffer The buffer with volume data */ -var Volume = function (xLength, yLength, zLength, type, arrayBuffer) { - if (arguments.length > 0) { +class Volume { + constructor(xLength, yLength, zLength, type, arrayBuffer) { + if (xLength !== undefined) { + /** + * @member {number} xLength Width of the volume in the IJK coordinate system + */ + this.xLength = Number(xLength) || 1 + /** + * @member {number} yLength Height of the volume in the IJK coordinate system + */ + this.yLength = Number(yLength) || 1 + /** + * @member {number} zLength Depth of the volume in the IJK coordinate system + */ + this.zLength = Number(zLength) || 1 + /** + * @member {Array} The order of the Axis dictated by the NRRD header + */ + this.axisOrder = ['x', 'y', 'z'] + /** + * @member {TypedArray} data Data of the volume + */ + + switch (type) { + case 'Uint8': + case 'uint8': + case 'uchar': + case 'unsigned char': + case 'uint8_t': + this.data = new Uint8Array(arrayBuffer) + break + case 'Int8': + case 'int8': + case 'signed char': + case 'int8_t': + this.data = new Int8Array(arrayBuffer) + break + case 'Int16': + case 'int16': + case 'short': + case 'short int': + case 'signed short': + case 'signed short int': + case 'int16_t': + this.data = new Int16Array(arrayBuffer) + break + case 'Uint16': + case 'uint16': + case 'ushort': + case 'unsigned short': + case 'unsigned short int': + case 'uint16_t': + this.data = new Uint16Array(arrayBuffer) + break + case 'Int32': + case 'int32': + case 'int': + case 'signed int': + case 'int32_t': + this.data = new Int32Array(arrayBuffer) + break + case 'Uint32': + case 'uint32': + case 'uint': + case 'unsigned int': + case 'uint32_t': + this.data = new Uint32Array(arrayBuffer) + break + case 'longlong': + case 'long long': + case 'long long int': + case 'signed long long': + case 'signed long long int': + case 'int64': + case 'int64_t': + case 'ulonglong': + case 'unsigned long long': + case 'unsigned long long int': + case 'uint64': + case 'uint64_t': + throw new Error('Error in Volume constructor : this type is not supported in JavaScript') + break + case 'Float32': + case 'float32': + case 'float': + this.data = new Float32Array(arrayBuffer) + break + case 'Float64': + case 'float64': + case 'double': + this.data = new Float64Array(arrayBuffer) + break + default: + this.data = new Uint8Array(arrayBuffer) + } + + if (this.data.length !== this.xLength * this.yLength * this.zLength) { + throw new Error('Error in Volume constructor, lengths are not matching arrayBuffer size') + } + } + + /** + * @member {Array} spacing Spacing to apply to the volume from IJK to RAS coordinate system + */ + this.spacing = [1, 1, 1] + /** + * @member {Array} offset Offset of the volume in the RAS coordinate system + */ + this.offset = [0, 0, 0] + /** + * @member {Martrix3} matrix The IJK to RAS matrix + */ + this.matrix = new Matrix3() + this.matrix.identity() /** - * @member {number} xLength Width of the volume in the IJK coordinate system + * @member {Martrix3} inverseMatrix The RAS to IJK matrix */ - this.xLength = Number(xLength) || 1 /** - * @member {number} yLength Height of the volume in the IJK coordinate system + * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices. + * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume */ - this.yLength = Number(yLength) || 1 + let lowerThreshold = -Infinity + Object.defineProperty(this, 'lowerThreshold', { + get: function () { + return lowerThreshold + }, + set: function (value) { + lowerThreshold = value + this.sliceList.forEach(function (slice) { + slice.geometryNeedsUpdate = true + }) + }, + }) /** - * @member {number} zLength Depth of the volume in the IJK coordinate system + * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices. + * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume */ - this.zLength = Number(zLength) || 1 + let upperThreshold = Infinity + Object.defineProperty(this, 'upperThreshold', { + get: function () { + return upperThreshold + }, + set: function (value) { + upperThreshold = value + this.sliceList.forEach(function (slice) { + slice.geometryNeedsUpdate = true + }) + }, + }) /** - * @member {TypedArray} data Data of the volume + * @member {Array} sliceList The list of all the slices associated to this volume */ + this.sliceList = [] - switch (type) { - case 'Uint8': - case 'uint8': - case 'uchar': - case 'unsigned char': - case 'uint8_t': - this.data = new Uint8Array(arrayBuffer) - break - case 'Int8': - case 'int8': - case 'signed char': - case 'int8_t': - this.data = new Int8Array(arrayBuffer) - break - case 'Int16': - case 'int16': - case 'short': - case 'short int': - case 'signed short': - case 'signed short int': - case 'int16_t': - this.data = new Int16Array(arrayBuffer) - break - case 'Uint16': - case 'uint16': - case 'ushort': - case 'unsigned short': - case 'unsigned short int': - case 'uint16_t': - this.data = new Uint16Array(arrayBuffer) - break - case 'Int32': - case 'int32': - case 'int': - case 'signed int': - case 'int32_t': - this.data = new Int32Array(arrayBuffer) - break - case 'Uint32': - case 'uint32': - case 'uint': - case 'unsigned int': - case 'uint32_t': - this.data = new Uint32Array(arrayBuffer) - break - case 'longlong': - case 'long long': - case 'long long int': - case 'signed long long': - case 'signed long long int': - case 'int64': - case 'int64_t': - case 'ulonglong': - case 'unsigned long long': - case 'unsigned long long int': - case 'uint64': - case 'uint64_t': - throw 'Error in Volume constructor : this type is not supported in JavaScript' - break - case 'Float32': - case 'float32': - case 'float': - this.data = new Float32Array(arrayBuffer) - break - case 'Float64': - case 'float64': - case 'double': - this.data = new Float64Array(arrayBuffer) - break - default: - this.data = new Uint8Array(arrayBuffer) - } + /** + * @member {boolean} segmentation in segmentation mode, it can load 16-bits nrrds correctly + */ + this.segmentation = false - if (this.data.length !== this.xLength * this.yLength * this.zLength) { - throw 'Error in Volume constructor, lengths are not matching arrayBuffer size' - } + /** + * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space + */ } - /** - * @member {Array} spacing Spacing to apply to the volume from IJK to RAS coordinate system - */ - this.spacing = [1, 1, 1] - /** - * @member {Array} offset Offset of the volume in the RAS coordinate system - */ - this.offset = [0, 0, 0] - /** - * @member {Martrix3} matrix The IJK to RAS matrix - */ - this.matrix = new Matrix3() - this.matrix.identity() - /** - * @member {Martrix3} inverseMatrix The RAS to IJK matrix - */ - /** - * @member {number} lowerThreshold The voxels with values under this threshold won't appear in the slices. - * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume - */ - var lowerThreshold = -Infinity - Object.defineProperty(this, 'lowerThreshold', { - get: function () { - return lowerThreshold - }, - set: function (value) { - lowerThreshold = value - this.sliceList.forEach(function (slice) { - slice.geometryNeedsUpdate = true - }) - }, - }) - /** - * @member {number} upperThreshold The voxels with values over this threshold won't appear in the slices. - * If changed, geometryNeedsUpdate is automatically set to true on all the slices associated to this volume - */ - var upperThreshold = Infinity - Object.defineProperty(this, 'upperThreshold', { - get: function () { - return upperThreshold - }, - set: function (value) { - upperThreshold = value - this.sliceList.forEach(function (slice) { - slice.geometryNeedsUpdate = true - }) - }, - }) - - /** - * @member {Array} sliceList The list of all the slices associated to this volume - */ - this.sliceList = [] - - /** - * @member {Array} RASDimensions This array holds the dimensions of the volume in the RAS space - */ -} - -Volume.prototype = { - constructor: Volume, - /** * @member {Function} getData Shortcut for data[access(i,j,k)] * @memberof Volume @@ -180,9 +186,9 @@ Volume.prototype = { * @param {number} k Third coordinate * @returns {number} value in the data array */ - getData: function (i, j, k) { + getData(i, j, k) { return this.data[k * this.xLength * this.yLength + j * this.xLength + i] - }, + } /** * @member {Function} access compute the index in the data array corresponding to the given coordinates in IJK system @@ -192,9 +198,9 @@ Volume.prototype = { * @param {number} k Third coordinate * @returns {number} index */ - access: function (i, j, k) { + access(i, j, k) { return k * this.xLength * this.yLength + j * this.xLength + i - }, + } /** * @member {Function} reverseAccess Retrieve the IJK coordinates of the voxel corresponding of the given index in the data @@ -202,12 +208,12 @@ Volume.prototype = { * @param {number} index index of the voxel * @returns {Array} [x,y,z] */ - reverseAccess: function (index) { - var z = Math.floor(index / (this.yLength * this.xLength)) - var y = Math.floor((index - z * this.yLength * this.xLength) / this.xLength) - var x = index - z * this.yLength * this.xLength - y * this.xLength + reverseAccess(index) { + const z = Math.floor(index / (this.yLength * this.xLength)) + const y = Math.floor((index - z * this.yLength * this.xLength) / this.xLength) + const x = index - z * this.yLength * this.xLength - y * this.xLength return [x, y, z] - }, + } /** * @member {Function} map Apply a function to all the voxels, be careful, the value will be replaced @@ -219,8 +225,8 @@ Volume.prototype = { * @param {Object} context You can specify a context in which call the function, default if this Volume * @returns {Volume} this */ - map: function (functionToMap, context) { - var length = this.data.length + map(functionToMap, context) { + const length = this.data.length context = context || this for (let i = 0; i < length; i++) { @@ -228,7 +234,7 @@ Volume.prototype = { } return this - }, + } /** * @member {Function} extractPerpendicularPlane Compute the orientation of the slice and returns all the information relative to the geometry such as sliceAccess, the plane matrix (orientation and position in RAS coordinate) and the dimensions of the plane in both coordinate system. @@ -237,32 +243,24 @@ Volume.prototype = { * @param {number} index the index of the slice * @returns {Object} an object containing all the usefull information on the geometry of the slice */ - extractPerpendicularPlane: function (axis, RASIndex) { - var iLength, - jLength, - sliceAccess, - planeMatrix = new Matrix4().identity(), - volume = this, - planeWidth, - planeHeight, - firstSpacing, - secondSpacing, - positionOffset, - IJKIndex - - var axisInIJK = new Vector3(), + extractPerpendicularPlane(axis, RASIndex) { + let firstSpacing, secondSpacing, positionOffset, IJKIndex + + const axisInIJK = new Vector3(), firstDirection = new Vector3(), - secondDirection = new Vector3() + secondDirection = new Vector3(), + planeMatrix = new Matrix4().identity(), + volume = this - var dimensions = new Vector3(this.xLength, this.yLength, this.zLength) + const dimensions = new Vector3(this.xLength, this.yLength, this.zLength) switch (axis) { case 'x': axisInIJK.set(1, 0, 0) firstDirection.set(0, 0, -1) secondDirection.set(0, -1, 0) - firstSpacing = this.spacing[2] - secondSpacing = this.spacing[1] + firstSpacing = this.spacing[this.axisOrder.indexOf('z')] + secondSpacing = this.spacing[this.axisOrder.indexOf('y')] IJKIndex = new Vector3(RASIndex, 0, 0) planeMatrix.multiply(new Matrix4().makeRotationY(Math.PI / 2)) @@ -273,8 +271,8 @@ Volume.prototype = { axisInIJK.set(0, 1, 0) firstDirection.set(1, 0, 0) secondDirection.set(0, 0, 1) - firstSpacing = this.spacing[0] - secondSpacing = this.spacing[2] + firstSpacing = this.spacing[this.axisOrder.indexOf('x')] + secondSpacing = this.spacing[this.axisOrder.indexOf('z')] IJKIndex = new Vector3(0, RASIndex, 0) planeMatrix.multiply(new Matrix4().makeRotationX(-Math.PI / 2)) @@ -286,8 +284,8 @@ Volume.prototype = { axisInIJK.set(0, 0, 1) firstDirection.set(1, 0, 0) secondDirection.set(0, -1, 0) - firstSpacing = this.spacing[0] - secondSpacing = this.spacing[1] + firstSpacing = this.spacing[this.axisOrder.indexOf('x')] + secondSpacing = this.spacing[this.axisOrder.indexOf('y')] IJKIndex = new Vector3(0, 0, RASIndex) positionOffset = (volume.RASDimensions[2] - 1) / 2 @@ -295,40 +293,42 @@ Volume.prototype = { break } - firstDirection.applyMatrix4(volume.inverseMatrix).normalize() - firstDirection.argVar = 'i' - secondDirection.applyMatrix4(volume.inverseMatrix).normalize() - secondDirection.argVar = 'j' - axisInIJK.applyMatrix4(volume.inverseMatrix).normalize() + let iLength, jLength + + if (!this.segmentation) { + firstDirection.applyMatrix4(volume.inverseMatrix).normalize() + secondDirection.applyMatrix4(volume.inverseMatrix).normalize() + axisInIJK.applyMatrix4(volume.inverseMatrix).normalize() + } + firstDirection.arglet = 'i' + secondDirection.arglet = 'j' iLength = Math.floor(Math.abs(firstDirection.dot(dimensions))) jLength = Math.floor(Math.abs(secondDirection.dot(dimensions))) - planeWidth = Math.abs(iLength * firstSpacing) - planeHeight = Math.abs(jLength * secondSpacing) + const planeWidth = Math.abs(iLength * firstSpacing) + const planeHeight = Math.abs(jLength * secondSpacing) IJKIndex = Math.abs(Math.round(IJKIndex.applyMatrix4(volume.inverseMatrix).dot(axisInIJK))) - var base = [new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1)] - var iDirection = [firstDirection, secondDirection, axisInIJK].find(function (x) { + const base = [new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(0, 0, 1)] + const iDirection = [firstDirection, secondDirection, axisInIJK].find(function (x) { return Math.abs(x.dot(base[0])) > 0.9 }) - var jDirection = [firstDirection, secondDirection, axisInIJK].find(function (x) { + const jDirection = [firstDirection, secondDirection, axisInIJK].find(function (x) { return Math.abs(x.dot(base[1])) > 0.9 }) - var kDirection = [firstDirection, secondDirection, axisInIJK].find(function (x) { + const kDirection = [firstDirection, secondDirection, axisInIJK].find(function (x) { return Math.abs(x.dot(base[2])) > 0.9 }) - sliceAccess = function (i, j) { - var accessI, accessJ, accessK - - var si = iDirection === axisInIJK ? IJKIndex : iDirection.argVar === 'i' ? i : j - var sj = jDirection === axisInIJK ? IJKIndex : jDirection.argVar === 'i' ? i : j - var sk = kDirection === axisInIJK ? IJKIndex : kDirection.argVar === 'i' ? i : j + function sliceAccess(i, j) { + const si = iDirection === axisInIJK ? IJKIndex : iDirection.arglet === 'i' ? i : j + const sj = jDirection === axisInIJK ? IJKIndex : jDirection.arglet === 'i' ? i : j + const sk = kDirection === axisInIJK ? IJKIndex : kDirection.arglet === 'i' ? i : j // invert indices if necessary - var accessI = iDirection.dot(base[0]) > 0 ? si : volume.xLength - 1 - si - var accessJ = jDirection.dot(base[1]) > 0 ? sj : volume.yLength - 1 - sj - var accessK = kDirection.dot(base[2]) > 0 ? sk : volume.zLength - 1 - sk + const accessI = iDirection.dot(base[0]) > 0 ? si : volume.xLength - 1 - si + const accessJ = jDirection.dot(base[1]) > 0 ? sj : volume.yLength - 1 - sj + const accessK = kDirection.dot(base[2]) > 0 ? sk : volume.zLength - 1 - sk return volume.access(accessI, accessJ, accessK) } @@ -341,7 +341,7 @@ Volume.prototype = { planeWidth: planeWidth, planeHeight: planeHeight, } - }, + } /** * @member {Function} extractSlice Returns a slice corresponding to the given axis and index @@ -351,11 +351,11 @@ Volume.prototype = { * @param {number} index the index of the slice * @returns {VolumeSlice} the extracted slice */ - extractSlice: function (axis, index) { - var slice = new VolumeSlice(this, index, axis) + extractSlice(axis, index) { + const slice = new VolumeSlice(this, index, axis) this.sliceList.push(slice) return slice - }, + } /** * @member {Function} repaintAllSlices Call repaint on all the slices extracted from this volume @@ -363,31 +363,31 @@ Volume.prototype = { * @memberof Volume * @returns {Volume} this */ - repaintAllSlices: function () { + repaintAllSlices() { this.sliceList.forEach(function (slice) { slice.repaint() }) return this - }, + } /** * @member {Function} computeMinMax Compute the minimum and the maximum of the data in the volume * @memberof Volume * @returns {Array} [min,max] */ - computeMinMax: function () { - var min = Infinity - var max = -Infinity + computeMinMax() { + let min = Infinity + let max = -Infinity // buffer the length - var datasize = this.data.length + const datasize = this.data.length - var i = 0 + let i = 0 for (i = 0; i < datasize; i++) { if (!isNaN(this.data[i])) { - var value = this.data[i] + const value = this.data[i] min = Math.min(min, value) max = Math.max(max, value) } @@ -397,7 +397,7 @@ Volume.prototype = { this.max = max return [min, max] - }, + } } export { Volume } diff --git a/src/misc/VolumeSlice.js b/src/misc/VolumeSlice.js index 15bb71dc..ff4e992e 100644 --- a/src/misc/VolumeSlice.js +++ b/src/misc/VolumeSlice.js @@ -8,96 +8,92 @@ import { ClampToEdgeWrapping, DoubleSide, LinearFilter, Mesh, MeshBasicMaterial, * @param {string} [axis='z'] For now only 'x', 'y' or 'z' but later it will change to a normal vector * @see Volume */ -var VolumeSlice = function (volume, index, axis) { - var slice = this - /** - * @member {Volume} volume The associated volume - */ - this.volume = volume - /** - * @member {Number} index The index of the slice, if changed, will automatically call updateGeometry at the next repaint - */ - index = index || 0 - Object.defineProperty(this, 'index', { - get: function () { - return index - }, - set: function (value) { - index = value - slice.geometryNeedsUpdate = true - return index - }, - }) - /** - * @member {String} axis The normal axis - */ - this.axis = axis || 'z' - - /** - * @member {HTMLCanvasElement} canvas The final canvas used for the texture - */ - /** - * @member {CanvasRenderingContext2D} ctx Context of the canvas - */ - this.canvas = document.createElement('canvas') - /** - * @member {HTMLCanvasElement} canvasBuffer The intermediary canvas used to paint the data - */ - /** - * @member {CanvasRenderingContext2D} ctxBuffer Context of the canvas buffer - */ - this.canvasBuffer = document.createElement('canvas') - this.updateGeometry() - - var canvasMap = new Texture(this.canvas) - canvasMap.minFilter = LinearFilter - canvasMap.wrapS = canvasMap.wrapT = ClampToEdgeWrapping - var material = new MeshBasicMaterial({ - map: canvasMap, - side: DoubleSide, - transparent: true, - }) - /** - * @member {Mesh} mesh The mesh ready to get used in the scene - */ - this.mesh = new Mesh(this.geometry, material) - this.mesh.matrixAutoUpdate = false - /** - * @member {Boolean} geometryNeedsUpdate If set to true, updateGeometry will be triggered at the next repaint - */ - this.geometryNeedsUpdate = true - this.repaint() - - /** - * @member {Number} iLength Width of slice in the original coordinate system, corresponds to the width of the buffer canvas - */ - - /** - * @member {Number} jLength Height of slice in the original coordinate system, corresponds to the height of the buffer canvas - */ - - /** - * @member {Function} sliceAccess Function that allow the slice to access right data - * @see Volume.extractPerpendicularPlane - * @param {Number} i The first coordinate - * @param {Number} j The second coordinate - * @returns {Number} the index corresponding to the voxel in volume.data of the given position in the slice - */ -} - -VolumeSlice.prototype = { - constructor: VolumeSlice, +class VolumeSlice { + constructor(volume, index, axis) { + const slice = this + /** + * @member {Volume} volume The associated volume + */ + this.volume = volume + /** + * @member {Number} index The index of the slice, if changed, will automatically call updateGeometry at the next repaint + */ + index = index || 0 + Object.defineProperty(this, 'index', { + get: function () { + return index + }, + set: function (value) { + index = value + slice.geometryNeedsUpdate = true + return index + }, + }) + /** + * @member {String} axis The normal axis + */ + this.axis = axis || 'z' + + /** + * @member {HTMLCanvasElement} canvas The final canvas used for the texture + */ + /** + * @member {CanvasRenderingContext2D} ctx Context of the canvas + */ + this.canvas = document.createElement('canvas') + /** + * @member {HTMLCanvasElement} canvasBuffer The intermediary canvas used to paint the data + */ + /** + * @member {CanvasRenderingContext2D} ctxBuffer Context of the canvas buffer + */ + this.canvasBuffer = document.createElement('canvas') + this.updateGeometry() + + const canvasMap = new Texture(this.canvas) + canvasMap.minFilter = LinearFilter + canvasMap.wrapS = canvasMap.wrapT = ClampToEdgeWrapping + if ('colorSpace' in canvasMap) canvasMap.colorSpace = 'srgb' + else canvasMap.encoding = 3001 // sRGBEncoding + const material = new MeshBasicMaterial({ map: canvasMap, side: DoubleSide, transparent: true }) + /** + * @member {Mesh} mesh The mesh ready to get used in the scene + */ + this.mesh = new Mesh(this.geometry, material) + this.mesh.matrixAutoUpdate = false + /** + * @member {Boolean} geometryNeedsUpdate If set to true, updateGeometry will be triggered at the next repaint + */ + this.geometryNeedsUpdate = true + this.repaint() + + /** + * @member {Number} iLength Width of slice in the original coordinate system, corresponds to the width of the buffer canvas + */ + + /** + * @member {Number} jLength Height of slice in the original coordinate system, corresponds to the height of the buffer canvas + */ + + /** + * @member {Function} sliceAccess Function that allow the slice to access right data + * @see Volume.extractPerpendicularPlane + * @param {Number} i The first coordinate + * @param {Number} j The second coordinate + * @returns {Number} the index corresponding to the voxel in volume.data of the given position in the slice + */ + } /** * @member {Function} repaint Refresh the texture and the geometry if geometryNeedsUpdate is set to true * @memberof VolumeSlice */ - repaint: function () { + repaint() { if (this.geometryNeedsUpdate) { this.updateGeometry() } - var iLength = this.iLength, + const iLength = this.iLength, jLength = this.jLength, sliceAccess = this.sliceAccess, volume = this.volume, @@ -105,24 +101,24 @@ VolumeSlice.prototype = { ctx = this.ctxBuffer // get the imageData and pixel array from the canvas - var imgData = ctx.getImageData(0, 0, iLength, jLength) - var data = imgData.data - var volumeData = volume.data - var upperThreshold = volume.upperThreshold - var lowerThreshold = volume.lowerThreshold - var windowLow = volume.windowLow - var windowHigh = volume.windowHigh + const imgData = ctx.getImageData(0, 0, iLength, jLength) + const data = imgData.data + const volumeData = volume.data + const upperThreshold = volume.upperThreshold + const lowerThreshold = volume.lowerThreshold + const windowLow = volume.windowLow + const windowHigh = volume.windowHigh // manipulate some pixel elements - var pixelCount = 0 + let pixelCount = 0 if (volume.dataType === 'label') { //this part is currently useless but will be used when colortables will be handled for (let j = 0; j < jLength; j++) { for (let i = 0; i < iLength; i++) { - var label = volumeData[sliceAccess(i, j)] + let label = volumeData[sliceAccess(i, j)] label = label >= this.colorMap.length ? (label % this.colorMap.length) + 1 : label - var color = this.colorMap[label] + const color = this.colorMap[label] data[4 * pixelCount] = (color >> 24) & 0xff data[4 * pixelCount + 1] = (color >> 16) & 0xff data[4 * pixelCount + 2] = (color >> 8) & 0xff @@ -133,8 +129,8 @@ VolumeSlice.prototype = { } else { for (let j = 0; j < jLength; j++) { for (let i = 0; i < iLength; i++) { - var value = volumeData[sliceAccess(i, j)] - var alpha = 0xff + let value = volumeData[sliceAccess(i, j)] + let alpha = 0xff //apply threshold alpha = upperThreshold >= value ? (lowerThreshold <= value ? alpha : 0) : 0 //apply window level @@ -154,15 +150,15 @@ VolumeSlice.prototype = { this.ctx.drawImage(canvas, 0, 0, iLength, jLength, 0, 0, this.canvas.width, this.canvas.height) this.mesh.material.map.needsUpdate = true - }, + } /** * @member {Function} Refresh the geometry according to axis and index * @see Volume.extractPerpendicularPlane * @memberof VolumeSlice */ - updateGeometry: function () { - var extracted = this.volume.extractPerpendicularPlane(this.axis, this.index) + updateGeometry() { + const extracted = this.volume.extractPerpendicularPlane(this.axis, this.index) this.sliceAccess = extracted.sliceAccess this.jLength = extracted.jLength this.iLength = extracted.iLength @@ -187,7 +183,7 @@ VolumeSlice.prototype = { } this.geometryNeedsUpdate = false - }, + } } export { VolumeSlice } diff --git a/src/modifiers/CurveModifier.ts b/src/modifiers/CurveModifier.ts index 84d00332..af5397eb 100644 --- a/src/modifiers/CurveModifier.ts +++ b/src/modifiers/CurveModifier.ts @@ -251,7 +251,7 @@ export class Flow { this.uniforms.pathOffset.value += amount } } -const matrix = new Matrix4() +const matrix = /* @__PURE__ */ new Matrix4() /** * A helper class for creating instanced versions of flow, where the instances are placed on the curve. diff --git a/src/modifiers/EdgeSplitModifier.ts b/src/modifiers/EdgeSplitModifier.ts index 2fd9a35f..3682ba04 100644 --- a/src/modifiers/EdgeSplitModifier.ts +++ b/src/modifiers/EdgeSplitModifier.ts @@ -180,7 +180,7 @@ class EdgeSplitModifier { for (let attribute of Object.values(newAttributes)) { for (let j = 0; j < attribute.itemSize; j++) { - // @ts-expect-error ArrayLike can't be mutated, but this works – https://github.com/three-types/three-ts-types/issues/35 + // @ts-ignore ArrayLike can't be mutated, but this works – https://github.com/three-types/three-ts-types/issues/35 attribute.array[(this.indexes.length + i) * attribute.itemSize + j] = attribute.array[index * attribute.itemSize + j] } @@ -209,7 +209,7 @@ class EdgeSplitModifier { for (let i = 0; i < changedNormals.length; i++) { if (changedNormals[i] === false) { for (let j = 0; j < 3; j++) { - // @ts-expect-error ArrayLike can't be mutated, but this works – https://github.com/three-types/three-ts-types/issues/35 + // @ts-ignore ArrayLike can't be mutated, but this works – https://github.com/three-types/three-ts-types/issues/35 geometry.attributes.normal.array[3 * i + j] = this.oldNormals[3 * i + j] } } diff --git a/src/modifiers/SimplifyModifier.ts b/src/modifiers/SimplifyModifier.ts index cf7a6956..d9d0348d 100644 --- a/src/modifiers/SimplifyModifier.ts +++ b/src/modifiers/SimplifyModifier.ts @@ -1,8 +1,8 @@ import { BufferGeometry, Float32BufferAttribute, Vector3 } from 'three' import * as BufferGeometryUtils from '../utils/BufferGeometryUtils' -const cb = new Vector3(), - ab = new Vector3() +const cb = /* @__PURE__ */ new Vector3() +const ab = /* @__PURE__ */ new Vector3() function pushIfUnique(array: TItem[], object: TItem): void { if (array.indexOf(object) === -1) array.push(object) diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js deleted file mode 100644 index f7675b09..00000000 --- a/src/nodes/Nodes.js +++ /dev/null @@ -1,251 +0,0 @@ -// core -import ArrayUniformNode from './core/ArrayUniformNode.js' -import AttributeNode from './core/AttributeNode.js' -import BypassNode from './core/BypassNode.js' -import CodeNode from './core/CodeNode.js' -import ConstNode from './core/ConstNode.js' -import ContextNode from './core/ContextNode.js' -import ExpressionNode from './core/ExpressionNode.js' -import FunctionCallNode from './core/FunctionCallNode.js' -import FunctionNode from './core/FunctionNode.js' -import Node from './core/Node.js' -import NodeAttribute from './core/NodeAttribute.js' -import NodeBuilder from './core/NodeBuilder.js' -import NodeCode from './core/NodeCode.js' -import NodeFrame from './core/NodeFrame.js' -import NodeFunctionInput from './core/NodeFunctionInput.js' -import NodeKeywords from './core/NodeKeywords.js' -import NodeUniform from './core/NodeUniform.js' -import NodeVar from './core/NodeVar.js' -import NodeVary from './core/NodeVary.js' -import PropertyNode from './core/PropertyNode.js' -import TempNode from './core/TempNode.js' -import UniformNode from './core/UniformNode.js' -import VarNode from './core/VarNode.js' -import VaryNode from './core/VaryNode.js' - -// accessors -import BufferNode from './accessors/BufferNode.js' -import CameraNode from './accessors/CameraNode.js' -import CubeTextureNode from './accessors/CubeTextureNode.js' -import MaterialNode from './accessors/MaterialNode.js' -import MaterialReferenceNode from './accessors/MaterialReferenceNode.js' -import ModelNode from './accessors/ModelNode.js' -import ModelViewProjectionNode from './accessors/ModelViewProjectionNode.js' -import NormalNode from './accessors/NormalNode.js' -import Object3DNode from './accessors/Object3DNode.js' -import PointUVNode from './accessors/PointUVNode.js' -import PositionNode from './accessors/PositionNode.js' -import ReferenceNode from './accessors/ReferenceNode.js' -import ReflectNode from './accessors/ReflectNode.js' -import SkinningNode from './accessors/SkinningNode.js' -import TextureNode from './accessors/TextureNode.js' -import UVNode from './accessors/UVNode.js' - -// display -import ColorSpaceNode from './display/ColorSpaceNode.js' -import NormalMapNode from './display/NormalMapNode.js' - -// math -import MathNode from './math/MathNode.js' -import OperatorNode from './math/OperatorNode.js' -import CondNode from './math/CondNode.js' - -// lights -import LightContextNode from './lights/LightContextNode.js' -import LightNode from './lights/LightNode.js' -import LightsNode from './lights/LightsNode.js' - -// utils -import ArrayElementNode from './utils/ArrayElementNode.js' -import ConvertNode from './utils/ConvertNode.js' -import JoinNode from './utils/JoinNode.js' -import SplitNode from './utils/SplitNode.js' -import SpriteSheetUVNode from './utils/SpriteSheetUVNode.js' -import MatcapUVNode from './utils/MatcapUVNode.js' -import OscNode from './utils/OscNode.js' -import TimerNode from './utils/TimerNode.js' - -// loaders -import NodeLoader from './loaders/NodeLoader.js' -import NodeObjectLoader from './loaders/NodeObjectLoader.js' -import NodeMaterialLoader from './loaders/NodeMaterialLoader.js' - -// procedural -import CheckerNode from './procedural/CheckerNode.js' - -// fog -import FogNode from './fog/FogNode.js' -import FogRangeNode from './fog/FogRangeNode.js' - -// core -export * from './core/constants.js' - -// functions -export * from './functions/BSDFs.js' - -// materials -export * from './materials/Materials.js' - -// shader node -export * from './ShaderNode.js' - -const nodeLib = { - // core - ArrayUniformNode, - AttributeNode, - BypassNode, - CodeNode, - ContextNode, - ConstNode, - ExpressionNode, - FunctionCallNode, - FunctionNode, - Node, - NodeAttribute, - NodeBuilder, - NodeCode, - NodeFrame, - NodeFunctionInput, - NodeKeywords, - NodeUniform, - NodeVar, - NodeVary, - PropertyNode, - TempNode, - UniformNode, - VarNode, - VaryNode, - - // accessors - BufferNode, - CameraNode, - CubeTextureNode, - MaterialNode, - MaterialReferenceNode, - ModelNode, - ModelViewProjectionNode, - NormalNode, - Object3DNode, - PointUVNode, - PositionNode, - ReferenceNode, - ReflectNode, - SkinningNode, - TextureNode, - UVNode, - - // display - ColorSpaceNode, - NormalMapNode, - - // math - MathNode, - OperatorNode, - CondNode, - - // lights - LightContextNode, - LightNode, - LightsNode, - - // utils - ArrayElementNode, - ConvertNode, - JoinNode, - SplitNode, - SpriteSheetUVNode, - MatcapUVNode, - OscNode, - TimerNode, - - // procedural - CheckerNode, - - // fog - FogNode, - FogRangeNode, - - // loaders - NodeLoader, - NodeObjectLoader, - NodeMaterialLoader, -} - -export const fromType = (type) => { - return new nodeLib[type]() -} - -export { - // core - ArrayUniformNode, - AttributeNode, - BypassNode, - CodeNode, - ContextNode, - ConstNode, - ExpressionNode, - FunctionCallNode, - FunctionNode, - Node, - NodeAttribute, - NodeBuilder, - NodeCode, - NodeFrame, - NodeFunctionInput, - NodeKeywords, - NodeUniform, - NodeVar, - NodeVary, - PropertyNode, - TempNode, - UniformNode, - VarNode, - VaryNode, - // accessors - BufferNode, - CameraNode, - CubeTextureNode, - MaterialNode, - MaterialReferenceNode, - ModelNode, - ModelViewProjectionNode, - NormalNode, - Object3DNode, - PointUVNode, - PositionNode, - ReferenceNode, - ReflectNode, - SkinningNode, - TextureNode, - UVNode, - // display - ColorSpaceNode, - NormalMapNode, - // math - MathNode, - OperatorNode, - CondNode, - // lights - LightContextNode, - LightNode, - LightsNode, - // utils - ArrayElementNode, - ConvertNode, - JoinNode, - SplitNode, - SpriteSheetUVNode, - MatcapUVNode, - OscNode, - TimerNode, - // procedural - CheckerNode, - // fog - FogNode, - FogRangeNode, - // loaders - NodeLoader, - NodeObjectLoader, - NodeMaterialLoader, -} diff --git a/src/nodes/ShaderNode.js b/src/nodes/ShaderNode.js deleted file mode 100644 index 82bf3c16..00000000 --- a/src/nodes/ShaderNode.js +++ /dev/null @@ -1,359 +0,0 @@ -// core nodes -import PropertyNode from './core/PropertyNode.js' -import VarNode from './core/VarNode.js' -import AttributeNode from './core/AttributeNode.js' -import ConstNode from './core/ConstNode.js' -import UniformNode from './core/UniformNode.js' - -// accessor nodes -import BufferNode from './accessors/BufferNode.js' -import PositionNode from './accessors/PositionNode.js' -import NormalNode from './accessors/NormalNode.js' -import CameraNode from './accessors/CameraNode.js' -import ModelNode from './accessors/ModelNode.js' -import TextureNode from './accessors/TextureNode.js' -import UVNode from './accessors/UVNode.js' - -// math nodes -import OperatorNode from './math/OperatorNode.js' -import CondNode from './math/CondNode.js' -import MathNode from './math/MathNode.js' - -// util nodes -import ArrayElementNode from './utils/ArrayElementNode.js' -import ConvertNode from './utils/ConvertNode.js' -import JoinNode from './utils/JoinNode.js' -import SplitNode from './utils/SplitNode.js' - -// utils -import { getValueFromType } from './core/NodeUtils.js' - -const NodeHandler = { - construct(NodeClosure, params) { - const inputs = params.shift() - - return NodeClosure(new ShaderNodeObjects(inputs), ...params) - }, - - get: function (node, prop) { - if (typeof prop === 'string' && node[prop] === undefined) { - if (/^[xyzwrgbastpq]{1,4}$/.test(prop) === true) { - // accessing properties ( swizzle ) - - prop = prop.replace(/r|s/g, 'x').replace(/g|t/g, 'y').replace(/b|p/g, 'z').replace(/a|q/g, 'w') - - return new ShaderNodeObject(new SplitNode(node, prop)) - } else if (/^\d+$/.test(prop) === true) { - // accessing array - - return new ShaderNodeObject(new ArrayElementNode(node, uint(Number(prop)))) - } - } - - return node[prop] - }, -} - -const nodeObjects = new WeakMap() - -const ShaderNodeObject = function (obj) { - const type = typeof obj - - if (type === 'number' || type === 'boolean') { - return new ShaderNodeObject(getAutoTypedConstNode(obj)) - } else if (type === 'object') { - if (obj.isNode === true) { - let nodeObject = nodeObjects.get(obj) - - if (nodeObject === undefined) { - nodeObject = new Proxy(obj, NodeHandler) - nodeObjects.set(obj, nodeObject) - nodeObjects.set(nodeObject, nodeObject) - } - - return nodeObject - } - } - - return obj -} - -const ShaderNodeObjects = function (objects) { - for (const name in objects) { - objects[name] = new ShaderNodeObject(objects[name]) - } - - return objects -} - -const getShaderNodeArray = (array) => { - const len = array.length - - for (let i = 0; i < len; i++) { - array[i] = new ShaderNodeObject(array[i]) - } - - return array -} - -const ShaderNodeProxy = function (NodeClass, scope = null, factor = null) { - if (scope === null) { - return (...params) => { - return new ShaderNodeObject(new NodeClass(...getShaderNodeArray(params))) - } - } else if (factor === null) { - return (...params) => { - return new ShaderNodeObject(new NodeClass(scope, ...getShaderNodeArray(params))) - } - } else { - factor = new ShaderNodeObject(factor) - - return (...params) => { - return new ShaderNodeObject(new NodeClass(scope, ...getShaderNodeArray(params), factor)) - } - } -} - -const ShaderNodeScript = function (jsFunc) { - return (inputs, builder) => { - new ShaderNodeObjects(inputs) - - return new ShaderNodeObject(jsFunc(inputs, builder)) - } -} - -const bools = [false, true] -const uints = [0, 1, 2, 3] -const ints = [-1, -2] -const floats = [ - 0.5, - 1.5, - 1 / 3, - 1e-6, - 1e6, - Math.PI, - Math.PI * 2, - 1 / Math.PI, - 2 / Math.PI, - 1 / (Math.PI * 2), - Math.PI / 2, -] - -const boolsCacheMap = new Map() -for (let bool of bools) boolsCacheMap.set(bool, new ConstNode(bool)) - -const uintsCacheMap = new Map() -for (let uint of uints) uintsCacheMap.set(uint, new ConstNode(uint, 'uint')) - -const intsCacheMap = new Map([...uintsCacheMap].map((el) => new ConstNode(el.value, 'int'))) -for (let int of ints) intsCacheMap.set(int, new ConstNode(int, 'int')) - -const floatsCacheMap = new Map([...intsCacheMap].map((el) => new ConstNode(el.value))) -for (let float of floats) floatsCacheMap.set(float, new ConstNode(float)) -for (let float of floats) floatsCacheMap.set(-float, new ConstNode(-float)) - -const constNodesCacheMap = new Map([...boolsCacheMap, ...floatsCacheMap]) - -const getAutoTypedConstNode = (value) => { - if (constNodesCacheMap.has(value)) { - return constNodesCacheMap.get(value) - } else if (value.isNode === true) { - return value - } else { - return new ConstNode(value) - } -} - -const ConvertType = function (type, cacheMap = null) { - return (...params) => { - if (params.length === 0) { - return nodeObject(new ConstNode(getValueFromType(type), type)) - } else { - if (type === 'color' && params[0].isNode !== true) { - params = [getValueFromType(type, ...params)] - } - - if (params.length === 1 && cacheMap !== null && cacheMap.has(params[0])) { - return cacheMap.get(params[0]) - } - - const nodes = params.map(getAutoTypedConstNode) - - return nodeObject(new ConvertNode(nodes.length === 1 ? nodes[0] : new JoinNode(nodes), type)) - } - } -} - -// -// Node Material Shader Syntax -// - -export const ShaderNode = new Proxy(ShaderNodeScript, NodeHandler) - -export const nodeObject = (val) => { - return new ShaderNodeObject(val) -} - -export const uniform = (value) => { - // TODO: get ConstNode from .traverse() in the future - value = value.isNode === true ? value.node?.value || value.value : value - - return nodeObject(new UniformNode(value, value.nodeType)) -} - -export const label = (node, name) => { - node = nodeObject(node) - - if (node.isVarNode === true) { - node.name = name - - return node - } - - return nodeObject(new VarNode(node, name)) -} - -export const temp = (node) => nodeObject(new VarNode(nodeObject(node))) - -export const color = new ConvertType('color') - -export const float = new ConvertType('float', floatsCacheMap) -export const int = new ConvertType('int', intsCacheMap) -export const uint = new ConvertType('uint', uintsCacheMap) -export const bool = new ConvertType('bool', boolsCacheMap) - -export const vec2 = new ConvertType('vec2') -export const ivec2 = new ConvertType('ivec2') -export const uvec2 = new ConvertType('uvec2') -export const bvec2 = new ConvertType('bvec2') - -export const vec3 = new ConvertType('vec3') -export const ivec3 = new ConvertType('ivec3') -export const uvec3 = new ConvertType('uvec3') -export const bvec3 = new ConvertType('bvec3') - -export const vec4 = new ConvertType('vec4') -export const ivec4 = new ConvertType('ivec4') -export const uvec4 = new ConvertType('uvec4') -export const bvec4 = new ConvertType('bvec4') - -export const mat3 = new ConvertType('mat3') -export const imat3 = new ConvertType('imat3') -export const umat3 = new ConvertType('umat3') -export const bmat3 = new ConvertType('bmat3') - -export const mat4 = new ConvertType('mat4') -export const imat4 = new ConvertType('imat4') -export const umat4 = new ConvertType('umat4') -export const bmat4 = new ConvertType('bmat4') - -export const join = (...params) => nodeObject(new JoinNode(getShaderNodeArray(params))) - -export const uv = (...params) => nodeObject(new UVNode(...params)) -export const attribute = (...params) => nodeObject(new AttributeNode(...params)) -export const buffer = (...params) => nodeObject(new BufferNode(...params)) -export const texture = (...params) => nodeObject(new TextureNode(...params)) -export const sampler = (texture) => - nodeObject(new ConvertNode(texture.isNode === true ? texture : new TextureNode(texture), 'sampler')) - -export const cond = (...params) => nodeObject(new CondNode(...getShaderNodeArray(params))) - -export const addTo = (varNode, ...params) => { - varNode.node = add(varNode.node, ...getShaderNodeArray(params)) - - return nodeObject(varNode) -} - -export const add = new ShaderNodeProxy(OperatorNode, '+') -export const sub = new ShaderNodeProxy(OperatorNode, '-') -export const mul = new ShaderNodeProxy(OperatorNode, '*') -export const div = new ShaderNodeProxy(OperatorNode, '/') -export const remainder = new ShaderNodeProxy(OperatorNode, '%') -export const equal = new ShaderNodeProxy(OperatorNode, '==') -export const assign = new ShaderNodeProxy(OperatorNode, '=') -export const lessThan = new ShaderNodeProxy(OperatorNode, '<') -export const greaterThan = new ShaderNodeProxy(OperatorNode, '>') -export const lessThanEqual = new ShaderNodeProxy(OperatorNode, '<=') -export const greaterThanEqual = new ShaderNodeProxy(OperatorNode, '>=') -export const and = new ShaderNodeProxy(OperatorNode, '&&') -export const or = new ShaderNodeProxy(OperatorNode, '||') -export const xor = new ShaderNodeProxy(OperatorNode, '^^') -export const bitAnd = new ShaderNodeProxy(OperatorNode, '&') -export const bitOr = new ShaderNodeProxy(OperatorNode, '|') -export const bitXor = new ShaderNodeProxy(OperatorNode, '^') -export const shiftLeft = new ShaderNodeProxy(OperatorNode, '<<') -export const shiftRight = new ShaderNodeProxy(OperatorNode, '>>') - -export const element = new ShaderNodeProxy(ArrayElementNode) - -export const normalGeometry = new ShaderNodeObject(new NormalNode(NormalNode.GEOMETRY)) -export const normalLocal = new ShaderNodeObject(new NormalNode(NormalNode.LOCAL)) -export const normalWorld = new ShaderNodeObject(new NormalNode(NormalNode.WORLD)) -export const normalView = new ShaderNodeObject(new NormalNode(NormalNode.VIEW)) -export const transformedNormalView = new ShaderNodeObject( - new VarNode(new NormalNode(NormalNode.VIEW), 'TransformedNormalView', 'vec3'), -) - -export const positionLocal = new ShaderNodeObject(new PositionNode(PositionNode.LOCAL)) -export const positionWorld = new ShaderNodeObject(new PositionNode(PositionNode.WORLD)) -export const positionView = new ShaderNodeObject(new PositionNode(PositionNode.VIEW)) -export const positionViewDirection = new ShaderNodeObject(new PositionNode(PositionNode.VIEW_DIRECTION)) - -export const viewMatrix = new ShaderNodeObject(new ModelNode(ModelNode.VIEW_MATRIX)) - -export const cameraPosition = new ShaderNodeObject(new CameraNode(CameraNode.POSITION)) - -export const diffuseColor = new ShaderNodeObject(new PropertyNode('DiffuseColor', 'vec4')) -export const roughness = new ShaderNodeObject(new PropertyNode('Roughness', 'float')) -export const metalness = new ShaderNodeObject(new PropertyNode('Metalness', 'float')) -export const alphaTest = new ShaderNodeObject(new PropertyNode('AlphaTest', 'float')) -export const specularColor = new ShaderNodeObject(new PropertyNode('SpecularColor', 'color')) - -export const abs = new ShaderNodeProxy(MathNode, 'abs') -export const acos = new ShaderNodeProxy(MathNode, 'acos') -export const asin = new ShaderNodeProxy(MathNode, 'asin') -export const atan = new ShaderNodeProxy(MathNode, 'atan') -export const ceil = new ShaderNodeProxy(MathNode, 'ceil') -export const clamp = new ShaderNodeProxy(MathNode, 'clamp') -export const cos = new ShaderNodeProxy(MathNode, 'cos') -export const cross = new ShaderNodeProxy(MathNode, 'cross') -export const degrees = new ShaderNodeProxy(MathNode, 'degrees') -export const dFdx = new ShaderNodeProxy(MathNode, 'dFdx') -export const dFdy = new ShaderNodeProxy(MathNode, 'dFdy') -export const distance = new ShaderNodeProxy(MathNode, 'distance') -export const dot = new ShaderNodeProxy(MathNode, 'dot') -export const exp = new ShaderNodeProxy(MathNode, 'exp') -export const exp2 = new ShaderNodeProxy(MathNode, 'exp2') -export const faceforward = new ShaderNodeProxy(MathNode, 'faceforward') -export const floor = new ShaderNodeProxy(MathNode, 'floor') -export const fract = new ShaderNodeProxy(MathNode, 'fract') -export const invert = new ShaderNodeProxy(MathNode, 'invert') -export const inversesqrt = new ShaderNodeProxy(MathNode, 'inversesqrt') -export const length = new ShaderNodeProxy(MathNode, 'length') -export const log = new ShaderNodeProxy(MathNode, 'log') -export const log2 = new ShaderNodeProxy(MathNode, 'log2') -export const max = new ShaderNodeProxy(MathNode, 'max') -export const min = new ShaderNodeProxy(MathNode, 'min') -export const mix = new ShaderNodeProxy(MathNode, 'mix') -export const mod = new ShaderNodeProxy(MathNode, 'mod') -export const negate = new ShaderNodeProxy(MathNode, 'negate') -export const normalize = new ShaderNodeProxy(MathNode, 'normalize') -export const pow = new ShaderNodeProxy(MathNode, 'pow') -export const pow2 = new ShaderNodeProxy(MathNode, 'pow', 2) -export const pow3 = new ShaderNodeProxy(MathNode, 'pow', 3) -export const pow4 = new ShaderNodeProxy(MathNode, 'pow', 4) -export const radians = new ShaderNodeProxy(MathNode, 'radians') -export const reflect = new ShaderNodeProxy(MathNode, 'reflect') -export const refract = new ShaderNodeProxy(MathNode, 'refract') -export const round = new ShaderNodeProxy(MathNode, 'round') -export const saturate = new ShaderNodeProxy(MathNode, 'saturate') -export const sign = new ShaderNodeProxy(MathNode, 'sign') -export const sin = new ShaderNodeProxy(MathNode, 'sin') -export const smoothstep = new ShaderNodeProxy(MathNode, 'smoothstep') -export const sqrt = new ShaderNodeProxy(MathNode, 'sqrt') -export const step = new ShaderNodeProxy(MathNode, 'step') -export const tan = new ShaderNodeProxy(MathNode, 'tan') -export const transformDirection = new ShaderNodeProxy(MathNode, 'transformDirection') - -export const EPSILON = float(1e-6) -export const INFINITY = float(1e6) diff --git a/src/nodes/accessors/BufferNode.js b/src/nodes/accessors/BufferNode.js deleted file mode 100644 index 067ea3ca..00000000 --- a/src/nodes/accessors/BufferNode.js +++ /dev/null @@ -1,18 +0,0 @@ -import UniformNode from '../core/UniformNode.js' - -class BufferNode extends UniformNode { - constructor(value, bufferType, bufferCount = 0) { - super(value, bufferType) - - this.bufferType = bufferType - this.bufferCount = bufferCount - } - - getInputType(/*builder*/) { - return 'buffer' - } -} - -BufferNode.prototype.isBufferNode = true - -export default BufferNode diff --git a/src/nodes/accessors/CameraNode.js b/src/nodes/accessors/CameraNode.js deleted file mode 100644 index 41eb74d3..00000000 --- a/src/nodes/accessors/CameraNode.js +++ /dev/null @@ -1,47 +0,0 @@ -import Object3DNode from './Object3DNode.js' - -class CameraNode extends Object3DNode { - static PROJECTION_MATRIX = 'projectionMatrix' - - constructor(scope = CameraNode.POSITION) { - super(scope) - } - - getNodeType(builder) { - const scope = this.scope - - if (scope === CameraNode.PROJECTION_MATRIX) { - return 'mat4' - } - - return super.getNodeType(builder) - } - - update(frame) { - const camera = frame.camera - const uniformNode = this._uniformNode - const scope = this.scope - - if (scope === CameraNode.PROJECTION_MATRIX) { - uniformNode.value = camera.projectionMatrix - } else if (scope === CameraNode.VIEW_MATRIX) { - uniformNode.value = camera.matrixWorldInverse - } else { - this.object3d = camera - - super.update(frame) - } - } - - generate(builder) { - const scope = this.scope - - if (scope === CameraNode.PROJECTION_MATRIX) { - this._uniformNode.nodeType = 'mat4' - } - - return super.generate(builder) - } -} - -export default CameraNode diff --git a/src/nodes/accessors/CubeTextureNode.js b/src/nodes/accessors/CubeTextureNode.js deleted file mode 100644 index 093cc9bd..00000000 --- a/src/nodes/accessors/CubeTextureNode.js +++ /dev/null @@ -1,54 +0,0 @@ -import TextureNode from './TextureNode.js' -import UniformNode from '../core/UniformNode.js' -import ReflectNode from './ReflectNode.js' - -class CubeTextureNode extends TextureNode { - constructor(value, uvNode = new ReflectNode(), biasNode = null) { - super(value, uvNode, biasNode) - } - - getInputType(/*builder*/) { - return 'cubeTexture' - } - - generate(builder, output) { - const texture = this.value - - if (!texture || texture.isCubeTexture !== true) { - throw new Error('CubeTextureNode: Need a three.js cube texture.') - } - - const textureProperty = UniformNode.prototype.generate.call(this, builder, 'cubeTexture') - - if (output === 'sampler') { - return textureProperty + '_sampler' - } else if (builder.isReference(output)) { - return textureProperty - } else { - const nodeData = builder.getDataFromNode(this) - - let snippet = nodeData.snippet - - if (snippet === undefined) { - const uvSnippet = this.uvNode.build(builder, 'vec3') - const biasNode = this.biasNode - - if (biasNode !== null) { - const biasSnippet = biasNode.build(builder, 'float') - - snippet = builder.getCubeTextureBias(textureProperty, uvSnippet, biasSnippet) - } else { - snippet = builder.getCubeTexture(textureProperty, uvSnippet) - } - - nodeData.snippet = snippet - } - - return builder.format(snippet, 'vec4', output) - } - } -} - -CubeTextureNode.prototype.isCubeTextureNode = true - -export default CubeTextureNode diff --git a/src/nodes/accessors/MaterialNode.js b/src/nodes/accessors/MaterialNode.js deleted file mode 100644 index bff29c43..00000000 --- a/src/nodes/accessors/MaterialNode.js +++ /dev/null @@ -1,80 +0,0 @@ -import Node from '../core/Node.js' -import OperatorNode from '../math/OperatorNode.js' -import MaterialReferenceNode from './MaterialReferenceNode.js' - -class MaterialNode extends Node { - static ALPHA_TEST = 'alphaTest' - static COLOR = 'color' - static OPACITY = 'opacity' - static SPECULAR = 'specular' - static ROUGHNESS = 'roughness' - static METALNESS = 'metalness' - - constructor(scope = MaterialNode.COLOR) { - super() - - this.scope = scope - } - - getNodeType(builder) { - const scope = this.scope - const material = builder.context.material - - if (scope === MaterialNode.COLOR) { - return material.map !== null ? 'vec4' : 'vec3' - } else if (scope === MaterialNode.OPACITY) { - return 'float' - } else if (scope === MaterialNode.SPECULAR) { - return 'vec3' - } else if (scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS) { - return 'float' - } - } - - generate(builder, output) { - const material = builder.context.material - const scope = this.scope - - let node = null - - if (scope === MaterialNode.ALPHA_TEST) { - node = new MaterialReferenceNode('alphaTest', 'float') - } else if (scope === MaterialNode.COLOR) { - const colorNode = new MaterialReferenceNode('color', 'color') - - if (material.map !== null && material.map !== undefined && material.map.isTexture === true) { - node = new OperatorNode('*', colorNode, new MaterialReferenceNode('map', 'texture')) - } else { - node = colorNode - } - } else if (scope === MaterialNode.OPACITY) { - const opacityNode = new MaterialReferenceNode('opacity', 'float') - - if (material.alphaMap !== null && material.alphaMap !== undefined && material.alphaMap.isTexture === true) { - node = new OperatorNode('*', opacityNode, new MaterialReferenceNode('alphaMap', 'texture')) - } else { - node = opacityNode - } - } else if (scope === MaterialNode.SPECULAR) { - const specularColorNode = new MaterialReferenceNode('specularColor', 'color') - - if ( - material.specularColorMap !== null && - material.specularColorMap !== undefined && - material.specularColorMap.isTexture === true - ) { - node = new OperatorNode('*', specularColorNode, new MaterialReferenceNode('specularColorMap', 'texture')) - } else { - node = specularColorNode - } - } else { - const outputType = this.getNodeType(builder) - - node = new MaterialReferenceNode(scope, outputType) - } - - return node.build(builder, output) - } -} - -export default MaterialNode diff --git a/src/nodes/accessors/MaterialReferenceNode.js b/src/nodes/accessors/MaterialReferenceNode.js deleted file mode 100644 index 1c0ea442..00000000 --- a/src/nodes/accessors/MaterialReferenceNode.js +++ /dev/null @@ -1,17 +0,0 @@ -import ReferenceNode from './ReferenceNode.js' - -class MaterialReferenceNode extends ReferenceNode { - constructor(property, inputType, material = null) { - super(property, inputType, material) - - this.material = material - } - - update(frame) { - this.object = this.material !== null ? this.material : frame.material - - super.update(frame) - } -} - -export default MaterialReferenceNode diff --git a/src/nodes/accessors/ModelNode.js b/src/nodes/accessors/ModelNode.js deleted file mode 100644 index b84cee12..00000000 --- a/src/nodes/accessors/ModelNode.js +++ /dev/null @@ -1,9 +0,0 @@ -import Object3DNode from './Object3DNode.js' - -class ModelNode extends Object3DNode { - constructor(scope = ModelNode.VIEW_MATRIX) { - super(scope) - } -} - -export default ModelNode diff --git a/src/nodes/accessors/ModelViewProjectionNode.js b/src/nodes/accessors/ModelViewProjectionNode.js deleted file mode 100644 index f2f3c8f2..00000000 --- a/src/nodes/accessors/ModelViewProjectionNode.js +++ /dev/null @@ -1,28 +0,0 @@ -import Node from '../core/Node.js' -import CameraNode from '../accessors/CameraNode.js' -import ModelNode from '../accessors/ModelNode.js' -import OperatorNode from '../math/OperatorNode.js' -import PositionNode from '../accessors/PositionNode.js' - -class ModelViewProjectionNode extends Node { - constructor(position = new PositionNode()) { - super('vec4') - - this.position = position - } - - generate(builder) { - const position = this.position - - const mvpMatrix = new OperatorNode( - '*', - new CameraNode(CameraNode.PROJECTION_MATRIX), - new ModelNode(ModelNode.VIEW_MATRIX), - ) - const mvpNode = new OperatorNode('*', mvpMatrix, position) - - return mvpNode.build(builder) - } -} - -export default ModelViewProjectionNode diff --git a/src/nodes/accessors/NormalNode.js b/src/nodes/accessors/NormalNode.js deleted file mode 100644 index f7f5b96e..00000000 --- a/src/nodes/accessors/NormalNode.js +++ /dev/null @@ -1,67 +0,0 @@ -import Node from '../core/Node.js' -import AttributeNode from '../core/AttributeNode.js' -import VaryNode from '../core/VaryNode.js' -import ModelNode from '../accessors/ModelNode.js' -import CameraNode from '../accessors/CameraNode.js' -import OperatorNode from '../math/OperatorNode.js' -import MathNode from '../math/MathNode.js' - -class NormalNode extends Node { - static GEOMETRY = 'geometry' - static LOCAL = 'local' - static WORLD = 'world' - static VIEW = 'view' - - constructor(scope = NormalNode.LOCAL) { - super('vec3') - - this.scope = scope - } - - getHash(/*builder*/) { - return `normal-${this.scope}` - } - - generate(builder) { - const scope = this.scope - - let outputNode = null - - if (scope === NormalNode.GEOMETRY) { - outputNode = new AttributeNode('normal', 'vec3') - } else if (scope === NormalNode.LOCAL) { - outputNode = new VaryNode(new NormalNode(NormalNode.GEOMETRY)) - } else if (scope === NormalNode.VIEW) { - const vertexNormalNode = new OperatorNode( - '*', - new ModelNode(ModelNode.NORMAL_MATRIX), - new NormalNode(NormalNode.LOCAL), - ) - outputNode = new MathNode(MathNode.NORMALIZE, new VaryNode(vertexNormalNode)) - } else if (scope === NormalNode.WORLD) { - // To use INVERSE_TRANSFORM_DIRECTION only inverse the param order like this: MathNode( ..., Vector, Matrix ); - const vertexNormalNode = new MathNode( - MathNode.TRANSFORM_DIRECTION, - new NormalNode(NormalNode.VIEW), - new CameraNode(CameraNode.VIEW_MATRIX), - ) - outputNode = new MathNode(MathNode.NORMALIZE, new VaryNode(vertexNormalNode)) - } - - return outputNode.build(builder) - } - - serialize(data) { - super.serialize(data) - - data.scope = this.scope - } - - deserialize(data) { - super.deserialize(data) - - this.scope = data.scope - } -} - -export default NormalNode diff --git a/src/nodes/accessors/Object3DNode.js b/src/nodes/accessors/Object3DNode.js deleted file mode 100644 index eb71bcf0..00000000 --- a/src/nodes/accessors/Object3DNode.js +++ /dev/null @@ -1,85 +0,0 @@ -import { Vector3 } from 'three' -import Node from '../core/Node.js' -import UniformNode from '../core/UniformNode.js' -import { NodeUpdateType } from '../core/constants.js' - -class Object3DNode extends Node { - static VIEW_MATRIX = 'viewMatrix' - static NORMAL_MATRIX = 'normalMatrix' - static WORLD_MATRIX = 'worldMatrix' - static POSITION = 'position' - static VIEW_POSITION = 'viewPosition' - - constructor(scope = Object3DNode.VIEW_MATRIX, object3d = null) { - super() - - this.scope = scope - this.object3d = object3d - - this.updateType = NodeUpdateType.Object - - this._uniformNode = new UniformNode(null) - } - - getNodeType() { - const scope = this.scope - - if (scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX) { - return 'mat4' - } else if (scope === Object3DNode.NORMAL_MATRIX) { - return 'mat3' - } else if (scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION) { - return 'vec3' - } - } - - update(frame) { - const object = this.object3d !== null ? this.object3d : frame.object - const uniformNode = this._uniformNode - const camera = frame.camera - const scope = this.scope - - if (scope === Object3DNode.VIEW_MATRIX) { - uniformNode.value = object.modelViewMatrix - } else if (scope === Object3DNode.NORMAL_MATRIX) { - uniformNode.value = object.normalMatrix - } else if (scope === Object3DNode.WORLD_MATRIX) { - uniformNode.value = object.matrixWorld - } else if (scope === Object3DNode.POSITION) { - uniformNode.value.setFromMatrixPosition(object.matrixWorld) - } else if (scope === Object3DNode.VIEW_POSITION) { - uniformNode.value.setFromMatrixPosition(object.matrixWorld) - - uniformNode.value.applyMatrix4(camera.matrixWorldInverse) - } - } - - generate(builder) { - const scope = this.scope - - if (scope === Object3DNode.WORLD_MATRIX || scope === Object3DNode.VIEW_MATRIX) { - this._uniformNode.nodeType = 'mat4' - } else if (scope === Object3DNode.NORMAL_MATRIX) { - this._uniformNode.nodeType = 'mat3' - } else if (scope === Object3DNode.POSITION || scope === Object3DNode.VIEW_POSITION) { - this._uniformNode.nodeType = 'vec3' - this._uniformNode.value = new Vector3() - } - - return this._uniformNode.build(builder) - } - - serialize(data) { - super.serialize(data) - - data.scope = this.scope - } - - deserialize(data) { - super.deserialize(data) - - this.scope = data.scope - } -} - -export default Object3DNode diff --git a/src/nodes/accessors/PointUVNode.js b/src/nodes/accessors/PointUVNode.js deleted file mode 100644 index 2a3f4ff5..00000000 --- a/src/nodes/accessors/PointUVNode.js +++ /dev/null @@ -1,15 +0,0 @@ -import Node from '../core/Node.js' - -class PointUVNode extends Node { - constructor() { - super('vec2') - } - - generate(/*builder*/) { - return 'vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y )' - } -} - -PointUVNode.prototype.isPointUVNode = true - -export default PointUVNode diff --git a/src/nodes/accessors/PositionNode.js b/src/nodes/accessors/PositionNode.js deleted file mode 100644 index 488dd8e1..00000000 --- a/src/nodes/accessors/PositionNode.js +++ /dev/null @@ -1,69 +0,0 @@ -import Node from '../core/Node.js' -import AttributeNode from '../core/AttributeNode.js' -import VaryNode from '../core/VaryNode.js' -import ModelNode from '../accessors/ModelNode.js' -import MathNode from '../math/MathNode.js' -import OperatorNode from '../math/OperatorNode.js' - -class PositionNode extends Node { - static GEOMETRY = 'geometry' - static LOCAL = 'local' - static WORLD = 'world' - static VIEW = 'view' - static VIEW_DIRECTION = 'viewDirection' - - constructor(scope = PositionNode.LOCAL) { - super('vec3') - - this.scope = scope - } - - getHash(/*builder*/) { - return `position-${this.scope}` - } - - generate(builder) { - const scope = this.scope - - let outputNode = null - - if (scope === PositionNode.GEOMETRY) { - outputNode = new AttributeNode('position', 'vec3') - } else if (scope === PositionNode.LOCAL) { - outputNode = new VaryNode(new PositionNode(PositionNode.GEOMETRY)) - } else if (scope === PositionNode.WORLD) { - const vertexPositionNode = new MathNode( - MathNode.TRANSFORM_DIRECTION, - new ModelNode(ModelNode.WORLD_MATRIX), - new PositionNode(PositionNode.LOCAL), - ) - outputNode = new VaryNode(vertexPositionNode) - } else if (scope === PositionNode.VIEW) { - const vertexPositionNode = new OperatorNode( - '*', - new ModelNode(ModelNode.VIEW_MATRIX), - new PositionNode(PositionNode.LOCAL), - ) - outputNode = new VaryNode(vertexPositionNode) - } else if (scope === PositionNode.VIEW_DIRECTION) { - const vertexPositionNode = new MathNode(MathNode.NEGATE, new PositionNode(PositionNode.VIEW)) - outputNode = new MathNode(MathNode.NORMALIZE, new VaryNode(vertexPositionNode)) - } - - return outputNode.build(builder, this.getNodeType(builder)) - } - - serialize(data) { - super.serialize(data) - - data.scope = this.scope - } - - deserialize(data) { - super.deserialize(data) - - this.scope = data.scope - } -} - -export default PositionNode diff --git a/src/nodes/accessors/ReferenceNode.js b/src/nodes/accessors/ReferenceNode.js deleted file mode 100644 index db323870..00000000 --- a/src/nodes/accessors/ReferenceNode.js +++ /dev/null @@ -1,49 +0,0 @@ -import Node from '../core/Node.js' -import UniformNode from '../core/UniformNode.js' -import { NodeUpdateType } from '../core/constants.js' - -class ReferenceNode extends Node { - constructor(property, uniformType, object = null) { - super() - - this.property = property - - this.uniformType = uniformType - - this.object = object - - this.node = null - - this.updateType = NodeUpdateType.Object - - this.setNodeType(uniformType) - } - - setNodeType(uniformType) { - this.node = new UniformNode(null, uniformType) - this.nodeType = uniformType - - if (uniformType === 'color') { - this.nodeType = 'vec3' - } else if (uniformType === 'texture') { - this.nodeType = 'vec4' - } - } - - getNodeType() { - return this.uniformType - } - - update(frame) { - const object = this.object !== null ? this.object : frame.object - const value = object[this.property] - - this.node.value = value - } - - generate(builder) { - return this.node.build(builder, this.getNodeType(builder)) - } -} - -export default ReferenceNode diff --git a/src/nodes/accessors/ReflectNode.js b/src/nodes/accessors/ReflectNode.js deleted file mode 100644 index 5b73df42..00000000 --- a/src/nodes/accessors/ReflectNode.js +++ /dev/null @@ -1,57 +0,0 @@ -import Node from '../core/Node.js' -import { - nodeObject, - normalWorld, - positionWorld, - cameraPosition, - sub, - normalize, - join, - negate, - reflect, -} from '../ShaderNode.js' - -class ReflectNode extends Node { - static VECTOR = 'vector' - static CUBE = 'cube' - - constructor(scope = ReflectNode.CUBE) { - super('vec3') - - this.scope = scope - } - - getHash(/*builder*/) { - return `reflect-${this.scope}` - } - - generate(builder) { - const scope = this.scope - - if (scope === ReflectNode.VECTOR) { - const cameraToFrag = normalize(sub(positionWorld, cameraPosition)) - const reflectVec = reflect(cameraToFrag, normalWorld) - - return reflectVec.build(builder) - } else if (scope === ReflectNode.CUBE) { - const reflectVec = nodeObject(new ReflectNode(ReflectNode.VECTOR)) - const cubeUV = join(negate(reflectVec.x), reflectVec.yz) - - return cubeUV.build(builder) - } - } - - serialize(data) { - super.serialize(data) - - data.scope = this.scope - } - - deserialize(data) { - super.deserialize(data) - - this.scope = data.scope - } -} - -export default ReflectNode diff --git a/src/nodes/accessors/SkinningNode.js b/src/nodes/accessors/SkinningNode.js deleted file mode 100644 index f393314b..00000000 --- a/src/nodes/accessors/SkinningNode.js +++ /dev/null @@ -1,96 +0,0 @@ -import Node from '../core/Node.js' - -import { - ShaderNode, - attribute, - buffer, - mat4, - uniform, - positionLocal, - normalLocal, - assign, - element, - add, - mul, - transformDirection, -} from '../ShaderNode.js' - -import { NodeUpdateType } from '../core/constants.js' - -const Skinning = new ShaderNode((inputs, builder) => { - const { index, weight, bindMatrix, bindMatrixInverse, boneMatrices } = inputs - - const boneMatX = element(boneMatrices, index.x) - const boneMatY = element(boneMatrices, index.y) - const boneMatZ = element(boneMatrices, index.z) - const boneMatW = element(boneMatrices, index.w) - - // POSITION - - const skinVertex = mul(bindMatrix, positionLocal) - - const skinned = add( - mul(mul(boneMatX, skinVertex), weight.x), - mul(mul(boneMatY, skinVertex), weight.y), - mul(mul(boneMatZ, skinVertex), weight.z), - mul(mul(boneMatW, skinVertex), weight.w), - ) - - const skinPosition = mul(bindMatrixInverse, skinned).xyz - - // NORMAL - - let skinMatrix = add( - mul(weight.x, boneMatX), - mul(weight.y, boneMatY), - mul(weight.z, boneMatZ), - mul(weight.w, boneMatW), - ) - - skinMatrix = mul(mul(bindMatrixInverse, skinMatrix), bindMatrix) - - const skinNormal = transformDirection(skinMatrix, normalLocal).xyz - - // ASSIGNS - - assign(positionLocal, skinPosition).build(builder) - assign(normalLocal, skinNormal).build(builder) -}) - -class SkinningNode extends Node { - constructor(skinnedMesh) { - super('void') - - this.skinnedMesh = skinnedMesh - - this.updateType = NodeUpdateType.Object - - // - - this.skinIndexNode = attribute('skinIndex', 'uvec4') - this.skinWeightNode = attribute('skinWeight', 'vec4') - - this.bindMatrixNode = uniform(mat4(skinnedMesh.bindMatrix)) - this.bindMatrixInverseNode = uniform(mat4(skinnedMesh.bindMatrixInverse)) - this.boneMatricesNode = buffer(skinnedMesh.skeleton.boneMatrices, 'mat4', skinnedMesh.skeleton.bones.length) - } - - generate(builder) { - Skinning( - { - index: this.skinIndexNode, - weight: this.skinWeightNode, - bindMatrix: this.bindMatrixNode, - bindMatrixInverse: this.bindMatrixInverseNode, - boneMatrices: this.boneMatricesNode, - }, - builder, - ) - } - - update() { - this.skinnedMesh.skeleton.update() - } -} - -export default SkinningNode diff --git a/src/nodes/accessors/TextureNode.js b/src/nodes/accessors/TextureNode.js deleted file mode 100644 index e1371089..00000000 --- a/src/nodes/accessors/TextureNode.js +++ /dev/null @@ -1,72 +0,0 @@ -import UniformNode from '../core/UniformNode.js' -import UVNode from './UVNode.js' - -class TextureNode extends UniformNode { - constructor(value, uvNode = new UVNode(), biasNode = null) { - super(value, 'vec4') - - this.uvNode = uvNode - this.biasNode = biasNode - } - - getUniformHash(/*builder*/) { - return this.value.uuid - } - - getInputType(/*builder*/) { - return 'texture' - } - - generate(builder, output) { - const texture = this.value - - if (!texture || texture.isTexture !== true) { - throw new Error('TextureNode: Need a three.js texture.') - } - - const textureProperty = super.generate(builder, 'texture') - - if (output === 'sampler') { - return textureProperty + '_sampler' - } else if (builder.isReference(output)) { - return textureProperty - } else { - const nodeData = builder.getDataFromNode(this) - - let snippet = nodeData.snippet - - if (snippet === undefined) { - const uvSnippet = this.uvNode.build(builder, 'vec2') - const biasNode = this.biasNode - - if (biasNode !== null) { - const biasSnippet = biasNode.build(builder, 'float') - - snippet = builder.getTextureBias(textureProperty, uvSnippet, biasSnippet) - } else { - snippet = builder.getTexture(textureProperty, uvSnippet) - } - - nodeData.snippet = snippet - } - - return builder.format(snippet, 'vec4', output) - } - } - - serialize(data) { - super.serialize(data) - - data.value = this.value.toJSON(data.meta).uuid - } - - deserialize(data) { - super.deserialize(data) - - this.value = data.meta.textures[data.value] - } -} - -TextureNode.prototype.isTextureNode = true - -export default TextureNode diff --git a/src/nodes/accessors/UVNode.js b/src/nodes/accessors/UVNode.js deleted file mode 100644 index 81d4d5e4..00000000 --- a/src/nodes/accessors/UVNode.js +++ /dev/null @@ -1,31 +0,0 @@ -import AttributeNode from '../core/AttributeNode.js' - -class UVNode extends AttributeNode { - constructor(index = 0) { - super(null, 'vec2') - - this.index = index - } - - getAttributeName(/*builder*/) { - const index = this.index - - return 'uv' + (index > 0 ? index + 1 : '') - } - - serialize(data) { - super.serialize(data) - - data.index = this.index - } - - deserialize(data) { - super.deserialize(data) - - this.index = data.index - } -} - -UVNode.prototype.isUVNode = true - -export default UVNode diff --git a/src/nodes/core/ArrayUniformNode.js b/src/nodes/core/ArrayUniformNode.js deleted file mode 100644 index 6bcc79fd..00000000 --- a/src/nodes/core/ArrayUniformNode.js +++ /dev/null @@ -1,17 +0,0 @@ -import UniformNode from './UniformNode.js' - -class ArrayUniformNode extends UniformNode { - constructor(nodes = []) { - super() - - this.nodes = nodes - } - - getNodeType(builder) { - return this.nodes[0].getNodeType(builder) - } -} - -ArrayUniformNode.prototype.isArrayUniformNode = true - -export default ArrayUniformNode diff --git a/src/nodes/core/AttributeNode.js b/src/nodes/core/AttributeNode.js deleted file mode 100644 index a85eb9e5..00000000 --- a/src/nodes/core/AttributeNode.js +++ /dev/null @@ -1,38 +0,0 @@ -import Node from './Node.js' -import VaryNode from './VaryNode.js' - -class AttributeNode extends Node { - constructor(attributeName, nodeType) { - super(nodeType) - - this._attributeName = attributeName - } - - getHash(builder) { - return this.getAttributeName(builder) - } - - setAttributeName(attributeName) { - this._attributeName = attributeName - - return this - } - - getAttributeName(/*builder*/) { - return this._attributeName - } - - generate(builder) { - const attribute = builder.getAttribute(this.getAttributeName(builder), this.getNodeType(builder)) - - if (builder.isShaderStage('vertex')) { - return attribute.name - } else { - const nodeVary = new VaryNode(this) - - return nodeVary.build(builder, attribute.type) - } - } -} - -export default AttributeNode diff --git a/src/nodes/core/BypassNode.js b/src/nodes/core/BypassNode.js deleted file mode 100644 index ae5c2e0c..00000000 --- a/src/nodes/core/BypassNode.js +++ /dev/null @@ -1,28 +0,0 @@ -import Node from './Node.js' - -class BypassNode extends Node { - constructor(returnNode, callNode) { - super() - - this.outputNode = returnNode - this.callNode = callNode - } - - getNodeType(builder) { - return this.outputNode.getNodeType(builder) - } - - generate(builder, output) { - const snippet = this.callNode.build(builder, 'void') - - if (snippet !== '') { - builder.addFlowCode(snippet) - } - - return this.outputNode.build(builder, output) - } -} - -BypassNode.prototype.isBypassNode = true - -export default BypassNode diff --git a/src/nodes/core/CodeNode.js b/src/nodes/core/CodeNode.js deleted file mode 100644 index 306676a1..00000000 --- a/src/nodes/core/CodeNode.js +++ /dev/null @@ -1,38 +0,0 @@ -import Node from './Node.js' - -class CodeNode extends Node { - constructor(code = '', nodeType = 'code') { - super(nodeType) - - this.code = code - - this._includes = [] - } - - setIncludes(includes) { - this._includes = includes - - return this - } - - getIncludes(/*builder*/) { - return this._includes - } - - generate(builder) { - const includes = this.getIncludes(builder) - - for (const include of includes) { - include.build(builder) - } - - const nodeCode = builder.getCodeFromNode(this, this.getNodeType(builder)) - nodeCode.code = this.code - - return nodeCode.code - } -} - -CodeNode.prototype.isCodeNode = true - -export default CodeNode diff --git a/src/nodes/core/ConstNode.js b/src/nodes/core/ConstNode.js deleted file mode 100644 index 79a4e273..00000000 --- a/src/nodes/core/ConstNode.js +++ /dev/null @@ -1,17 +0,0 @@ -import InputNode from './InputNode.js' - -class ConstNode extends InputNode { - generateConst(builder) { - return builder.getConst(this.getNodeType(builder), this.value) - } - - generate(builder, output) { - const type = this.getNodeType(builder) - - return builder.format(this.generateConst(builder), type, output) - } -} - -ConstNode.prototype.isConstNode = true - -export default ConstNode diff --git a/src/nodes/core/ContextNode.js b/src/nodes/core/ContextNode.js deleted file mode 100644 index 2cc24913..00000000 --- a/src/nodes/core/ContextNode.js +++ /dev/null @@ -1,30 +0,0 @@ -import Node from './Node.js' - -class ContextNode extends Node { - constructor(node, context = {}) { - super() - - this.node = node - this.context = context - } - - getNodeType(builder) { - return this.node.getNodeType(builder) - } - - generate(builder, output) { - const previousContext = builder.getContext() - - builder.setContext(Object.assign({}, builder.context, this.context)) - - const snippet = this.node.build(builder, output) - - builder.setContext(previousContext) - - return snippet - } -} - -ContextNode.prototype.isContextNode = true - -export default ContextNode diff --git a/src/nodes/core/ExpressionNode.js b/src/nodes/core/ExpressionNode.js deleted file mode 100644 index e106789d..00000000 --- a/src/nodes/core/ExpressionNode.js +++ /dev/null @@ -1,22 +0,0 @@ -import TempNode from './TempNode.js' - -class ExpressionNode extends TempNode { - constructor(snipped = '', nodeType = 'void') { - super(nodeType) - - this.snipped = snipped - } - - generate(builder) { - const type = this.getNodeType(builder) - const snipped = this.snipped - - if (type === 'void') { - builder.addFlowCode(snipped) - } else { - return `( ${snipped} )` - } - } -} - -export default ExpressionNode diff --git a/src/nodes/core/FunctionCallNode.js b/src/nodes/core/FunctionCallNode.js deleted file mode 100644 index dd50e55f..00000000 --- a/src/nodes/core/FunctionCallNode.js +++ /dev/null @@ -1,49 +0,0 @@ -import TempNode from './TempNode.js' - -class FunctionCallNode extends TempNode { - constructor(functionNode = null, parameters = {}) { - super() - - this.functionNode = functionNode - this.parameters = parameters - } - - setParameters(parameters) { - this.parameters = parameters - - return this - } - - getParameters() { - return this.parameters - } - - getNodeType(builder) { - return this.functionNode.getNodeType(builder) - } - - generate(builder) { - const params = [] - - const functionNode = this.functionNode - - const inputs = functionNode.getInputs(builder) - const parameters = this.parameters - - for (const inputNode of inputs) { - const node = parameters[inputNode.name] - - if (node !== undefined) { - params.push(node.build(builder, inputNode.type)) - } else { - throw new Error(`FunctionCallNode: Input '${inputNode.name}' not found in FunctionNode.`) - } - } - - const functionName = functionNode.build(builder, 'property') - - return `${functionName}( ${params.join(', ')} )` - } -} - -export default FunctionCallNode diff --git a/src/nodes/core/FunctionNode.js b/src/nodes/core/FunctionNode.js deleted file mode 100644 index 32426812..00000000 --- a/src/nodes/core/FunctionNode.js +++ /dev/null @@ -1,79 +0,0 @@ -import CodeNode from './CodeNode.js' -import FunctionCallNode from './FunctionCallNode.js' - -class FunctionNode extends CodeNode { - constructor(code = '') { - super(code) - - this.keywords = {} - } - - getNodeType(builder) { - return this.getNodeFunction(builder).type - } - - getInputs(builder) { - return this.getNodeFunction(builder).inputs - } - - getNodeFunction(builder) { - const nodeData = builder.getDataFromNode(this) - - let nodeFunction = nodeData.nodeFunction - - if (nodeFunction === undefined) { - nodeFunction = builder.parser.parseFunction(this.code) - - nodeData.nodeFunction = nodeFunction - } - - return nodeFunction - } - - call(parameters = {}) { - return new FunctionCallNode(this, parameters) - } - - generate(builder, output) { - super.generate(builder) - - const nodeFunction = this.getNodeFunction(builder) - - const name = nodeFunction.name - const type = nodeFunction.type - - const nodeCode = builder.getCodeFromNode(this, type) - - if (name !== '') { - // use a custom property name - - nodeCode.name = name - } - - const propertyName = builder.getPropertyName(nodeCode) - - let code = this.getNodeFunction(builder).getCode(propertyName) - - const keywords = this.keywords - const keywordsProperties = Object.keys(keywords) - - if (keywordsProperties.length > 0) { - for (const property of keywordsProperties) { - const propertyRegExp = new RegExp(`\\b${property}\\b`, 'g') - const nodeProperty = keywords[property].build(builder, 'property') - - code = code.replace(propertyRegExp, nodeProperty) - } - } - - nodeCode.code = code - - if (output === 'property') { - return propertyName - } else { - return builder.format(`${propertyName}()`, type, output) - } - } -} - -export default FunctionNode diff --git a/src/nodes/core/InputNode.js b/src/nodes/core/InputNode.js deleted file mode 100644 index 60b5b29e..00000000 --- a/src/nodes/core/InputNode.js +++ /dev/null @@ -1,46 +0,0 @@ -import Node from './Node.js' -import { getValueType, getValueFromType } from './NodeUtils.js' - -class InputNode extends Node { - constructor(value, nodeType = null) { - super(nodeType) - - this.value = value - } - - getNodeType(/*builder*/) { - if (this.nodeType === null) { - return getValueType(this.value) - } - - return this.nodeType - } - - getInputType(builder) { - return this.getNodeType(builder) - } - - serialize(data) { - super.serialize(data) - - data.value = this.value?.toArray?.() || this.value - data.valueType = getValueType(this.value) - data.nodeType = this.nodeType - } - - deserialize(data) { - super.deserialize(data) - - this.nodeType = data.nodeType - this.value = getValueFromType(data.valueType) - this.value = this.value?.fromArray?.(data.value) || data.value - } - - generate(/*builder, output*/) { - console.warn('Abstract function.') - } -} - -InputNode.prototype.isInputNode = true - -export default InputNode diff --git a/src/nodes/core/Node.js b/src/nodes/core/Node.js deleted file mode 100644 index a0091a79..00000000 --- a/src/nodes/core/Node.js +++ /dev/null @@ -1,188 +0,0 @@ -import { NodeUpdateType } from './constants.js' -import { getNodesKeys } from './NodeUtils.js' -import { MathUtils } from 'three' - -let _nodeId = 0 - -class Node { - constructor(nodeType = null) { - this.nodeType = nodeType - - this.updateType = NodeUpdateType.None - - this.uuid = MathUtils.generateUUID() - - Object.defineProperty(this, 'id', { value: _nodeId++ }) - } - - get type() { - return this.constructor.name - } - - getHash(/*builder*/) { - return this.uuid - } - - getUpdateType(/*builder*/) { - return this.updateType - } - - getNodeType(/*builder*/) { - return this.nodeType - } - - update(/*frame*/) { - console.warn('Abstract function.') - } - - generate(/*builder, output*/) { - console.warn('Abstract function.') - } - - analyze(builder) { - const hash = this.getHash(builder) - const sharedNode = builder.getNodeFromHash(hash) - - if (sharedNode !== undefined && this !== sharedNode) { - return sharedNode.analyze(builder) - } - - const nodeData = builder.getDataFromNode(this) - nodeData.dependenciesCount = nodeData.dependenciesCount === undefined ? 1 : nodeData.dependenciesCount + 1 - - const nodeKeys = getNodesKeys(this) - - for (const property of nodeKeys) { - this[property].analyze(builder) - } - } - - build(builder, output = null) { - const hash = this.getHash(builder) - const sharedNode = builder.getNodeFromHash(hash) - - if (sharedNode !== undefined && this !== sharedNode) { - return sharedNode.build(builder, output) - } - - builder.addNode(this) - builder.addStack(this) - - const nodeData = builder.getDataFromNode(this) - const isGenerateOnce = this.generate.length === 1 - - let snippet = null - - if (isGenerateOnce) { - const type = this.getNodeType(builder) - - snippet = nodeData.snippet - - if (snippet === undefined) { - snippet = this.generate(builder) || '' - - nodeData.snippet = snippet - } - - snippet = builder.format(snippet, type, output) - } else { - snippet = this.generate(builder, output) || '' - } - - builder.removeStack(this) - - return snippet - } - - serialize(json) { - const nodeKeys = getNodesKeys(this) - - if (nodeKeys.length > 0) { - const inputNodes = {} - - for (const property of nodeKeys) { - inputNodes[property] = this[property].toJSON(json.meta).uuid - } - - json.inputNodes = inputNodes - } - } - - deserialize(json) { - if (json.inputNodes !== undefined) { - const nodes = json.meta.nodes - - for (const property in json.inputNodes) { - const uuid = json.inputNodes[property] - - this[property] = nodes[uuid] - } - } - } - - toJSON(meta) { - const { uuid, type } = this - const isRoot = meta === undefined || typeof meta === 'string' - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - } - } - - // serialize - - let data = meta.nodes[uuid] - - if (data === undefined) { - data = { - uuid, - type, - meta, - metadata: { - version: 4.5, - type: 'Node', - generator: 'Node.toJSON', - }, - } - - meta.nodes[data.uuid] = data - - this.serialize(data) - - delete data.meta - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = [] - - for (const key in cache) { - const data = cache[key] - delete data.metadata - values.push(data) - } - - return values - } - - if (isRoot) { - const textures = extractFromCache(meta.textures) - const images = extractFromCache(meta.images) - const nodes = extractFromCache(meta.nodes) - - if (textures.length > 0) data.textures = textures - if (images.length > 0) data.images = images - if (nodes.length > 0) data.nodes = nodes - } - - return data - } -} - -Node.prototype.isNode = true - -export default Node diff --git a/src/nodes/core/NodeAttribute.js b/src/nodes/core/NodeAttribute.js deleted file mode 100644 index 781a02dd..00000000 --- a/src/nodes/core/NodeAttribute.js +++ /dev/null @@ -1,10 +0,0 @@ -class NodeAttribute { - constructor(name, type) { - this.name = name - this.type = type - } -} - -NodeAttribute.prototype.isNodeAttribute = true - -export default NodeAttribute diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js deleted file mode 100644 index 210383c1..00000000 --- a/src/nodes/core/NodeBuilder.js +++ /dev/null @@ -1,562 +0,0 @@ -import NodeUniform from './NodeUniform.js' -import NodeAttribute from './NodeAttribute.js' -import NodeVary from './NodeVary.js' -import NodeVar from './NodeVar.js' -import NodeCode from './NodeCode.js' -import NodeKeywords from './NodeKeywords.js' -import { NodeUpdateType } from './constants.js' - -import { REVISION } from 'three' - -export const shaderStages = ['fragment', 'vertex'] -export const vector = ['x', 'y', 'z', 'w'] - -const toFloat = (value) => { - value = Number(value) - - return value + (value % 1 ? '' : '.0') -} - -class NodeBuilder { - constructor(object, renderer, parser) { - this.object = object - this.material = object.material - this.renderer = renderer - this.parser = parser - - this.nodes = [] - this.updateNodes = [] - this.hashNodes = {} - - this.vertexShader = null - this.fragmentShader = null - - this.flowNodes = { vertex: [], fragment: [] } - this.flowCode = { vertex: '', fragment: '' } - this.uniforms = { vertex: [], fragment: [], index: 0 } - this.codes = { vertex: [], fragment: [] } - this.attributes = [] - this.varys = [] - this.vars = { vertex: [], fragment: [] } - this.flow = { code: '' } - this.stack = [] - - this.context = { - keywords: new NodeKeywords(), - material: object.material, - } - - this.nodesData = new WeakMap() - this.flowsData = new WeakMap() - - this.shaderStage = null - this.node = null - } - - addStack(node) { - /* - if ( this.stack.indexOf( node ) !== - 1 ) { - - console.warn( 'Recursive node: ', node ); - - } - */ - - this.stack.push(node) - } - - removeStack(node) { - const lastStack = this.stack.pop() - - if (lastStack !== node) { - throw new Error('NodeBuilder: Invalid node stack!') - } - } - - setHashNode(node, hash) { - this.hashNodes[hash] = node - } - - addNode(node) { - if (this.nodes.indexOf(node) === -1) { - const updateType = node.getUpdateType(this) - - if (updateType !== NodeUpdateType.None) { - this.updateNodes.push(node) - } - - this.nodes.push(node) - - this.setHashNode(node, node.getHash(this)) - } - } - - getMethod(method) { - return method - } - - getNodeFromHash(hash) { - return this.hashNodes[hash] - } - - addFlow(shaderStage, node) { - this.flowNodes[shaderStage].push(node) - - return node - } - - setContext(context) { - this.context = context - } - - getContext() { - return this.context - } - - getTexture(/* textureProperty, uvSnippet */) { - console.warn('Abstract function.') - } - - getTextureBias(/* textureProperty, uvSnippet, biasSnippet */) { - console.warn('Abstract function.') - } - - getCubeTexture(/* textureProperty, uvSnippet */) { - console.warn('Abstract function.') - } - - getCubeTextureBias(/* textureProperty, uvSnippet, biasSnippet */) { - console.warn('Abstract function.') - } - - // @TODO: rename to .generateConst() - getConst(type, value) { - if (type === 'float') return toFloat(value) - if (type === 'int') return `${Math.round(value)}` - if (type === 'uint') return value >= 0 ? `${Math.round(value)}u` : '0u' - if (type === 'bool') return value ? 'true' : 'false' - if (type === 'color') { - return `${this.getType('vec3')}( ${toFloat(value.r)}, ${toFloat(value.g)}, ${toFloat(value.b)} )` - } - - const typeLength = this.getTypeLength(type) - - const componentType = this.getComponentType(type) - - const getConst = (value) => this.getConst(componentType, value) - - if (typeLength === 2) { - return `${this.getType(type)}( ${getConst(value.x)}, ${getConst(value.y)} )` - } else if (typeLength === 3) { - return `${this.getType(type)}( ${getConst(value.x)}, ${getConst(value.y)}, ${getConst(value.z)} )` - } else if (typeLength === 4) { - return `${this.getType(type)}( ${getConst(value.x)}, ${getConst(value.y)}, ${getConst(value.z)}, ${getConst( - value.w, - )} )` - } - - throw new Error(`NodeBuilder: Type '${type}' not found in generate constant attempt.`) - } - - getType(type) { - return type - } - - generateMethod(method) { - return method - } - - getAttribute(name, type) { - const attributes = this.attributes - - // find attribute - - for (const attribute of attributes) { - if (attribute.name === name) { - return attribute - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type) - - attributes.push(attribute) - - return attribute - } - - getPropertyName(node /*, shaderStage*/) { - return node.name - } - - isVector(type) { - return /vec\d/.test(type) - } - - isMatrix(type) { - return /mat\d/.test(type) - } - - isReference(type) { - return type === 'void' || type === 'property' || type === 'sampler' - } - - isShaderStage(shaderStage) { - return this.shaderStage === shaderStage - } - - getTextureEncodingFromMap(map) { - let encoding - - if (map && map.isTexture) { - if ('colorSpace' in map) encoding = map.colorSpace === 'srgb' ? 3001 : 3000 - else encoding = map.encoding - } else if (map && map.isWebGLRenderTarget) { - if ('colorSpace' in map.texture) encoding = map.texture.colorSpace === 'srgb' ? 3001 : 3000 - else encoding = map.texture.encoding - } else { - encoding = 3000 // LinearEncoding - } - - return encoding - } - - getComponentType(type) { - type = this.getVectorType(type) - - const componentType = /(b|i|u|)(vec|mat)([2-4])/.exec(type) - - if (componentType === null) return null - - if (componentType[1] === 'b') return 'bool' - if (componentType[1] === 'i') return 'int' - if (componentType[1] === 'u') return 'uint' - - return 'float' - } - - getVectorType(type) { - if (type === 'color') return 'vec3' - if (type === 'texture') return 'vec4' - - return type - } - - getTypeFromLength(type) { - if (type === 1) return 'float' - if (type === 2) return 'vec2' - if (type === 3) return 'vec3' - if (type === 4) return 'vec4' - - return 0 - } - - getTypeLength(type) { - const vecType = this.getVectorType(type) - const vecNum = /vec([2-4])/.exec(vecType) - - if (vecNum !== null) return Number(vecNum[1]) - if (vecType === 'float' || vecType === 'bool' || vecType === 'int' || vecType === 'uint') return 1 - - return 0 - } - - getVectorFromMatrix(type) { - return type.replace('mat', 'vec') - } - - getDataFromNode(node, shaderStage = this.shaderStage) { - let nodeData = this.nodesData.get(node) - - if (nodeData === undefined) { - nodeData = { vertex: {}, fragment: {} } - - this.nodesData.set(node, nodeData) - } - - return shaderStage !== null ? nodeData[shaderStage] : nodeData - } - - getUniformFromNode(node, shaderStage, type) { - const nodeData = this.getDataFromNode(node, shaderStage) - - let nodeUniform = nodeData.uniform - - if (nodeUniform === undefined) { - const index = this.uniforms.index++ - - nodeUniform = new NodeUniform('nodeUniform' + index, type, node) - - this.uniforms[shaderStage].push(nodeUniform) - - nodeData.uniform = nodeUniform - } - - return nodeUniform - } - - getVarFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node, shaderStage) - - let nodeVar = nodeData.variable - - if (nodeVar === undefined) { - const vars = this.vars[shaderStage] - const index = vars.length - - nodeVar = new NodeVar('nodeVar' + index, type) - - vars.push(nodeVar) - - nodeData.variable = nodeVar - } - - return nodeVar - } - - getVaryFromNode(node, type) { - const nodeData = this.getDataFromNode(node, null) - - let nodeVary = nodeData.vary - - if (nodeVary === undefined) { - const varys = this.varys - const index = varys.length - - nodeVary = new NodeVary('nodeVary' + index, type) - - varys.push(nodeVary) - - nodeData.vary = nodeVary - } - - return nodeVary - } - - getCodeFromNode(node, type, shaderStage = this.shaderStage) { - const nodeData = this.getDataFromNode(node) - - let nodeCode = nodeData.code - - if (nodeCode === undefined) { - const codes = this.codes[shaderStage] - const index = codes.length - - nodeCode = new NodeCode('nodeCode' + index, type) - - codes.push(nodeCode) - - nodeData.code = nodeCode - } - - return nodeCode - } - - addFlowCode(code) { - this.flow.code += code - } - - getFlowData(shaderStage, node) { - return this.flowsData.get(node) - } - - flowNode(node) { - this.node = node - - const output = node.getNodeType(this) - - const flowData = this.flowChildNode(node, output) - - this.flowsData.set(node, flowData) - - this.node = null - - return flowData - } - - flowChildNode(node, output = null) { - const previousFlow = this.flow - - const flow = { - code: '', - } - - this.flow = flow - - flow.result = node.build(this, output) - - this.flow = previousFlow - - return flow - } - - flowNodeFromShaderStage(shaderStage, node, output = null, propertyName = null) { - const previousShaderStage = this.shaderStage - - this.setShaderStage(shaderStage) - - const flowData = this.flowChildNode(node, output) - - if (propertyName !== null) { - flowData.code += `${propertyName} = ${flowData.result};\n\t` - } - - this.flowCode[shaderStage] = this.flowCode[shaderStage] + flowData.code - - this.setShaderStage(previousShaderStage) - - return flowData - } - - getAttributes(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getVarys(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getVars(shaderStage) { - let snippet = '' - - const vars = this.vars[shaderStage] - - for (let index = 0; index < vars.length; index++) { - const variable = vars[index] - - snippet += `${variable.type} ${variable.name}; ` - } - - return snippet - } - - getUniforms(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getCodes(shaderStage) { - const codes = this.codes[shaderStage] - - let code = '' - - for (const nodeCode of codes) { - code += nodeCode.code + '\n' - } - - return code - } - - getHash() { - return this.vertexShader + this.fragmentShader - } - - getShaderStage() { - return this.shaderStage - } - - setShaderStage(shaderStage) { - this.shaderStage = shaderStage - } - - buildCode() { - console.warn('Abstract function.') - } - - build() { - // stage 1: analyze nodes to possible optimization and validation - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage) - - const flowNodes = this.flowNodes[shaderStage] - - for (const node of flowNodes) { - node.analyze(this) - } - } - - // stage 2: pre-build vertex code used in fragment shader - - if (this.context.vertex && this.context.vertex.isNode) { - this.flowNodeFromShaderStage('vertex', this.context.vertex) - } - - // stage 3: generate shader - - for (const shaderStage of shaderStages) { - this.setShaderStage(shaderStage) - - const flowNodes = this.flowNodes[shaderStage] - - for (const node of flowNodes) { - this.flowNode(node, shaderStage) - } - } - - this.setShaderStage(null) - - // stage 4: build code for a specific output - - this.buildCode() - - return this - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType) - toType = this.getVectorType(toType) - - if (fromType === toType || toType === null || this.isReference(toType)) { - return snippet - } - - const fromTypeLength = this.getTypeLength(fromType) - const toTypeLength = this.getTypeLength(toType) - - if (fromTypeLength === 0) { - // fromType is matrix-like - - const vectorType = this.getVectorFromMatrix(fromType) - - return this.format(`( ${snippet} * ${this.getType(vectorType)}( 1.0 ) )`, vectorType, toType) - } - - if (toTypeLength === 0) { - // toType is matrix-like - - // ignore for now - //return `${ this.getType( toType ) }( ${ snippet } )`; - - return snippet - } - - if (fromTypeLength === toTypeLength) { - return `${this.getType(toType)}( ${snippet} )` - } - - if (fromTypeLength > toTypeLength) { - return this.format(`${snippet}.${'xyz'.slice(0, toTypeLength)}`, this.getTypeFromLength(toTypeLength), toType) - } - - if (toTypeLength === 4) { - // toType is vec4-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec3')}, 1.0 )` - } - - if (fromTypeLength === 2) { - // fromType is vec2-like and toType is vec3-like - - return `${this.getType(toType)}( ${this.format(snippet, fromType, 'vec2')}, 0.0 )` - } - - return `${this.getType(toType)}( ${snippet} )` // fromType is float-like - } - - getSignature() { - return `// Three.js r${REVISION} - NodeMaterial System\n` - } -} - -export default NodeBuilder diff --git a/src/nodes/core/NodeCode.js b/src/nodes/core/NodeCode.js deleted file mode 100644 index d2607c06..00000000 --- a/src/nodes/core/NodeCode.js +++ /dev/null @@ -1,11 +0,0 @@ -class NodeCode { - constructor(name, type, code = '') { - this.name = name - this.type = type - this.code = code - - Object.defineProperty(this, 'isNodeCode', { value: true }) - } -} - -export default NodeCode diff --git a/src/nodes/core/NodeFrame.js b/src/nodes/core/NodeFrame.js deleted file mode 100644 index 37f924b0..00000000 --- a/src/nodes/core/NodeFrame.js +++ /dev/null @@ -1,45 +0,0 @@ -import { NodeUpdateType } from './constants.js' - -class NodeFrame { - constructor() { - this.time = 0 - this.deltaTime = 0 - - this.frameId = 0 - - this.startTime = null - - this.updateMap = new WeakMap() - - this.renderer = null - this.material = null - this.camera = null - this.object = null - } - - updateNode(node) { - if (node.updateType === NodeUpdateType.Frame) { - if (this.updateMap.get(node) !== this.frameId) { - this.updateMap.set(node, this.frameId) - - node.update(this) - } - } else if (node.updateType === NodeUpdateType.Object) { - node.update(this) - } - } - - update() { - this.frameId++ - - if (this.lastTime === undefined) this.lastTime = performance.now() - - this.deltaTime = (performance.now() - this.lastTime) / 1000 - - this.lastTime = performance.now() - - this.time += this.deltaTime - } -} - -export default NodeFrame diff --git a/src/nodes/core/NodeFunction.js b/src/nodes/core/NodeFunction.js deleted file mode 100644 index a50b4aad..00000000 --- a/src/nodes/core/NodeFunction.js +++ /dev/null @@ -1,16 +0,0 @@ -class NodeFunction { - constructor(type, inputs, name = '', presicion = '') { - this.type = type - this.inputs = inputs - this.name = name - this.presicion = presicion - } - - getCode(/*name = this.name*/) { - console.warn('Abstract function.') - } -} - -NodeFunction.isNodeFunction = true - -export default NodeFunction diff --git a/src/nodes/core/NodeFunctionInput.js b/src/nodes/core/NodeFunctionInput.js deleted file mode 100644 index fd836b83..00000000 --- a/src/nodes/core/NodeFunctionInput.js +++ /dev/null @@ -1,13 +0,0 @@ -class NodeFunctionInput { - constructor(type, name, count = null, qualifier = '', isConst = false) { - this.type = type - this.name = name - this.count = count - this.qualifier = qualifier - this.isConst = isConst - } -} - -NodeFunctionInput.isNodeFunctionInput = true - -export default NodeFunctionInput diff --git a/src/nodes/core/NodeKeywords.js b/src/nodes/core/NodeKeywords.js deleted file mode 100644 index 19608600..00000000 --- a/src/nodes/core/NodeKeywords.js +++ /dev/null @@ -1,58 +0,0 @@ -class NodeKeywords { - constructor() { - this.keywords = [] - this.nodes = [] - this.keywordsCallback = {} - } - - getNode(name) { - let node = this.nodes[name] - - if (node === undefined && this.keywordsCallback[name] !== undefined) { - node = this.keywordsCallback[name](name) - - this.nodes[name] = node - } - - return node - } - - addKeyword(name, callback) { - this.keywords.push(name) - this.keywordsCallback[name] = callback - - return this - } - - parse(code) { - const keywordNames = this.keywords - - const regExp = new RegExp(`\\b${keywordNames.join('\\b|\\b')}\\b`, 'g') - - const codeKeywords = code.match(regExp) - - const keywordNodes = [] - - if (codeKeywords !== null) { - for (const keyword of codeKeywords) { - const node = this.getNode(keyword) - - if (node !== undefined && keywordNodes.indexOf(node) === -1) { - keywordNodes.push(node) - } - } - } - - return keywordNodes - } - - include(builder, code) { - const keywordNodes = this.parse(code) - - for (const keywordNode of keywordNodes) { - keywordNode.build(builder) - } - } -} - -export default NodeKeywords diff --git a/src/nodes/core/NodeParser.js b/src/nodes/core/NodeParser.js deleted file mode 100644 index f2b7821c..00000000 --- a/src/nodes/core/NodeParser.js +++ /dev/null @@ -1,7 +0,0 @@ -class NodeParser { - parseFunction(/*source*/) { - console.warn('Abstract function.') - } -} - -export default NodeParser diff --git a/src/nodes/core/NodeUniform.js b/src/nodes/core/NodeUniform.js deleted file mode 100644 index 96ee6077..00000000 --- a/src/nodes/core/NodeUniform.js +++ /dev/null @@ -1,20 +0,0 @@ -class NodeUniform { - constructor(name, type, node, needsUpdate = undefined) { - this.name = name - this.type = type - this.node = node - this.needsUpdate = needsUpdate - } - - get value() { - return this.node.value - } - - set value(val) { - this.node.value = val - } -} - -NodeUniform.prototype.isNodeUniform = true - -export default NodeUniform diff --git a/src/nodes/core/NodeUtils.js b/src/nodes/core/NodeUtils.js deleted file mode 100644 index 7d77f741..00000000 --- a/src/nodes/core/NodeUtils.js +++ /dev/null @@ -1,61 +0,0 @@ -import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three' - -export const getNodesKeys = (object) => { - const props = [] - - for (const name in object) { - const value = object[name] - - if (value && value.isNode === true) { - props.push(name) - } - } - - return props -} - -export const getValueType = (value) => { - if (typeof value === 'number') { - return 'float' - } else if (typeof value === 'boolean') { - return 'bool' - } else if (value?.isVector2 === true) { - return 'vec2' - } else if (value?.isVector3 === true) { - return 'vec3' - } else if (value?.isVector4 === true) { - return 'vec4' - } else if (value?.isMatrix3 === true) { - return 'mat3' - } else if (value?.isMatrix4 === true) { - return 'mat4' - } else if (value?.isColor === true) { - return 'color' - } - - return null -} - -export const getValueFromType = (type, ...params) => { - const last4 = type?.slice(-4) - - if (type === 'color') { - return new Color(...params) - } else if (last4 === 'vec2') { - return new Vector2(...params) - } else if (last4 === 'vec3') { - return new Vector3(...params) - } else if (last4 === 'vec4') { - return new Vector4(...params) - } else if (last4 === 'mat3') { - return new Matrix3(...params) - } else if (last4 === 'mat4') { - return new Matrix4(...params) - } else if (type === 'bool') { - return false - } else if (type === 'float' || type === 'int' || type === 'uint') { - return 0 - } - - return null -} diff --git a/src/nodes/core/NodeVar.js b/src/nodes/core/NodeVar.js deleted file mode 100644 index 41d05289..00000000 --- a/src/nodes/core/NodeVar.js +++ /dev/null @@ -1,10 +0,0 @@ -class NodeVar { - constructor(name, type) { - this.name = name - this.type = type - } -} - -NodeVar.prototype.isNodeVar = true - -export default NodeVar diff --git a/src/nodes/core/NodeVary.js b/src/nodes/core/NodeVary.js deleted file mode 100644 index 99cc07b1..00000000 --- a/src/nodes/core/NodeVary.js +++ /dev/null @@ -1,10 +0,0 @@ -class NodeVary { - constructor(name, type) { - this.name = name - this.type = type - } -} - -NodeVary.prototype.isNodeVary = true - -export default NodeVary diff --git a/src/nodes/core/PropertyNode.js b/src/nodes/core/PropertyNode.js deleted file mode 100644 index 978448c4..00000000 --- a/src/nodes/core/PropertyNode.js +++ /dev/null @@ -1,26 +0,0 @@ -import Node from './Node.js' - -class PropertyNode extends Node { - constructor(name = null, nodeType = 'vec4') { - super(nodeType) - - this.name = name - } - - getHash(builder) { - return this.name || super.getHash(builder) - } - - generate(builder) { - const nodeVary = builder.getVarFromNode(this, this.getNodeType(builder)) - const name = this.name - - if (name !== null) { - nodeVary.name = name - } - - return builder.getPropertyName(nodeVary) - } -} - -export default PropertyNode diff --git a/src/nodes/core/TempNode.js b/src/nodes/core/TempNode.js deleted file mode 100644 index 10c23f7f..00000000 --- a/src/nodes/core/TempNode.js +++ /dev/null @@ -1,32 +0,0 @@ -import Node from './Node.js' - -class TempNode extends Node { - constructor(type) { - super(type) - } - - build(builder, output) { - const type = builder.getVectorType(this.getNodeType(builder, output)) - const nodeData = builder.getDataFromNode(this) - - if (builder.context.temp !== false && type !== 'void ' && output !== 'void' && nodeData.dependenciesCount > 1) { - if (nodeData.snippet === undefined) { - const snippet = super.build(builder, type) - - const nodeVar = builder.getVarFromNode(this, type) - const propertyName = builder.getPropertyName(nodeVar) - - builder.addFlowCode(`${propertyName} = ${snippet}`) - - nodeData.snippet = snippet - nodeData.propertyName = propertyName - } - - return builder.format(nodeData.propertyName, type, output) - } - - return super.build(builder, output) - } -} - -export default TempNode diff --git a/src/nodes/core/UniformNode.js b/src/nodes/core/UniformNode.js deleted file mode 100644 index 381a1974..00000000 --- a/src/nodes/core/UniformNode.js +++ /dev/null @@ -1,32 +0,0 @@ -import InputNode from './InputNode.js' - -class UniformNode extends InputNode { - getUniformHash(builder) { - return this.getHash(builder) - } - - generate(builder, output) { - const type = this.getNodeType(builder) - - const hash = this.getUniformHash(builder) - - let sharedNode = builder.getNodeFromHash(hash) - - if (sharedNode === undefined) { - builder.setHashNode(this, hash) - - sharedNode = this - } - - const sharedNodeType = sharedNode.getInputType(builder) - - const nodeUniform = builder.getUniformFromNode(sharedNode, builder.shaderStage, sharedNodeType) - const propertyName = builder.getPropertyName(nodeUniform) - - return builder.format(propertyName, type, output) - } -} - -UniformNode.prototype.isUniformNode = true - -export default UniformNode diff --git a/src/nodes/core/VarNode.js b/src/nodes/core/VarNode.js deleted file mode 100644 index a8aeb3d1..00000000 --- a/src/nodes/core/VarNode.js +++ /dev/null @@ -1,41 +0,0 @@ -import Node from './Node.js' - -class VarNode extends Node { - constructor(node, name = null, nodeType = null) { - super(nodeType) - - this.node = node - this.name = name - } - - getHash(builder) { - return this.name || super.getHash(builder) - } - - getNodeType(builder) { - return super.getNodeType(builder) || this.node.getNodeType(builder) - } - - generate(builder) { - const type = builder.getVectorType(this.getNodeType(builder)) - const node = this.node - const name = this.name - - const snippet = node.build(builder, type) - const nodeVar = builder.getVarFromNode(this, type) - - if (name !== null) { - nodeVar.name = name - } - - const propertyName = builder.getPropertyName(nodeVar) - - builder.addFlowCode(`${propertyName} = ${snippet}`) - - return propertyName - } -} - -VarNode.prototype.isVarNode = true - -export default VarNode diff --git a/src/nodes/core/VaryNode.js b/src/nodes/core/VaryNode.js deleted file mode 100644 index e5688961..00000000 --- a/src/nodes/core/VaryNode.js +++ /dev/null @@ -1,42 +0,0 @@ -import Node from './Node.js' -import { NodeShaderStage } from './constants.js' - -class VaryNode extends Node { - constructor(node, name = null) { - super() - - this.node = node - this.name = name - } - - getHash(builder) { - return this.name || super.getHash(builder) - } - - getNodeType(builder) { - // VaryNode is auto type - - return this.node.getNodeType(builder) - } - - generate(builder) { - const type = this.getNodeType(builder) - const node = this.node - const name = this.name - - const nodeVary = builder.getVaryFromNode(this, type) - - if (name !== null) { - nodeVary.name = name - } - - const propertyName = builder.getPropertyName(nodeVary, NodeShaderStage.Vertex) - - // force node run in vertex stage - builder.flowNodeFromShaderStage(NodeShaderStage.Vertex, node, type, propertyName) - - return builder.getPropertyName(nodeVary) - } -} - -export default VaryNode diff --git a/src/nodes/core/constants.js b/src/nodes/core/constants.js deleted file mode 100644 index dacf194b..00000000 --- a/src/nodes/core/constants.js +++ /dev/null @@ -1,21 +0,0 @@ -export const NodeShaderStage = { - Vertex: 'vertex', - Fragment: 'fragment', -} - -export const NodeUpdateType = { - None: 'none', - Frame: 'frame', - Object: 'object', -} - -export const NodeType = { - Boolean: 'bool', - Integer: 'int', - Float: 'float', - Vector2: 'vec2', - Vector3: 'vec3', - Vector4: 'vec4', - Matrix3: 'mat3', - Matrix4: 'mat4', -} diff --git a/src/nodes/display/ColorSpaceNode.js b/src/nodes/display/ColorSpaceNode.js deleted file mode 100644 index 52688238..00000000 --- a/src/nodes/display/ColorSpaceNode.js +++ /dev/null @@ -1,71 +0,0 @@ -import TempNode from '../core/Node.js' -import { ShaderNode, vec3, pow, mul, sub, mix, join, lessThanEqual } from '../ShaderNode.js' - -export const LinearToLinear = new ShaderNode((inputs) => { - return inputs.value -}) - -export const LinearTosRGB = new ShaderNode((inputs) => { - const { value } = inputs - - const rgb = value.rgb - - const a = sub(mul(pow(value.rgb, vec3(0.41666)), 1.055), vec3(0.055)) - const b = mul(rgb, 12.92) - const factor = vec3(lessThanEqual(rgb, vec3(0.0031308))) - - const rgbResult = mix(a, b, factor) - - return join(rgbResult.r, rgbResult.g, rgbResult.b, value.a) -}) - -const EncodingLib = { - LinearToLinear, - LinearTosRGB, -} - -class ColorSpaceNode extends TempNode { - static LINEAR_TO_LINEAR = 'LinearToLinear' - static LINEAR_TO_SRGB = 'LinearTosRGB' - - constructor(method, node) { - super('vec4') - - this.method = method - - this.node = node - } - - fromEncoding(encoding) { - let method = null - - if (encoding === 3000) { - method = 'Linear' - } else if (encoding === 3001) { - method = 'sRGB' - } - - this.method = 'LinearTo' + method - - return this - } - - generate(builder) { - const type = this.getNodeType(builder) - - const method = this.method - const node = this.node - - if (method !== ColorSpaceNode.LINEAR_TO_LINEAR) { - const encodingFunctionNode = EncodingLib[method] - - return encodingFunctionNode({ - value: node, - }).build(builder, type) - } else { - return node.build(builder, type) - } - } -} - -export default ColorSpaceNode diff --git a/src/nodes/display/NormalMapNode.js b/src/nodes/display/NormalMapNode.js deleted file mode 100644 index eef34f4c..00000000 --- a/src/nodes/display/NormalMapNode.js +++ /dev/null @@ -1,93 +0,0 @@ -import TempNode from '../core/TempNode.js' -import ModelNode from '../accessors/ModelNode.js' -import { - ShaderNode, - positionView, - normalView, - uv, - join, - cond, - add, - sub, - mul, - dFdx, - dFdy, - cross, - max, - dot, - normalize, - inversesqrt, - equal, -} from '../ShaderNode.js' - -import { TangentSpaceNormalMap, ObjectSpaceNormalMap } from 'three' - -// Normal Mapping Without Precomputed Tangents -// http://www.thetenthplanet.de/archives/1180 - -const perturbNormal2ArbNode = new ShaderNode((inputs) => { - const { eye_pos, surf_norm, mapN, faceDirection, uv } = inputs - - const q0 = dFdx(eye_pos.xyz) - const q1 = dFdy(eye_pos.xyz) - const st0 = dFdx(uv.st) - const st1 = dFdy(uv.st) - - const N = surf_norm // normalized - - const q1perp = cross(q1, N) - const q0perp = cross(N, q0) - - const T = add(mul(q1perp, st0.x), mul(q0perp, st1.x)) - const B = add(mul(q1perp, st0.y), mul(q0perp, st1.y)) - - const det = max(dot(T, T), dot(B, B)) - const scale = cond(equal(det, 0), 0, mul(faceDirection, inversesqrt(det))) - - return normalize(add(mul(T, mul(mapN.x, scale)), mul(B, mul(mapN.y, scale)), mul(N, mapN.z))) -}) - -class NormalMapNode extends TempNode { - constructor(node, scaleNode = null) { - super('vec3') - - this.node = node - this.scaleNode = scaleNode - - this.normalMapType = TangentSpaceNormalMap - } - - generate(builder) { - const type = this.getNodeType(builder) - - const { normalMapType, scaleNode } = this - - const normalOP = mul(this.node, 2.0) - let normalMap = sub(normalOP, 1.0) - - if (scaleNode !== null) { - const normalMapScale = mul(normalMap.xy, scaleNode) - normalMap = join(normalMapScale, normalMap.z) - } - - if (normalMapType === ObjectSpaceNormalMap) { - const vertexNormalNode = mul(new ModelNode(ModelNode.NORMAL_MATRIX), normalMap) - - const normal = normalize(vertexNormalNode) - - return normal.build(builder, type) - } else if (normalMapType === TangentSpaceNormalMap) { - const perturbNormal2ArbCall = perturbNormal2ArbNode({ - eye_pos: positionView, - surf_norm: normalView, - mapN: normalMap, - faceDirection: 1.0, - uv: uv(), - }) - - return perturbNormal2ArbCall.build(builder, type) - } - } -} - -export default NormalMapNode diff --git a/src/nodes/fog/FogNode.js b/src/nodes/fog/FogNode.js deleted file mode 100644 index a07efc88..00000000 --- a/src/nodes/fog/FogNode.js +++ /dev/null @@ -1,18 +0,0 @@ -import Node from '../core/Node.js' - -class FogNode extends Node { - constructor(colorNode, factorNode) { - super('float') - - this.colorNode = colorNode - this.factorNode = factorNode - } - - generate(builder) { - return this.factorNode.build(builder, 'float') - } -} - -FogNode.prototype.isFogNode = true - -export default FogNode diff --git a/src/nodes/fog/FogRangeNode.js b/src/nodes/fog/FogRangeNode.js deleted file mode 100644 index f682be45..00000000 --- a/src/nodes/fog/FogRangeNode.js +++ /dev/null @@ -1,21 +0,0 @@ -import FogNode from './FogNode.js' -import { smoothstep, negate, positionView } from '../ShaderNode.js' - -class FogRangeNode extends FogNode { - constructor(colorNode, nearNode, farNode) { - super(colorNode) - - this.nearNode = nearNode - this.farNode = farNode - } - - generate(builder) { - this.factorNode = smoothstep(this.nearNode, this.farNode, negate(positionView.z)) - - return super.generate(builder) - } -} - -FogRangeNode.prototype.isFogRangeNode = true - -export default FogRangeNode diff --git a/src/nodes/functions/BSDFs.js b/src/nodes/functions/BSDFs.js deleted file mode 100644 index 8bb031d3..00000000 --- a/src/nodes/functions/BSDFs.js +++ /dev/null @@ -1,124 +0,0 @@ -import { - ShaderNode, - add, - addTo, - sub, - mul, - div, - saturate, - dot, - pow, - pow2, - exp2, - normalize, - max, - sqrt, - negate, - cond, - greaterThan, - and, - transformedNormalView, - positionViewDirection, - diffuseColor, - specularColor, - roughness, - EPSILON, -} from '../ShaderNode.js' - -export const F_Schlick = new ShaderNode((inputs) => { - const { f0, f90, dotVH } = inputs - - // Original approximation by Christophe Schlick '94 - // float fresnel = pow( 1.0 - dotVH, 5.0 ); - - // Optimized variant (presented by Epic at SIGGRAPH '13) - // https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf - const fresnel = exp2(mul(sub(mul(-5.55473, dotVH), 6.98316), dotVH)) - - return add(mul(f0, sub(1.0, fresnel)), mul(f90, fresnel)) -}) // validated - -export const BRDF_Lambert = new ShaderNode((inputs) => { - return mul(1 / Math.PI, inputs.diffuseColor) // punctual light -}) // validated - -export const getDistanceAttenuation = new ShaderNode((inputs) => { - const { lightDistance, cutoffDistance, decayExponent } = inputs - - return cond( - and(greaterThan(cutoffDistance, 0), greaterThan(decayExponent, 0)), - pow(saturate(add(div(negate(lightDistance), cutoffDistance), 1.0)), decayExponent), - 1.0, - ) -}) // validated - -// -// STANDARD -// - -// Moving Frostbite to Physically Based Rendering 3.0 - page 12, listing 2 -// https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf -export const V_GGX_SmithCorrelated = new ShaderNode((inputs) => { - const { alpha, dotNL, dotNV } = inputs - - const a2 = pow2(alpha) - - const gv = mul(dotNL, sqrt(add(a2, mul(sub(1.0, a2), pow2(dotNV))))) - const gl = mul(dotNV, sqrt(add(a2, mul(sub(1.0, a2), pow2(dotNL))))) - - return div(0.5, max(add(gv, gl), EPSILON)) -}) // validated - -// Microfacet Models for Refraction through Rough Surfaces - equation (33) -// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html -// alpha is "roughness squared" in Disney’s reparameterization -export const D_GGX = new ShaderNode((inputs) => { - const { alpha, dotNH } = inputs - - const a2 = pow2(alpha) - - const denom = add(mul(pow2(dotNH), sub(a2, 1.0)), 1.0) // avoid alpha = 0 with dotNH = 1 - - return mul(1 / Math.PI, div(a2, pow2(denom))) -}) // validated - -// GGX Distribution, Schlick Fresnel, GGX_SmithCorrelated Visibility -export const BRDF_GGX = new ShaderNode((inputs) => { - const { lightDirection, f0, f90, roughness } = inputs - - const alpha = pow2(roughness) // UE4's roughness - - const halfDir = normalize(add(lightDirection, positionViewDirection)) - - const dotNL = saturate(dot(transformedNormalView, lightDirection)) - const dotNV = saturate(dot(transformedNormalView, positionViewDirection)) - const dotNH = saturate(dot(transformedNormalView, halfDir)) - const dotVH = saturate(dot(positionViewDirection, halfDir)) - - const F = F_Schlick({ f0, f90, dotVH }) - - const V = V_GGX_SmithCorrelated({ alpha, dotNL, dotNV }) - - const D = D_GGX({ alpha, dotNH }) - - return mul(F, mul(V, D)) -}) // validated - -export const RE_Direct_Physical = new ShaderNode((inputs) => { - const { lightDirection, lightColor, directDiffuse, directSpecular } = inputs - - const dotNL = saturate(dot(transformedNormalView, lightDirection)) - let irradiance = mul(dotNL, lightColor) - - irradiance = mul(irradiance, Math.PI) // punctual light - - addTo(directDiffuse, mul(irradiance, BRDF_Lambert({ diffuseColor: diffuseColor.rgb }))) - - addTo(directSpecular, mul(irradiance, BRDF_GGX({ lightDirection, f0: specularColor, f90: 1, roughness }))) -}) - -export const PhysicalLightingModel = new ShaderNode((inputs /*, builder*/) => { - // PHYSICALLY_CORRECT_LIGHTS <-> builder.renderer.physicallyCorrectLights === true - - RE_Direct_Physical(inputs) -}) diff --git a/src/nodes/functions/PhysicalMaterialFunctions.js b/src/nodes/functions/PhysicalMaterialFunctions.js deleted file mode 100644 index 74b72b3f..00000000 --- a/src/nodes/functions/PhysicalMaterialFunctions.js +++ /dev/null @@ -1,20 +0,0 @@ -import { ShaderNode, add, max, min, abs, dFdx, dFdy, normalGeometry } from '../ShaderNode.js' - -export const getGeometryRoughness = new ShaderNode(() => { - const dxy = max(abs(dFdx(normalGeometry)), abs(dFdy(normalGeometry))) - const geometryRoughness = max(max(dxy.x, dxy.y), dxy.z) - - return geometryRoughness -}) - -export const getRoughness = new ShaderNode((inputs) => { - const { roughness } = inputs - - const geometryRoughness = getGeometryRoughness() - - let roughnessFactor = max(roughness, 0.0525) // 0.0525 corresponds to the base mip of a 256 cubemap. - roughnessFactor = add(roughnessFactor, geometryRoughness) - roughnessFactor = min(roughnessFactor, 1.0) - - return roughnessFactor -}) diff --git a/src/nodes/lights/LightContextNode.js b/src/nodes/lights/LightContextNode.js deleted file mode 100644 index c424b550..00000000 --- a/src/nodes/lights/LightContextNode.js +++ /dev/null @@ -1,48 +0,0 @@ -import ContextNode from '../core/ContextNode.js' -import VarNode from '../core/VarNode.js' -import UniformNode from '../core/UniformNode.js' -import OperatorNode from '../math/OperatorNode.js' -import { PhysicalLightingModel } from '../functions/BSDFs.js' -import { Vector3 } from 'three' - -class LightContextNode extends ContextNode { - constructor(node) { - super(node) - } - - getNodeType(/*builder*/) { - return 'vec3' - } - - generate(builder) { - const material = builder.material - - let lightingModel = null - - if (material.isMeshStandardMaterial === true) { - lightingModel = PhysicalLightingModel - } - - const directDiffuse = new VarNode(new UniformNode(new Vector3()), 'DirectDiffuse', 'vec3') - const directSpecular = new VarNode(new UniformNode(new Vector3()), 'DirectSpecular', 'vec3') - - this.context.directDiffuse = directDiffuse - this.context.directSpecular = directSpecular - - if (lightingModel !== null) { - this.context.lightingModel = lightingModel - } - - // add code - - const type = this.getNodeType(builder) - - super.generate(builder, type) - - const totalLight = new OperatorNode('+', directDiffuse, directSpecular) - - return totalLight.build(builder, type) - } -} - -export default LightContextNode diff --git a/src/nodes/lights/LightNode.js b/src/nodes/lights/LightNode.js deleted file mode 100644 index bb9d0a32..00000000 --- a/src/nodes/lights/LightNode.js +++ /dev/null @@ -1,75 +0,0 @@ -import Node from '../core/Node.js' -import Object3DNode from '../accessors/Object3DNode.js' -import PositionNode from '../accessors/PositionNode.js' -import UniformNode from '../core/UniformNode.js' -import OperatorNode from '../math/OperatorNode.js' -import MathNode from '../math/MathNode.js' -import { NodeUpdateType } from '../core/constants.js' -import { getDistanceAttenuation } from '../functions/BSDFs.js' - -import { Color } from 'three' - -class LightNode extends Node { - constructor(light = null) { - super('vec3') - - this.updateType = NodeUpdateType.Object - - this.light = light - - this._colorNode = new UniformNode(new Color()) - - this._lightCutoffDistanceNode = new UniformNode(0) - this._lightDecayExponentNode = new UniformNode(0) - } - - getHash(/*builder*/) { - return this.light.uuid - } - - update(/* frame */) { - this._colorNode.value.copy(this.light.color).multiplyScalar(this.light.intensity) - this._lightCutoffDistanceNode.value = this.light.distance - this._lightDecayExponentNode.value = this.light.decay - } - - generate(builder) { - const lightPositionView = new Object3DNode(Object3DNode.VIEW_POSITION) - const positionView = new PositionNode(PositionNode.VIEW) - - const lVector = new OperatorNode('-', lightPositionView, positionView) - - const lightDirection = new MathNode(MathNode.NORMALIZE, lVector) - - const lightDistance = new MathNode(MathNode.LENGTH, lVector) - - const lightAttenuation = getDistanceAttenuation({ - lightDistance, - cutoffDistance: this._lightCutoffDistanceNode, - decayExponent: this._lightDecayExponentNode, - }) - - const lightColor = new OperatorNode('*', this._colorNode, lightAttenuation) - - lightPositionView.object3d = this.light - - const lightingModelFunction = builder.context.lightingModel - - if (lightingModelFunction !== undefined) { - const directDiffuse = builder.context.directDiffuse - const directSpecular = builder.context.directSpecular - - lightingModelFunction( - { - lightDirection, - lightColor, - directDiffuse, - directSpecular, - }, - builder, - ) - } - } -} - -export default LightNode diff --git a/src/nodes/lights/LightsNode.js b/src/nodes/lights/LightsNode.js deleted file mode 100644 index 107ac1c4..00000000 --- a/src/nodes/lights/LightsNode.js +++ /dev/null @@ -1,81 +0,0 @@ -import Node from '../core/Node.js' -import LightNode from './LightNode.js' - -const sortLights = (lights) => { - return lights.sort((a, b) => a.id - b.id) -} - -class LightsNode extends Node { - constructor(lightNodes = []) { - super('vec3') - - this.lightNodes = lightNodes - - this._hash = null - } - - get hasLight() { - return this.lightNodes.length > 0 - } - - generate(builder) { - const lightNodes = this.lightNodes - - for (const lightNode of lightNodes) { - lightNode.build(builder) - } - - return 'vec3( 0.0 )' - } - - getHash(/*builder*/) { - if (this._hash === null) { - let hash = '' - - const lightNodes = this.lightNodes - - for (const lightNode of lightNodes) { - hash += lightNode.light.uuid + ' ' - } - - this._hash = hash - } - - return this._hash - } - - getLightNodeByHash(hash) { - const lightNodes = this.lightNodes - - for (const lightNode of lightNodes) { - if (lightNode.light.uuid === hash) { - return lightNode - } - } - - return null - } - - fromLights(lights) { - const lightNodes = [] - - lights = sortLights(lights) - - for (const light of lights) { - let lightNode = this.getLightNodeByHash(light.uuid) - - if (lightNode === null) { - lightNode = new LightNode(light) - } - - lightNodes.push(lightNode) - } - - this.lightNodes = lightNodes - this._hash = null - - return this - } -} - -export default LightsNode diff --git a/src/nodes/loaders/NodeLoader.js b/src/nodes/loaders/NodeLoader.js deleted file mode 100644 index 6708bda9..00000000 --- a/src/nodes/loaders/NodeLoader.js +++ /dev/null @@ -1,238 +0,0 @@ -import { Loader } from 'three' -// core -import ArrayUniformNode from '../core/ArrayUniformNode.js' -import AttributeNode from '../core/AttributeNode.js' -import BypassNode from '../core/BypassNode.js' -import CodeNode from '../core/CodeNode.js' -import ConstNode from '../core/ConstNode.js' -import ContextNode from '../core/ContextNode.js' -import ExpressionNode from '../core/ExpressionNode.js' -import FunctionCallNode from '../core/FunctionCallNode.js' -import FunctionNode from '../core/FunctionNode.js' -import Node from '../core/Node.js' -import NodeAttribute from '../core/NodeAttribute.js' -import NodeBuilder from '../core/NodeBuilder.js' -import NodeCode from '../core/NodeCode.js' -import NodeFrame from '../core/NodeFrame.js' -import NodeFunctionInput from '../core/NodeFunctionInput.js' -import NodeKeywords from '../core/NodeKeywords.js' -import NodeUniform from '../core/NodeUniform.js' -import NodeVar from '../core/NodeVar.js' -import NodeVary from '../core/NodeVary.js' -import PropertyNode from '../core/PropertyNode.js' -import TempNode from '../core/TempNode.js' -import UniformNode from '../core/UniformNode.js' -import VarNode from '../core/VarNode.js' -import VaryNode from '../core/VaryNode.js' - -// accessors -import BufferNode from '../accessors/BufferNode.js' -import CameraNode from '../accessors/CameraNode.js' -import CubeTextureNode from '../accessors/CubeTextureNode.js' -import MaterialNode from '../accessors/MaterialNode.js' -import MaterialReferenceNode from '../accessors/MaterialReferenceNode.js' -import ModelNode from '../accessors/ModelNode.js' -import ModelViewProjectionNode from '../accessors/ModelViewProjectionNode.js' -import NormalNode from '../accessors/NormalNode.js' -import Object3DNode from '../accessors/Object3DNode.js' -import PointUVNode from '../accessors/PointUVNode.js' -import PositionNode from '../accessors/PositionNode.js' -import ReferenceNode from '../accessors/ReferenceNode.js' -import ReflectNode from '../accessors/ReflectNode.js' -import SkinningNode from '../accessors/SkinningNode.js' -import TextureNode from '../accessors/TextureNode.js' -import UVNode from '../accessors/UVNode.js' - -// display -import ColorSpaceNode from '../display/ColorSpaceNode.js' -import NormalMapNode from '../display/NormalMapNode.js' - -// math -import MathNode from '../math/MathNode.js' -import OperatorNode from '../math/OperatorNode.js' -import CondNode from '../math/CondNode.js' - -// lights -import LightContextNode from '../lights/LightContextNode.js' -import LightNode from '../lights/LightNode.js' -import LightsNode from '../lights/LightsNode.js' - -// utils -import ArrayElementNode from '../utils/ArrayElementNode.js' -import ConvertNode from '../utils/ConvertNode.js' -import JoinNode from '../utils/JoinNode.js' -import SplitNode from '../utils/SplitNode.js' -import SpriteSheetUVNode from '../utils/SpriteSheetUVNode.js' -import MatcapUVNode from '../utils/MatcapUVNode.js' -import OscNode from '../utils/OscNode.js' -import TimerNode from '../utils/TimerNode.js' - -// procedural -import CheckerNode from '../procedural/CheckerNode.js' - -// fog -import FogNode from '../fog/FogNode.js' -import FogRangeNode from '../fog/FogRangeNode.js' - -const nodeLib = { - // core - ArrayUniformNode, - AttributeNode, - BypassNode, - CodeNode, - ContextNode, - ConstNode, - ExpressionNode, - FunctionCallNode, - FunctionNode, - Node, - NodeAttribute, - NodeBuilder, - NodeCode, - NodeFrame, - NodeFunctionInput, - NodeKeywords, - NodeUniform, - NodeVar, - NodeVary, - PropertyNode, - TempNode, - UniformNode, - VarNode, - VaryNode, - - // accessors - BufferNode, - CameraNode, - CubeTextureNode, - MaterialNode, - MaterialReferenceNode, - ModelNode, - ModelViewProjectionNode, - NormalNode, - Object3DNode, - PointUVNode, - PositionNode, - ReferenceNode, - ReflectNode, - SkinningNode, - TextureNode, - UVNode, - - // display - ColorSpaceNode, - NormalMapNode, - - // math - MathNode, - OperatorNode, - CondNode, - - // lights - LightContextNode, - LightNode, - LightsNode, - - // utils - ArrayElementNode, - ConvertNode, - JoinNode, - SplitNode, - SpriteSheetUVNode, - MatcapUVNode, - OscNode, - TimerNode, - - // procedural - CheckerNode, - - // fog - FogNode, - FogRangeNode, -} - -const fromType = (type) => { - return new nodeLib[type]() -} - -class NodeLoader extends Loader { - constructor(manager) { - super(manager) - - this.textures = {} - } - - load(url, onLoad, onProgress, onError) { - const loader = new FileLoader(this.manager) - loader.setPath(this.path) - loader.setRequestHeader(this.requestHeader) - loader.setWithCredentials(this.withCredentials) - loader.load( - url, - (text) => { - try { - onLoad(this.parse(JSON.parse(text))) - } catch (e) { - if (onError) { - onError(e) - } else { - console.error(e) - } - - this.manager.itemError(url) - } - }, - onProgress, - onError, - ) - } - - parseNodes(json) { - const nodes = {} - - if (json !== undefined) { - for (const nodeJSON of json) { - const { uuid, type } = nodeJSON - - nodes[uuid] = fromType(type) - nodes[uuid].uuid = uuid - } - - const meta = { nodes, textures: this.textures } - - for (const nodeJSON of json) { - nodeJSON.meta = meta - - const node = nodes[nodeJSON.uuid] - node.deserialize(nodeJSON) - - delete nodeJSON.meta - } - } - - return nodes - } - - parse(json) { - const node = fromType(type) - node.uuid = json.uuid - - const nodes = this.parseNodes(json.inputNodes) - const meta = { nodes, textures: this.textures } - - json.meta = meta - - node.deserialize(json) - - delete json.meta - - return node - } - - setTextures(value) { - this.textures = value - return this - } -} - -export default NodeLoader diff --git a/src/nodes/loaders/NodeMaterialLoader.js b/src/nodes/loaders/NodeMaterialLoader.js deleted file mode 100644 index 0368e5ba..00000000 --- a/src/nodes/loaders/NodeMaterialLoader.js +++ /dev/null @@ -1,32 +0,0 @@ -import { MaterialLoader } from 'three' - -class NodeMaterialLoader extends MaterialLoader { - constructor(manager) { - super(manager) - - this.nodes = {} - } - - parse(json) { - const material = super.parse(json) - - const nodes = this.nodes - const inputNodes = json.inputNodes - - for (const property in inputNodes) { - const uuid = inputNodes[property] - - material[property] = nodes[uuid] - } - - return material - } - - setNodes(value) { - this.nodes = value - - return this - } -} - -export default NodeMaterialLoader diff --git a/src/nodes/loaders/NodeObjectLoader.js b/src/nodes/loaders/NodeObjectLoader.js deleted file mode 100644 index d40b600f..00000000 --- a/src/nodes/loaders/NodeObjectLoader.js +++ /dev/null @@ -1,54 +0,0 @@ -import NodeLoader from './NodeLoader.js' -import NodeMaterialLoader from './NodeMaterialLoader.js' -import { ObjectLoader } from 'three' - -class NodeObjectLoader extends ObjectLoader { - constructor(manager) { - super(manager) - - this._nodesJSON = null - } - - parse(json, onLoad) { - this._nodesJSON = json.nodes - - const data = super.parse(json, onLoad) - - this._nodesJSON = null // dispose - - return data - } - - parseNodes(json, textures) { - if (json !== undefined) { - const loader = new NodeLoader() - loader.setTextures(textures) - - return loader.parseNodes(json) - } - - return {} - } - - parseMaterials(json, textures) { - const materials = {} - - if (json !== undefined) { - const nodes = this.parseNodes(this._nodesJSON, textures) - - const loader = new NodeMaterialLoader() - loader.setTextures(textures) - loader.setNodes(nodes) - - for (let i = 0, l = json.length; i < l; i++) { - const data = json[i] - - materials[data.uuid] = loader.parse(data) - } - } - - return materials - } -} - -export default NodeObjectLoader diff --git a/src/nodes/materials/LineBasicNodeMaterial.js b/src/nodes/materials/LineBasicNodeMaterial.js deleted file mode 100644 index 224be9f7..00000000 --- a/src/nodes/materials/LineBasicNodeMaterial.js +++ /dev/null @@ -1,40 +0,0 @@ -import NodeMaterial from './NodeMaterial.js' -import { LineBasicMaterial } from 'three' - -const defaultValues = new LineBasicMaterial() - -class LineBasicNodeMaterial extends NodeMaterial { - constructor(parameters) { - super() - - this.colorNode = null - this.opacityNode = null - - this.alphaTestNode = null - - this.lightNode = null - - this.positionNode = null - - this.setDefaultValues(defaultValues) - - this.setValues(parameters) - } - - copy(source) { - this.colorNode = source.colorNode - this.opacityNode = source.opacityNode - - this.alphaTestNode = source.alphaTestNode - - this.lightNode = source.lightNode - - this.positionNode = source.positionNode - - return super.copy(source) - } -} - -LineBasicNodeMaterial.prototype.isLineBasicNodeMaterial = true - -export default LineBasicNodeMaterial diff --git a/src/nodes/materials/Materials.js b/src/nodes/materials/Materials.js deleted file mode 100644 index c5a689f2..00000000 --- a/src/nodes/materials/Materials.js +++ /dev/null @@ -1,24 +0,0 @@ -import LineBasicNodeMaterial from './LineBasicNodeMaterial.js' -import MeshBasicNodeMaterial from './MeshBasicNodeMaterial.js' -import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js' -import PointsNodeMaterial from './PointsNodeMaterial.js' -import { Material } from 'three' - -export { LineBasicNodeMaterial, MeshBasicNodeMaterial, MeshStandardNodeMaterial, PointsNodeMaterial } - -const materialLib = { - LineBasicNodeMaterial, - MeshBasicNodeMaterial, - MeshStandardNodeMaterial, - PointsNodeMaterial, -} - -const fromTypeFunction = Material.fromType - -Material.fromType = function (type) { - if (materialLib[type] !== undefined) { - return new materialLib[type]() - } - - return fromTypeFunction.call(this, type) -} diff --git a/src/nodes/materials/MeshBasicNodeMaterial.js b/src/nodes/materials/MeshBasicNodeMaterial.js deleted file mode 100644 index 2d03ecdf..00000000 --- a/src/nodes/materials/MeshBasicNodeMaterial.js +++ /dev/null @@ -1,42 +0,0 @@ -import NodeMaterial from './NodeMaterial.js' -import { MeshBasicMaterial } from 'three' - -const defaultValues = new MeshBasicMaterial() - -class MeshBasicNodeMaterial extends NodeMaterial { - constructor(parameters) { - super() - - this.lights = true - - this.colorNode = null - this.opacityNode = null - - this.alphaTestNode = null - - this.lightNode = null - - this.positionNode = null - - this.setDefaultValues(defaultValues) - - this.setValues(parameters) - } - - copy(source) { - this.colorNode = source.colorNode - this.opacityNode = source.opacityNode - - this.alphaTestNode = source.alphaTestNode - - this.lightNode = source.lightNode - - this.positionNode = source.positionNode - - return super.copy(source) - } -} - -MeshBasicNodeMaterial.prototype.isMeshBasicNodeMaterial = true - -export default MeshBasicNodeMaterial diff --git a/src/nodes/materials/MeshStandardNodeMaterial.js b/src/nodes/materials/MeshStandardNodeMaterial.js deleted file mode 100644 index cd0b91b9..00000000 --- a/src/nodes/materials/MeshStandardNodeMaterial.js +++ /dev/null @@ -1,62 +0,0 @@ -import NodeMaterial from './NodeMaterial.js' -import { MeshStandardMaterial } from 'three' - -const defaultValues = new MeshStandardMaterial() - -export default class MeshStandardNodeMaterial extends NodeMaterial { - constructor(parameters) { - super() - - this.colorNode = null - this.opacityNode = null - - this.alphaTestNode = null - - this.normalNode = null - - this.emissiveNode = null - - this.metalnessNode = null - this.roughnessNode = null - - this.clearcoatNode = null - this.clearcoatRoughnessNode = null - - this.envNode = null - - this.lightNode = null - - this.positionNode = null - - this.setDefaultValues(defaultValues) - - this.setValues(parameters) - } - - copy(source) { - this.colorNode = source.colorNode - this.opacityNode = source.opacityNode - - this.alphaTestNode = source.alphaTestNode - - this.normalNode = source.normalNode - - this.emissiveNode = source.emissiveNode - - this.metalnessNode = source.metalnessNode - this.roughnessNode = source.roughnessNode - - this.clearcoatNode = source.clearcoatNode - this.clearcoatRoughnessNode = source.clearcoatRoughnessNode - - this.envNode = source.envNode - - this.lightNode = source.lightNode - - this.positionNode = source.positionNode - - return super.copy(source) - } -} - -MeshStandardNodeMaterial.prototype.isMeshStandardNodeMaterial = true diff --git a/src/nodes/materials/NodeMaterial.js b/src/nodes/materials/NodeMaterial.js deleted file mode 100644 index fd1cd9db..00000000 --- a/src/nodes/materials/NodeMaterial.js +++ /dev/null @@ -1,84 +0,0 @@ -import { Material, ShaderMaterial } from 'three' -import { getNodesKeys } from '../core/NodeUtils.js' - -class NodeMaterial extends ShaderMaterial { - constructor() { - super() - - this.type = this.constructor.name - - this.lights = true - } - - setDefaultValues(values) { - // This approach is to reuse the native refreshUniforms* - // and turn available the use of features like transmission and environment in core - - let value - - for (const property in values) { - value = values[property] - - if (this[property] === undefined) { - if (value && typeof value.clone === 'function') { - this[property] = value.clone() - } else { - this[property] = value - } - } - } - - Object.assign(this.defines, values.defines) - } - - toJSON(meta) { - const isRoot = meta === undefined || typeof meta === 'string' - - if (isRoot) { - meta = { - textures: {}, - images: {}, - nodes: {}, - } - } - - const data = Material.prototype.toJSON.call(this, meta) - const nodeKeys = getNodesKeys(this) - - data.inputNodes = {} - - for (const name of nodeKeys) { - data.inputNodes[name] = this[name].toJSON(meta).uuid - } - - // TODO: Copied from Object3D.toJSON - - function extractFromCache(cache) { - const values = [] - - for (const key in cache) { - const data = cache[key] - delete data.metadata - values.push(data) - } - - return values - } - - if (isRoot) { - const textures = extractFromCache(meta.textures) - const images = extractFromCache(meta.images) - const nodes = extractFromCache(meta.nodes) - - if (textures.length > 0) data.textures = textures - if (images.length > 0) data.images = images - if (nodes.length > 0) data.nodes = nodes - } - - return data - } -} - -NodeMaterial.prototype.isNodeMaterial = true - -export default NodeMaterial diff --git a/src/nodes/materials/PointsNodeMaterial.js b/src/nodes/materials/PointsNodeMaterial.js deleted file mode 100644 index ef03df7f..00000000 --- a/src/nodes/materials/PointsNodeMaterial.js +++ /dev/null @@ -1,46 +0,0 @@ -import NodeMaterial from './NodeMaterial.js' -import { PointsMaterial } from 'three' - -const defaultValues = new PointsMaterial() - -class PointsNodeMaterial extends NodeMaterial { - constructor(parameters) { - super() - - this.transparent = true - - this.colorNode = null - this.opacityNode = null - - this.alphaTestNode = null - - this.lightNode = null - - this.sizeNode = null - - this.positionNode = null - - this.setDefaultValues(defaultValues) - - this.setValues(parameters) - } - - copy(source) { - this.colorNode = source.colorNode - this.opacityNode = source.opacityNode - - this.alphaTestNode = source.alphaTestNode - - this.lightNode = source.lightNode - - this.sizeNode = source.sizeNode - - this.positionNode = source.positionNode - - return super.copy(source) - } -} - -PointsNodeMaterial.prototype.isPointsNodeMaterial = true - -export default PointsNodeMaterial diff --git a/src/nodes/math/CondNode.js b/src/nodes/math/CondNode.js deleted file mode 100644 index de96eb3a..00000000 --- a/src/nodes/math/CondNode.js +++ /dev/null @@ -1,50 +0,0 @@ -import Node from '../core/Node.js' -import PropertyNode from '../core/PropertyNode.js' -import ContextNode from '../core/ContextNode.js' - -class CondNode extends Node { - constructor(node, ifNode, elseNode) { - super() - - this.node = node - - this.ifNode = ifNode - this.elseNode = elseNode - } - - getNodeType(builder) { - const ifType = this.ifNode.getNodeType(builder) - const elseType = this.elseNode.getNodeType(builder) - - if (builder.getTypeLength(elseType) > builder.getTypeLength(ifType)) { - return elseType - } - - return ifType - } - - generate(builder) { - const type = this.getNodeType(builder) - - const context = { temp: false } - const nodeProperty = new PropertyNode(null, type).build(builder) - - const nodeSnippet = new ContextNode(this.node /*, context*/).build(builder, 'bool'), - ifSnippet = new ContextNode(this.ifNode, context).build(builder, type), - elseSnippet = new ContextNode(this.elseNode, context).build(builder, type) - - builder.addFlowCode(`if ( ${nodeSnippet} ) { - -\t\t${nodeProperty} = ${ifSnippet}; - -\t} else { - -\t\t${nodeProperty} = ${elseSnippet}; - -\t}`) - - return nodeProperty - } -} - -export default CondNode diff --git a/src/nodes/math/MathNode.js b/src/nodes/math/MathNode.js deleted file mode 100644 index c6120365..00000000 --- a/src/nodes/math/MathNode.js +++ /dev/null @@ -1,193 +0,0 @@ -import TempNode from '../core/TempNode.js' -import ExpressionNode from '../core/ExpressionNode.js' -import JoinNode from '../utils/JoinNode.js' -import SplitNode from '../utils/SplitNode.js' -import OperatorNode from './OperatorNode.js' - -class MathNode extends TempNode { - // 1 input - - static RAD = 'radians' - static DEG = 'degrees' - static EXP = 'exp' - static EXP2 = 'exp2' - static LOG = 'log' - static LOG2 = 'log2' - static SQRT = 'sqrt' - static INV_SQRT = 'inversesqrt' - static FLOOR = 'floor' - static CEIL = 'ceil' - static NORMALIZE = 'normalize' - static FRACT = 'fract' - static SIN = 'sin' - static COS = 'cos' - static TAN = 'tan' - static ASIN = 'asin' - static ACOS = 'acos' - static ATAN = 'atan' - static ABS = 'abs' - static SIGN = 'sign' - static LENGTH = 'length' - static NEGATE = 'negate' - static INVERT = 'invert' - static DFDX = 'dFdx' - static DFDY = 'dFdy' - static SATURATE = 'saturate' - static ROUND = 'round' - - // 2 inputs - - static MIN = 'min' - static MAX = 'max' - static MOD = 'mod' - static STEP = 'step' - static REFLECT = 'reflect' - static DISTANCE = 'distance' - static DOT = 'dot' - static CROSS = 'cross' - static POW = 'pow' - static TRANSFORM_DIRECTION = 'transformDirection' - - // 3 inputs - - static MIX = 'mix' - static CLAMP = 'clamp' - static REFRACT = 'refract' - static SMOOTHSTEP = 'smoothstep' - static FACEFORWARD = 'faceforward' - - constructor(method, aNode, bNode = null, cNode = null) { - super() - - this.method = method - - this.aNode = aNode - this.bNode = bNode - this.cNode = cNode - } - - getInputType(builder) { - const aType = this.aNode.getNodeType(builder) - const bType = this.bNode ? this.bNode.getNodeType(builder) : null - const cType = this.cNode ? this.cNode.getNodeType(builder) : null - - const aLen = builder.getTypeLength(aType) - const bLen = builder.getTypeLength(bType) - const cLen = builder.getTypeLength(cType) - - if (aLen > bLen && aLen > cLen) { - return aType - } else if (bLen > cLen) { - return bType - } else if (cLen > aLen) { - return cType - } - - return aType - } - - getNodeType(builder) { - const method = this.method - - if (method === MathNode.LENGTH || method === MathNode.DISTANCE || method === MathNode.DOT) { - return 'float' - } else if (method === MathNode.CROSS) { - return 'vec3' - } else { - return this.getInputType(builder) - } - } - - generate(builder, output) { - const method = this.method - - const type = this.getNodeType(builder) - const inputType = this.getInputType(builder) - - const a = this.aNode - const b = this.bNode - const c = this.cNode - - const isWebGL = builder.renderer.isWebGLRenderer === true - - if (isWebGL && (method === MathNode.DFDX || method === MathNode.DFDY) && output === 'vec3') { - // Workaround for Adreno 3XX dFd*( vec3 ) bug. See #9988 - - return new JoinNode([ - new MathNode(method, new SplitNode(a, 'x')), - new MathNode(method, new SplitNode(a, 'y')), - new MathNode(method, new SplitNode(a, 'z')), - ]).build(builder) - } else if (method === MathNode.TRANSFORM_DIRECTION) { - // dir can be either a direction vector or a normal vector - // upper-left 3x3 of matrix is assumed to be orthogonal - - let tA = a - let tB = b - - if (builder.isMatrix(tA.getNodeType(builder))) { - tB = new ExpressionNode(`${builder.getType('vec4')}( ${tB.build(builder, 'vec3')}, 0.0 )`, 'vec4') - } else { - tA = new ExpressionNode(`${builder.getType('vec4')}( ${tA.build(builder, 'vec3')}, 0.0 )`, 'vec4') - } - - const mulNode = new SplitNode(new OperatorNode('*', tA, tB), 'xyz') - - return new MathNode(MathNode.NORMALIZE, mulNode).build(builder) - } else if (method === MathNode.SATURATE) { - return builder.format(`clamp( ${a.build(builder, inputType)}, 0.0, 1.0 )`, type, output) - } else if (method === MathNode.NEGATE) { - return builder.format('( -' + a.build(builder, inputType) + ' )', type, output) - } else if (method === MathNode.INVERT) { - return builder.format('( 1.0 - ' + a.build(builder, inputType) + ' )', type, output) - } else { - const params = [] - - if (method === MathNode.CROSS) { - params.push(a.build(builder, type), b.build(builder, type)) - } else if (method === MathNode.STEP) { - params.push( - a.build(builder, builder.getTypeLength(a.getNodeType(builder)) === 1 ? 'float' : inputType), - b.build(builder, inputType), - ) - } else if ((isWebGL && (method === MathNode.MIN || method === MathNode.MAX)) || method === MathNode.MOD) { - params.push( - a.build(builder, inputType), - b.build(builder, builder.getTypeLength(b.getNodeType(builder)) === 1 ? 'float' : inputType), - ) - } else if (method === MathNode.REFRACT) { - params.push(a.build(builder, inputType), b.build(builder, inputType), c.build(builder, 'float')) - } else if (method === MathNode.MIX) { - params.push( - a.build(builder, inputType), - b.build(builder, inputType), - c.build(builder, builder.getTypeLength(c.getNodeType(builder)) === 1 ? 'float' : inputType), - ) - } else { - params.push(a.build(builder, inputType)) - - if (c !== null) { - params.push(b.build(builder, inputType), c.build(builder, inputType)) - } else if (b !== null) { - params.push(b.build(builder, inputType)) - } - } - - return builder.format(`${builder.getMethod(method)}( ${params.join(', ')} )`, type, output) - } - } - - serialize(data) { - super.serialize(data) - - data.method = this.method - } - - deserialize(data) { - super.deserialize(data) - - this.method = data.method - } -} - -export default MathNode diff --git a/src/nodes/math/OperatorNode.js b/src/nodes/math/OperatorNode.js deleted file mode 100644 index 529396d6..00000000 --- a/src/nodes/math/OperatorNode.js +++ /dev/null @@ -1,140 +0,0 @@ -import TempNode from '../core/TempNode.js' - -class OperatorNode extends TempNode { - constructor(op, aNode, bNode, ...params) { - super() - - this.op = op - - if (params.length > 0) { - let finalBNode = bNode - - for (let i = 0; i < params.length; i++) { - finalBNode = new OperatorNode(op, finalBNode, params[i]) - } - - bNode = finalBNode - } - - this.aNode = aNode - this.bNode = bNode - } - - getNodeType(builder, output) { - const op = this.op - - const aNode = this.aNode - const bNode = this.bNode - - const typeA = aNode.getNodeType(builder) - const typeB = bNode.getNodeType(builder) - - if (typeA === 'void' || typeB === 'void') { - return 'void' - } else if (op === '=' || op === '%') { - return typeA - } else if (op === '&' || op === '|' || op === '^' || op === '>>' || op === '<<') { - return 'int' - } else if (op === '==' || op === '&&' || op === '||' || op === '^^') { - return 'bool' - } else if (op === '<' || op === '>' || op === '<=' || op === '>=') { - const typeLength = builder.getTypeLength(output) - - return typeLength > 1 ? `bvec${typeLength}` : 'bool' - } else { - if (typeA === 'float' && builder.isMatrix(typeB)) { - return typeB - } else if (builder.isMatrix(typeA) && builder.isVector(typeB)) { - // matrix x vector - - return builder.getVectorFromMatrix(typeA) - } else if (builder.isVector(typeA) && builder.isMatrix(typeB)) { - // vector x matrix - - return builder.getVectorFromMatrix(typeB) - } else if (builder.getTypeLength(typeB) > builder.getTypeLength(typeA)) { - // anytype x anytype: use the greater length vector - - return typeB - } - - return typeA - } - } - - generate(builder, output) { - const op = this.op - - const aNode = this.aNode - const bNode = this.bNode - - const type = this.getNodeType(builder, output) - - let typeA = null - let typeB = null - - if (type !== 'void') { - typeA = aNode.getNodeType(builder) - typeB = bNode.getNodeType(builder) - - if (op === '=') { - typeB = typeA - } else if (op === '<' || op === '>' || op === '<=' || op === '>=') { - if (builder.isVector(typeA)) { - typeB = typeA - } else { - typeA = typeB = 'float' - } - } else if (builder.isMatrix(typeA) && builder.isVector(typeB)) { - // matrix x vector - - typeB = builder.getVectorFromMatrix(typeA) - } else if (builder.isVector(typeA) && builder.isMatrix(typeB)) { - // vector x matrix - - typeA = builder.getVectorFromMatrix(typeB) - } else { - // anytype x anytype - - typeA = typeB = type - } - } else { - typeA = typeB = type - } - - const a = aNode.build(builder, typeA) - const b = bNode.build(builder, typeB) - - const outputLength = builder.getTypeLength(output) - - if (output !== 'void') { - if (op === '=') { - builder.addFlowCode(`${a} ${this.op} ${b}`) - - return a - } else if (op === '>' && outputLength > 1) { - return builder.format(`${builder.getMethod('greaterThan')}( ${a}, ${b} )`, type, output) - } else if (op === '<=' && outputLength > 1) { - return builder.format(`${builder.getMethod('lessThanEqual')}( ${a}, ${b} )`, type, output) - } else { - return builder.format(`( ${a} ${this.op} ${b} )`, type, output) - } - } else if (typeA !== 'void') { - return builder.format(`${a} ${this.op} ${b}`, type, output) - } - } - - serialize(data) { - super.serialize(data) - - data.op = this.op - } - - deserialize(data) { - super.deserialize(data) - - this.op = data.op - } -} - -export default OperatorNode diff --git a/src/nodes/parsers/GLSLNodeFunction.js b/src/nodes/parsers/GLSLNodeFunction.js deleted file mode 100644 index d5b72343..00000000 --- a/src/nodes/parsers/GLSLNodeFunction.js +++ /dev/null @@ -1,113 +0,0 @@ -import NodeFunction from '../core/NodeFunction.js' -import NodeFunctionInput from '../core/NodeFunctionInput.js' - -const declarationRegexp = /^\s*(highp|mediump|lowp)?\s*([a-z_0-9]+)\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)/i -const propertiesRegexp = /[a-z_0-9]+/gi - -const pragmaMain = '#pragma main' - -const parse = (source) => { - source = source.trim() - - const pragmaMainIndex = source.indexOf(pragmaMain) - - const mainCode = pragmaMainIndex !== -1 ? source.slice(pragmaMainIndex + pragmaMain.length) : source - - const declaration = mainCode.match(declarationRegexp) - - if (declaration !== null && declaration.length === 5) { - // tokenizer - - const inputsCode = declaration[4] - const propsMatches = [] - - let nameMatch = null - - while ((nameMatch = propertiesRegexp.exec(inputsCode)) !== null) { - propsMatches.push(nameMatch) - } - - // parser - - const inputs = [] - - let i = 0 - - while (i < propsMatches.length) { - const isConst = propsMatches[i][0] === 'const' - - if (isConst === true) { - i++ - } - - let qualifier = propsMatches[i][0] - - if (qualifier === 'in' || qualifier === 'out' || qualifier === 'inout') { - i++ - } else { - qualifier = '' - } - - const type = propsMatches[i++][0] - - let count = Number.parseInt(propsMatches[i][0]) - - if (Number.isNaN(count) === false) i++ - else count = null - - const name = propsMatches[i++][0] - - inputs.push(new NodeFunctionInput(type, name, count, qualifier, isConst)) - } - - // - - const blockCode = mainCode.substring(declaration[0].length) - - const name = declaration[3] !== undefined ? declaration[3] : '' - const type = declaration[2] - - const presicion = declaration[1] !== undefined ? declaration[1] : '' - - const headerCode = pragmaMainIndex !== -1 ? source.slice(0, pragmaMainIndex) : '' - - return { - type, - inputs, - name, - presicion, - inputsCode, - blockCode, - headerCode, - } - } else { - throw new Error('FunctionNode: Function is not a GLSL code.') - } -} - -class GLSLNodeFunction extends NodeFunction { - constructor(source) { - const { type, inputs, name, presicion, inputsCode, blockCode, headerCode } = parse(source) - - super(type, inputs, name, presicion) - - this.inputsCode = inputsCode - this.blockCode = blockCode - this.headerCode = headerCode - } - - getCode(name = this.name) { - const headerCode = this.headerCode - const presicion = this.presicion - - let declarationCode = `${this.type} ${name} ( ${this.inputsCode.trim()} )` - - if (presicion !== '') { - declarationCode = `${presicion} ${declarationCode}` - } - - return headerCode + declarationCode + this.blockCode - } -} - -export default GLSLNodeFunction diff --git a/src/nodes/parsers/GLSLNodeParser.js b/src/nodes/parsers/GLSLNodeParser.js deleted file mode 100644 index 1234031c..00000000 --- a/src/nodes/parsers/GLSLNodeParser.js +++ /dev/null @@ -1,10 +0,0 @@ -import NodeParser from '../core/NodeParser.js' -import GLSLNodeFunction from './GLSLNodeFunction.js' - -class GLSLNodeParser extends NodeParser { - parseFunction(source) { - return new GLSLNodeFunction(source) - } -} - -export default GLSLNodeParser diff --git a/src/nodes/parsers/WGSLNodeFunction.js b/src/nodes/parsers/WGSLNodeFunction.js deleted file mode 100644 index df28a896..00000000 --- a/src/nodes/parsers/WGSLNodeFunction.js +++ /dev/null @@ -1,79 +0,0 @@ -import NodeFunction from '../core/NodeFunction.js' -import NodeFunctionInput from '../core/NodeFunctionInput.js' - -const declarationRegexp = /^fn\s*([a-z_0-9]+)?\s*\(([\s\S]*?)\)\s*\-\>\s*([a-z_0-9]+)?/i -const propertiesRegexp = /[a-z_0-9]+/gi - -const parse = (source) => { - source = source.trim() - - const declaration = source.match(declarationRegexp) - - if (declaration !== null && declaration.length === 4) { - // tokenizer - - const inputsCode = declaration[2] - const propsMatches = [] - - let nameMatch = null - - while ((nameMatch = propertiesRegexp.exec(inputsCode)) !== null) { - propsMatches.push(nameMatch) - } - - // parser - - const inputs = [] - - let i = 0 - - while (i < propsMatches.length) { - // default - - const name = propsMatches[i++][0] - const type = propsMatches[i++][0] - - // precision - - if (i < propsMatches.length && /^[fui]\d{2}$/.test(propsMatches[i][0]) === true) i++ - - // add input - - inputs.push(new NodeFunctionInput(type, name)) - } - - // - - const blockCode = source.substring(declaration[0].length) - - const name = declaration[1] !== undefined ? declaration[1] : '' - const type = declaration[3] - - return { - type, - inputs, - name, - inputsCode, - blockCode, - } - } else { - throw new Error('FunctionNode: Function is not a WGSL code.') - } -} - -class WGSLNodeFunction extends NodeFunction { - constructor(source) { - const { type, inputs, name, inputsCode, blockCode } = parse(source) - - super(type, inputs, name) - - this.inputsCode = inputsCode - this.blockCode = blockCode - } - - getCode(name = this.name) { - return `fn ${name} ( ${this.inputsCode.trim()} ) -> ${this.type}` + this.blockCode - } -} - -export default WGSLNodeFunction diff --git a/src/nodes/parsers/WGSLNodeParser.js b/src/nodes/parsers/WGSLNodeParser.js deleted file mode 100644 index 8f372a09..00000000 --- a/src/nodes/parsers/WGSLNodeParser.js +++ /dev/null @@ -1,10 +0,0 @@ -import NodeParser from '../core/NodeParser.js' -import WGSLNodeFunction from './WGSLNodeFunction.js' - -class WGSLNodeParser extends NodeParser { - parseFunction(source) { - return new WGSLNodeFunction(source) - } -} - -export default WGSLNodeParser diff --git a/src/nodes/procedural/CheckerNode.js b/src/nodes/procedural/CheckerNode.js deleted file mode 100644 index c93b02d2..00000000 --- a/src/nodes/procedural/CheckerNode.js +++ /dev/null @@ -1,27 +0,0 @@ -import Node from '../core/Node.js' - -import { ShaderNode, uv, add, mul, floor, mod, sign } from '../ShaderNode.js' - -const checkerShaderNode = new ShaderNode((inputs) => { - const uv = mul(inputs.uv, 2.0) - - const cx = floor(uv.x) - const cy = floor(uv.y) - const result = mod(add(cx, cy), 2.0) - - return sign(result) -}) - -class CheckerNode extends Node { - constructor(uvNode = uv()) { - super('float') - - this.uvNode = uvNode - } - - generate(builder) { - return checkerShaderNode({ uv: this.uvNode }).build(builder) - } -} - -export default CheckerNode diff --git a/src/nodes/utils/ArrayElementNode.js b/src/nodes/utils/ArrayElementNode.js deleted file mode 100644 index 3bfd6513..00000000 --- a/src/nodes/utils/ArrayElementNode.js +++ /dev/null @@ -1,23 +0,0 @@ -import Node from '../core/Node.js' - -class ArrayElementNode extends Node { - constructor(node, indexNode) { - super() - - this.node = node - this.indexNode = indexNode - } - - getNodeType(builder) { - return this.node.getNodeType(builder) - } - - generate(builder) { - const nodeSnippet = this.node.build(builder) - const indexSnippet = this.indexNode.build(builder, 'int') - - return `${nodeSnippet}[ ${indexSnippet} ]` - } -} - -export default ArrayElementNode diff --git a/src/nodes/utils/ConvertNode.js b/src/nodes/utils/ConvertNode.js deleted file mode 100644 index a458cc85..00000000 --- a/src/nodes/utils/ConvertNode.js +++ /dev/null @@ -1,30 +0,0 @@ -import Node from '../core/Node.js' - -class ConvertNode extends Node { - constructor(node, convertTo) { - super() - - this.node = node - this.convertTo = convertTo - } - - getNodeType(/*builder*/) { - return this.convertTo - } - - generate(builder) { - const convertTo = this.convertTo - const node = this.node - - if (builder.isReference(convertTo) === false) { - const convertToSnippet = builder.getType(convertTo) - const nodeSnippet = node.build(builder, convertTo) - - return `${builder.getVectorType(convertToSnippet)}( ${nodeSnippet} )` - } else { - return node.build(builder, convertTo) - } - } -} - -export default ConvertNode diff --git a/src/nodes/utils/JoinNode.js b/src/nodes/utils/JoinNode.js deleted file mode 100644 index 6df0e56e..00000000 --- a/src/nodes/utils/JoinNode.js +++ /dev/null @@ -1,34 +0,0 @@ -import Node from '../core/Node.js' - -class JoinNode extends Node { - constructor(nodes = []) { - super() - - this.nodes = nodes - } - - getNodeType(builder) { - return builder.getTypeFromLength( - this.nodes.reduce((count, cur) => count + builder.getTypeLength(cur.getNodeType(builder)), 0), - ) - } - - generate(builder) { - const type = this.getNodeType(builder) - const nodes = this.nodes - - const snippetValues = [] - - for (let i = 0; i < nodes.length; i++) { - const input = nodes[i] - - const inputSnippet = input.build(builder) - - snippetValues.push(inputSnippet) - } - - return `${builder.getType(type)}( ${snippetValues.join(', ')} )` - } -} - -export default JoinNode diff --git a/src/nodes/utils/MatcapUVNode.js b/src/nodes/utils/MatcapUVNode.js deleted file mode 100644 index 5e68da9c..00000000 --- a/src/nodes/utils/MatcapUVNode.js +++ /dev/null @@ -1,29 +0,0 @@ -import TempNode from '../core/TempNode.js' -import { - join, - negate, - normalize, - cross, - dot, - mul, - add, - transformedNormalView, - positionViewDirection, -} from '../ShaderNode.js' - -class MatcapUVNode extends TempNode { - constructor() { - super('vec2') - } - - generate(builder) { - const x = normalize(join(positionViewDirection.z, 0, negate(positionViewDirection.x))) - const y = cross(positionViewDirection, x) - - const uv = add(mul(join(dot(x, transformedNormalView), dot(y, transformedNormalView)), 0.495), 0.5) - - return uv.build(builder, this.getNodeType(builder)) - } -} - -export default MatcapUVNode diff --git a/src/nodes/utils/OscNode.js b/src/nodes/utils/OscNode.js deleted file mode 100644 index 632e2d8a..00000000 --- a/src/nodes/utils/OscNode.js +++ /dev/null @@ -1,54 +0,0 @@ -import Node from '../core/Node.js' -import TimerNode from './TimerNode.js' -import { abs, fract, round, sin, add, sub, mul } from '../ShaderNode.js' - -class OscNode extends Node { - static SINE = 'sine' - static SQUARE = 'square' - static TRIANGLE = 'triangle' - static SAWTOOTH = 'sawtooth' - - constructor(method = OscNode.SINE, timeNode = new TimerNode()) { - super() - - this.method = method - this.timeNode = timeNode - } - - getNodeType(builder) { - return this.timeNode.getNodeType(builder) - } - - generate(builder) { - const method = this.method - const timeNode = this.timeNode - - let outputNode = null - - if (method === OscNode.SINE) { - outputNode = add(mul(sin(mul(add(timeNode, 0.75), Math.PI * 2)), 0.5), 0.5) - } else if (method === OscNode.SQUARE) { - outputNode = round(fract(timeNode)) - } else if (method === OscNode.TRIANGLE) { - outputNode = abs(sub(1, mul(fract(add(timeNode, 0.5)), 2))) - } else if (method === OscNode.SAWTOOTH) { - outputNode = fract(timeNode) - } - - return outputNode.build(builder) - } - - serialize(data) { - super.serialize(data) - - data.method = this.method - } - - deserialize(data) { - super.deserialize(data) - - this.method = data.method - } -} - -export default OscNode diff --git a/src/nodes/utils/SplitNode.js b/src/nodes/utils/SplitNode.js deleted file mode 100644 index 438588d0..00000000 --- a/src/nodes/utils/SplitNode.js +++ /dev/null @@ -1,64 +0,0 @@ -import Node from '../core/Node.js' -import { vector } from '../core/NodeBuilder.js' - -class SplitNode extends Node { - constructor(node, components = 'x') { - super() - - this.node = node - this.components = components - } - - getVectorLength() { - let vectorLength = this.components.length - - for (const c of this.components) { - vectorLength = Math.max(vector.indexOf(c) + 1, vectorLength) - } - - return vectorLength - } - - getNodeType(builder) { - return builder.getTypeFromLength(this.components.length) - } - - generate(builder) { - const node = this.node - const nodeTypeLength = builder.getTypeLength(node.getNodeType(builder)) - - if (nodeTypeLength > 1) { - let type = null - - const componentsLength = this.getVectorLength() - - if (componentsLength >= nodeTypeLength) { - // need expand the input node - - type = builder.getTypeFromLength(this.getVectorLength()) - } - - const nodeSnippet = node.build(builder, type) - - return `${nodeSnippet}.${this.components}` - } else { - // ignore components if node is a float - - return node.build(builder) - } - } - - serialize(data) { - super.serialize(data) - - data.components = this.components - } - - deserialize(data) { - super.deserialize(data) - - this.components = data.components - } -} - -export default SplitNode diff --git a/src/nodes/utils/SpriteSheetUVNode.js b/src/nodes/utils/SpriteSheetUVNode.js deleted file mode 100644 index d963ba99..00000000 --- a/src/nodes/utils/SpriteSheetUVNode.js +++ /dev/null @@ -1,52 +0,0 @@ -import Node from '../core/Node.js' -import ConstNode from '../core/ConstNode.js' -import UVNode from '../accessors/UVNode.js' -import MathNode from '../math/MathNode.js' -import OperatorNode from '../math/OperatorNode.js' -import SplitNode from '../utils/SplitNode.js' -import JoinNode from '../utils/JoinNode.js' - -class SpriteSheetUVNode extends Node { - constructor(countNode, uvNode = new UVNode(), frameNode = new ConstNode(0)) { - super('vec2') - - this.countNode = countNode - this.uvNode = uvNode - this.frameNode = frameNode - } - - generate(builder) { - const count = this.countNode - const uv = this.uvNode - const frame = this.frameNode - - const one = new ConstNode(1) - - const width = new SplitNode(count, 'x') - const height = new SplitNode(count, 'y') - - const total = new OperatorNode('*', width, height) - - const roundFrame = new MathNode(MathNode.FLOOR, new MathNode(MathNode.MOD, frame, total)) - - const frameNum = new OperatorNode('+', roundFrame, one) - - const cell = new MathNode(MathNode.MOD, roundFrame, width) - const row = new MathNode(MathNode.CEIL, new OperatorNode('/', frameNum, width)) - const rowInv = new OperatorNode('-', height, row) - - const scale = new OperatorNode('/', one, count) - - const uvFrameOffset = new JoinNode([ - new OperatorNode('*', cell, new SplitNode(scale, 'x')), - new OperatorNode('*', rowInv, new SplitNode(scale, 'y')), - ]) - - const uvScale = new OperatorNode('*', uv, scale) - const uvFrame = new OperatorNode('+', uvScale, uvFrameOffset) - - return uvFrame.build(builder, this.getNodeType(builder)) - } -} - -export default SpriteSheetUVNode diff --git a/src/nodes/utils/TimerNode.js b/src/nodes/utils/TimerNode.js deleted file mode 100644 index da4c2937..00000000 --- a/src/nodes/utils/TimerNode.js +++ /dev/null @@ -1,48 +0,0 @@ -import UniformNode from '../core/UniformNode.js' -import { NodeUpdateType } from '../core/constants.js' - -class TimerNode extends UniformNode { - static LOCAL = 'local' - static GLOBAL = 'global' - static DELTA = 'delta' - - constructor(scope = TimerNode.LOCAL) { - super(0) - - this.scope = scope - this.scale = 1 - - this.updateType = NodeUpdateType.Frame - } - - update(frame) { - const scope = this.scope - const scale = this.scale - - if (scope === TimerNode.LOCAL) { - this.value += frame.deltaTime * scale - } else if (scope === TimerNode.DELTA) { - this.value = frame.deltaTime * scale - } else { - // global - - this.value = frame.time * scale - } - } - - serialize(data) { - super.serialize(data) - - data.scope = this.scope - data.scale = this.scale - } - - deserialize(data) { - super.deserialize(data) - - this.scope = data.scope - this.scale = data.scale - } -} - -export default TimerNode diff --git a/src/objects/Lensflare.js b/src/objects/Lensflare.js index da1362e2..cdef0141 100644 --- a/src/objects/Lensflare.js +++ b/src/objects/Lensflare.js @@ -17,10 +17,30 @@ import { Vector4, } from 'three' +const geometry = /* @__PURE__ */ new BufferGeometry() + +const float32Array = /* @__PURE__ */ new Float32Array([-1, -1, 0, 0, 0, 1, -1, 0, 1, 0, 1, 1, 0, 1, 1, -1, 1, 0, 0, 1]) + +const interleavedBuffer = /* @__PURE__ */ new InterleavedBuffer(float32Array, 5) + +/* @__PURE__ */ geometry.setIndex([0, 1, 2, 0, 2, 3]) +/* @__PURE__ */ geometry.setAttribute( + 'position', + /* @__PURE__ */ new InterleavedBufferAttribute(interleavedBuffer, 3, 0, false), +) +/* @__PURE__ */ geometry.setAttribute( + 'uv', + /* @__PURE__ */ new InterleavedBufferAttribute(interleavedBuffer, 2, 3, false), +) + class Lensflare extends Mesh { + static Geometry = geometry + constructor() { super(Lensflare.Geometry, new MeshBasicMaterial({ opacity: 0, transparent: true })) + this.isLensflare = true + this.type = 'Lensflare' this.frustumCulled = false this.renderOrder = Infinity @@ -259,29 +279,19 @@ class Lensflare extends Mesh { } } -Lensflare.prototype.isLensflare = true - // class LensflareElement { - constructor(texture, size = 1, distance = 0, color = new Color(0xffffff)) { - this.texture = texture - this.size = size - this.distance = distance - this.color = color - } -} + static Shader = { + uniforms: { + map: { value: null }, + occlusionMap: { value: null }, + color: { value: null }, + scale: { value: null }, + screenPosition: { value: null }, + }, -LensflareElement.Shader = { - uniforms: { - map: { value: null }, - occlusionMap: { value: null }, - color: { value: null }, - scale: { value: null }, - screenPosition: { value: null }, - }, - - vertexShader: /* glsl */ ` + vertexShader: /* glsl */ ` precision highp float; @@ -320,7 +330,7 @@ LensflareElement.Shader = { }`, - fragmentShader: /* glsl */ ` + fragmentShader: /* glsl */ ` precision highp float; @@ -338,20 +348,14 @@ LensflareElement.Shader = { gl_FragColor.rgb *= color; }`, -} - -Lensflare.Geometry = (function () { - const geometry = new BufferGeometry() - - const float32Array = new Float32Array([-1, -1, 0, 0, 0, 1, -1, 0, 1, 0, 1, 1, 0, 1, 1, -1, 1, 0, 0, 1]) - - const interleavedBuffer = new InterleavedBuffer(float32Array, 5) - - geometry.setIndex([0, 1, 2, 0, 2, 3]) - geometry.setAttribute('position', new InterleavedBufferAttribute(interleavedBuffer, 3, 0, false)) - geometry.setAttribute('uv', new InterleavedBufferAttribute(interleavedBuffer, 2, 3, false)) + } - return geometry -})() + constructor(texture, size = 1, distance = 0, color = new Color(0xffffff)) { + this.texture = texture + this.size = size + this.distance = distance + this.color = color + } +} export { Lensflare, LensflareElement } diff --git a/src/objects/LightningStorm.d.ts b/src/objects/LightningStorm.d.ts index 7777b81c..17e56e36 100644 --- a/src/objects/LightningStorm.d.ts +++ b/src/objects/LightningStorm.d.ts @@ -1,4 +1,4 @@ -import { Material, Vector3 } from 'three' +import { Material, Object3D, Vector3 } from 'three' import { LightningStrike, RayParameters } from '../geometries/LightningStrike' @@ -24,9 +24,9 @@ export interface StormParams { onLightningDown?: (lightning: LightningStrike) => void } -export class LightningStorm { +export class LightningStorm extends Object3D { constructor(stormParams?: StormParams) update(time: number): void - copy(source: LightningStorm): LightningStorm + copy(source: LightningStorm, recursive?: boolean): this clone(): this } diff --git a/src/objects/LightningStorm.js b/src/objects/LightningStorm.js index fd827c10..b1aa203b 100644 --- a/src/objects/LightningStorm.js +++ b/src/objects/LightningStorm.js @@ -1,5 +1,5 @@ import { MathUtils, Mesh, MeshBasicMaterial, Object3D } from 'three' -import { LightningStrike } from '../geometries/LightningStrike.js' +import { LightningStrike } from '../geometries/LightningStrike' /** * @fileoverview Lightning strike object generator @@ -51,6 +51,8 @@ class LightningStorm extends Object3D { constructor(stormParams = {}) { super() + this.isLightningStorm = true + // Parameters this.stormParams = stormParams @@ -190,8 +192,8 @@ class LightningStorm extends Object3D { ) } - copy(source) { - super.copy(source) + copy(source, recursive) { + super.copy(source, recursive) this.stormParams.size = source.stormParams.size this.stormParams.minHeight = source.stormParams.minHeight @@ -220,6 +222,4 @@ class LightningStorm extends Object3D { } } -LightningStorm.prototype.isLightningStorm = true - export { LightningStorm } diff --git a/src/objects/MarchingCubes.d.ts b/src/objects/MarchingCubes.d.ts index df01dfb0..ae83fd87 100644 --- a/src/objects/MarchingCubes.d.ts +++ b/src/objects/MarchingCubes.d.ts @@ -66,6 +66,7 @@ export class MarchingCubes extends Mesh { blur(intensity: number): void reset(): void + update(): void render(renderCallback: any): void generateGeometry(): BufferGeometry generateBufferGeometry(): BufferGeometry diff --git a/src/objects/MarchingCubes.js b/src/objects/MarchingCubes.js index 48bf32d9..d74f5312 100644 --- a/src/objects/MarchingCubes.js +++ b/src/objects/MarchingCubes.js @@ -1,4 +1,4 @@ -import { Mesh, Color, BufferGeometry, BufferAttribute, DynamicDrawUsage } from 'three' +import { BufferAttribute, BufferGeometry, Color, DynamicDrawUsage, Mesh, Sphere, Vector3 } from 'three' /** * Port of http://webglsamples.org/blob/blob.html @@ -7,40 +7,61 @@ import { Mesh, Color, BufferGeometry, BufferAttribute, DynamicDrawUsage } from ' class MarchingCubes extends Mesh { constructor(resolution, material, enableUvs = false, enableColors = false, maxPolyCount = 10000) { const geometry = new BufferGeometry() + super(geometry, material) - const scope = this // temp buffers used in polygonize + + this.isMarchingCubes = true + + const scope = this + + // temp buffers used in polygonize const vlist = new Float32Array(12 * 3) const nlist = new Float32Array(12 * 3) const clist = new Float32Array(12 * 3) + this.enableUvs = enableUvs - this.enableColors = enableColors // functions have to be object properties + this.enableColors = enableColors + + // functions have to be object properties // prototype functions kill performance // (tested and it was 4x slower !!!) this.init = function (resolution) { - this.resolution = resolution // parameters + this.resolution = resolution + + // parameters - this.isolation = 80.0 // size of field, 32 is pushing it in Javascript :) + this.isolation = 80.0 + + // size of field, 32 is pushing it in Javascript :) this.size = resolution this.size2 = this.size * this.size this.size3 = this.size2 * this.size - this.halfsize = this.size / 2.0 // deltas + this.halfsize = this.size / 2.0 + + // deltas this.delta = 2.0 / this.size this.yd = this.size this.zd = this.size2 + this.field = new Float32Array(this.size3) this.normal_cache = new Float32Array(this.size3 * 3) - this.palette = new Float32Array(this.size3 * 3) // + this.palette = new Float32Array(this.size3 * 3) + + // this.count = 0 + const maxVertexCount = maxPolyCount * 3 + this.positionArray = new Float32Array(maxVertexCount * 3) const positionAttribute = new BufferAttribute(this.positionArray, 3) positionAttribute.setUsage(DynamicDrawUsage) geometry.setAttribute('position', positionAttribute) + this.normalArray = new Float32Array(maxVertexCount * 3) const normalAttribute = new BufferAttribute(this.normalArray, 3) normalAttribute.setUsage(DynamicDrawUsage) @@ -59,7 +80,11 @@ class MarchingCubes extends Mesh { colorAttribute.setUsage(DynamicDrawUsage) geometry.setAttribute('color', colorAttribute) } - } /////////////////////// + + geometry.boundingSphere = new Sphere(new Vector3(), 1) + } + + /////////////////////// // Polygonization /////////////////////// @@ -70,12 +95,15 @@ class MarchingCubes extends Mesh { function VIntX(q, offset, isol, x, y, z, valp1, valp2, c_offset1, c_offset2) { const mu = (isol - valp1) / (valp2 - valp1), nc = scope.normal_cache + vlist[offset + 0] = x + mu * scope.delta vlist[offset + 1] = y vlist[offset + 2] = z + nlist[offset + 0] = lerp(nc[q + 0], nc[q + 3], mu) nlist[offset + 1] = lerp(nc[q + 1], nc[q + 4], mu) nlist[offset + 2] = lerp(nc[q + 2], nc[q + 5], mu) + clist[offset + 0] = lerp(scope.palette[c_offset1 * 3 + 0], scope.palette[c_offset2 * 3 + 0], mu) clist[offset + 1] = lerp(scope.palette[c_offset1 * 3 + 1], scope.palette[c_offset2 * 3 + 1], mu) clist[offset + 2] = lerp(scope.palette[c_offset1 * 3 + 2], scope.palette[c_offset2 * 3 + 2], mu) @@ -84,13 +112,17 @@ class MarchingCubes extends Mesh { function VIntY(q, offset, isol, x, y, z, valp1, valp2, c_offset1, c_offset2) { const mu = (isol - valp1) / (valp2 - valp1), nc = scope.normal_cache + vlist[offset + 0] = x vlist[offset + 1] = y + mu * scope.delta vlist[offset + 2] = z + const q2 = q + scope.yd * 3 + nlist[offset + 0] = lerp(nc[q + 0], nc[q2 + 0], mu) nlist[offset + 1] = lerp(nc[q + 1], nc[q2 + 1], mu) nlist[offset + 2] = lerp(nc[q + 2], nc[q2 + 2], mu) + clist[offset + 0] = lerp(scope.palette[c_offset1 * 3 + 0], scope.palette[c_offset2 * 3 + 0], mu) clist[offset + 1] = lerp(scope.palette[c_offset1 * 3 + 1], scope.palette[c_offset2 * 3 + 1], mu) clist[offset + 2] = lerp(scope.palette[c_offset1 * 3 + 2], scope.palette[c_offset2 * 3 + 2], mu) @@ -99,13 +131,17 @@ class MarchingCubes extends Mesh { function VIntZ(q, offset, isol, x, y, z, valp1, valp2, c_offset1, c_offset2) { const mu = (isol - valp1) / (valp2 - valp1), nc = scope.normal_cache + vlist[offset + 0] = x vlist[offset + 1] = y vlist[offset + 2] = z + mu * scope.delta + const q2 = q + scope.zd * 3 + nlist[offset + 0] = lerp(nc[q + 0], nc[q2 + 0], mu) nlist[offset + 1] = lerp(nc[q + 1], nc[q2 + 1], mu) nlist[offset + 2] = lerp(nc[q + 2], nc[q2 + 2], mu) + clist[offset + 0] = lerp(scope.palette[c_offset1 * 3 + 0], scope.palette[c_offset2 * 3 + 0], mu) clist[offset + 1] = lerp(scope.palette[c_offset1 * 3 + 1], scope.palette[c_offset2 * 3 + 1], mu) clist[offset + 2] = lerp(scope.palette[c_offset1 * 3 + 2], scope.palette[c_offset2 * 3 + 2], mu) @@ -119,7 +155,9 @@ class MarchingCubes extends Mesh { scope.normal_cache[q3 + 1] = scope.field[q - scope.yd] - scope.field[q + scope.yd] scope.normal_cache[q3 + 2] = scope.field[q - scope.zd] - scope.field[q + scope.zd] } - } // Returns total number of triangles. Fills triangles. + } + + // Returns total number of triangles. Fills triangles. // (this is where most of time is spent - it's inner work of O(n3) loop ) function polygonize(fx, fy, fz, q, isol) { @@ -131,6 +169,7 @@ class MarchingCubes extends Mesh { q1z = q1 + scope.zd, qyz = q + scope.yd + scope.zd, q1yz = q1 + scope.yd + scope.zd + let cubeindex = 0 const field0 = scope.field[q], field1 = scope.field[q1], @@ -140,6 +179,7 @@ class MarchingCubes extends Mesh { field5 = scope.field[q1z], field6 = scope.field[qyz], field7 = scope.field[q1yz] + if (field0 < isol) cubeindex |= 1 if (field1 < isol) cubeindex |= 2 if (field2 < isol) cubeindex |= 8 @@ -147,14 +187,19 @@ class MarchingCubes extends Mesh { if (field4 < isol) cubeindex |= 16 if (field5 < isol) cubeindex |= 32 if (field6 < isol) cubeindex |= 128 - if (field7 < isol) cubeindex |= 64 // if cube is entirely in/out of the surface - bail, nothing to draw + if (field7 < isol) cubeindex |= 64 + + // if cube is entirely in/out of the surface - bail, nothing to draw const bits = edgeTable[cubeindex] if (bits === 0) return 0 + const d = scope.delta, fx2 = fx + d, fy2 = fy + d, - fz2 = fz + d // top of the cube + fz2 = fz + d + + // top of the cube if (bits & 1) { compNorm(q) @@ -178,7 +223,9 @@ class MarchingCubes extends Mesh { compNorm(q) compNorm(qy) VIntY(q * 3, 9, isol, fx, fy, fz, field0, field2, q, qy) - } // bottom of the cube + } + + // bottom of the cube if (bits & 16) { compNorm(qz) @@ -202,8 +249,9 @@ class MarchingCubes extends Mesh { compNorm(qz) compNorm(qyz) VIntY(qz * 3, 21, isol, fx, fy, fz2, field4, field6, qz, qyz) - } // vertical lines of the cube + } + // vertical lines of the cube if (bits & 256) { compNorm(q) compNorm(qz) @@ -234,13 +282,17 @@ class MarchingCubes extends Mesh { o2, o3, numtris = 0, - i = 0 // here is where triangles are created + i = 0 + + // here is where triangles are created while (triTable[cubeindex + i] != -1) { o1 = cubeindex + i o2 = o1 + 1 o3 = o1 + 2 + posnormtriv(vlist, nlist, clist, 3 * triTable[o1], 3 * triTable[o2], 3 * triTable[o3]) + i += 3 numtris++ } @@ -249,28 +301,37 @@ class MarchingCubes extends Mesh { } function posnormtriv(pos, norm, colors, o1, o2, o3) { - const c = scope.count * 3 // positions + const c = scope.count * 3 + + // positions scope.positionArray[c + 0] = pos[o1] scope.positionArray[c + 1] = pos[o1 + 1] scope.positionArray[c + 2] = pos[o1 + 2] + scope.positionArray[c + 3] = pos[o2] scope.positionArray[c + 4] = pos[o2 + 1] scope.positionArray[c + 5] = pos[o2 + 2] + scope.positionArray[c + 6] = pos[o3] scope.positionArray[c + 7] = pos[o3 + 1] - scope.positionArray[c + 8] = pos[o3 + 2] // normals + scope.positionArray[c + 8] = pos[o3 + 2] + + // normals if (scope.material.flatShading === true) { const nx = (norm[o1 + 0] + norm[o2 + 0] + norm[o3 + 0]) / 3 const ny = (norm[o1 + 1] + norm[o2 + 1] + norm[o3 + 1]) / 3 const nz = (norm[o1 + 2] + norm[o2 + 2] + norm[o3 + 2]) / 3 + scope.normalArray[c + 0] = nx scope.normalArray[c + 1] = ny scope.normalArray[c + 2] = nz + scope.normalArray[c + 3] = nx scope.normalArray[c + 4] = ny scope.normalArray[c + 5] = nz + scope.normalArray[c + 6] = nx scope.normalArray[c + 7] = ny scope.normalArray[c + 8] = nz @@ -278,40 +339,54 @@ class MarchingCubes extends Mesh { scope.normalArray[c + 0] = norm[o1 + 0] scope.normalArray[c + 1] = norm[o1 + 1] scope.normalArray[c + 2] = norm[o1 + 2] + scope.normalArray[c + 3] = norm[o2 + 0] scope.normalArray[c + 4] = norm[o2 + 1] scope.normalArray[c + 5] = norm[o2 + 2] + scope.normalArray[c + 6] = norm[o3 + 0] scope.normalArray[c + 7] = norm[o3 + 1] scope.normalArray[c + 8] = norm[o3 + 2] - } // uvs + } + + // uvs if (scope.enableUvs) { const d = scope.count * 2 + scope.uvArray[d + 0] = pos[o1 + 0] scope.uvArray[d + 1] = pos[o1 + 2] + scope.uvArray[d + 2] = pos[o2 + 0] scope.uvArray[d + 3] = pos[o2 + 2] + scope.uvArray[d + 4] = pos[o3 + 0] scope.uvArray[d + 5] = pos[o3 + 2] - } // colors + } + + // colors if (scope.enableColors) { scope.colorArray[c + 0] = colors[o1 + 0] scope.colorArray[c + 1] = colors[o1 + 1] scope.colorArray[c + 2] = colors[o1 + 2] + scope.colorArray[c + 3] = colors[o2 + 0] scope.colorArray[c + 4] = colors[o2 + 1] scope.colorArray[c + 5] = colors[o2 + 2] + scope.colorArray[c + 6] = colors[o3 + 0] scope.colorArray[c + 7] = colors[o3 + 1] scope.colorArray[c + 8] = colors[o3 + 2] } scope.count += 3 - } ///////////////////////////////////// + } + + ///////////////////////////////////// // Metaballs ///////////////////////////////////// + // Adds a reciprocal ball (nice and blobby) that, to be fast, fades to zero after // a fixed distance, determined by strength and subtract. @@ -336,7 +411,9 @@ class MarchingCubes extends Mesh { } catch (err) { ballColor = new Color(ballx, bally, ballz) } - } // Let's solve the equation to find the radius: + } + + // Let's solve the equation to find the radius: // 1.0 / (0.000001 + radius^2) * strength - subtract = 0 // strength / (radius^2) = subtract // strength = subtract * radius^2 @@ -347,6 +424,7 @@ class MarchingCubes extends Mesh { zs = ballz * this.size, ys = bally * this.size, xs = ballx * this.size + let min_z = Math.floor(zs - radius) if (min_z < 1) min_z = 1 let max_z = Math.floor(zs + radius) @@ -358,7 +436,9 @@ class MarchingCubes extends Mesh { let min_x = Math.floor(xs - radius) if (min_x < 1) min_x = 1 let max_x = Math.floor(xs + radius) - if (max_x > this.size - 1) max_x = this.size - 1 // Don't polygonize in the outer layer because normals aren't + if (max_x > this.size - 1) max_x = this.size - 1 + + // Don't polygonize in the outer layer because normals aren't // well-defined there. let x, y, z, y_offset, z_offset, fx, fy, fz, fz2, fy2, val @@ -376,11 +456,11 @@ class MarchingCubes extends Mesh { for (x = min_x; x < max_x; x++) { fx = x / this.size - ballx val = strength / (0.000001 + fx * fx + fy2 + fz2) - subtract - if (val > 0.0) { - this.field[y_offset + x] += val * sign // optimization - // http://www.geisswerks.com/ryan/BLOBS/blobs.html + this.field[y_offset + x] += val * sign + // optimization + // http://www.geisswerks.com/ryan/BLOBS/blobs.html const ratio = Math.sqrt((x - xs) * (x - xs) + (y - ys) * (y - ys) + (z - zs) * (z - zs)) / radius const contrib = 1 - ratio * ratio * ratio * (ratio * (ratio * 6 - 15) + 10) this.palette[(y_offset + x) * 3 + 0] += ballColor.r * contrib @@ -398,6 +478,7 @@ class MarchingCubes extends Mesh { yd = this.yd, zd = this.zd, field = this.field + let x, y, z, @@ -406,6 +487,7 @@ class MarchingCubes extends Mesh { xdiv, cxy, dist = size * Math.sqrt(strength / subtract) + if (dist > size) dist = size for (x = 0; x < dist; x++) { @@ -431,6 +513,7 @@ class MarchingCubes extends Mesh { yd = this.yd, zd = this.zd, field = this.field + let x, y, z, @@ -440,6 +523,7 @@ class MarchingCubes extends Mesh { cy, cxy, dist = size * Math.sqrt(strength / subtract) + if (dist > size) dist = size for (y = 0; y < dist; y++) { @@ -461,10 +545,12 @@ class MarchingCubes extends Mesh { this.addPlaneZ = function (strength, subtract) { // cache attribute lookups + const size = this.size, yd = this.yd, zd = this.zd, field = this.field + let x, y, z, @@ -474,13 +560,13 @@ class MarchingCubes extends Mesh { cz, cyz, dist = size * Math.sqrt(strength / subtract) + if (dist > size) dist = size for (z = 0; z < dist; z++) { zdiv = z / size zz = zdiv * zdiv val = strength / (0.0001 + zz) - subtract - if (val > 0.0) { cz = zd * z @@ -491,7 +577,9 @@ class MarchingCubes extends Mesh { } } } - } ///////////////////////////////////// + } + + ///////////////////////////////////// // Updates ///////////////////////////////////// @@ -510,7 +598,6 @@ class MarchingCubes extends Mesh { const fieldCopy = field.slice() const size = this.size const size2 = this.size2 - for (let x = 0; x < size; x++) { for (let y = 0; y < size; y++) { for (let z = 0; z < size; z++) { @@ -529,8 +616,10 @@ class MarchingCubes extends Mesh { for (let z2 = -1; z2 <= 1; z2 += 2) { const z3 = z2 + z if (z3 < 0 || z3 >= size) continue + const index2 = size2 * z3 + size * y3 + x3 const val2 = fieldCopy[index2] + count++ val += (intensity * (val2 - val)) / count } @@ -545,6 +634,7 @@ class MarchingCubes extends Mesh { this.reset = function () { // wipe the normal cache + for (let i = 0; i < this.size3; i++) { this.normal_cache[i * 3] = 0.0 this.field[i] = 0.0 @@ -552,8 +642,10 @@ class MarchingCubes extends Mesh { } } - this.onBeforeRender = function () { - this.count = 0 // Triangulate. Yeah, this is slow. + this.update = function () { + this.count = 0 + + // Triangulate. Yeah, this is slow. const smin2 = this.size - 2 @@ -567,43 +659,337 @@ class MarchingCubes extends Mesh { for (let x = 1; x < smin2; x++) { const fx = (x - this.halfsize) / this.halfsize //+ 1 - const q = y_offset + x + polygonize(fx, fy, fz, q, this.isolation) } } - } // reset unneeded data + } + + // set the draw range to only the processed triangles + + this.geometry.setDrawRange(0, this.count) - for (let i = this.count * 3; i < this.positionArray.length; i++) { - this.positionArray[i] = 0.0 - } // update geometry data + // update geometry data geometry.getAttribute('position').needsUpdate = true geometry.getAttribute('normal').needsUpdate = true + if (this.enableUvs) geometry.getAttribute('uv').needsUpdate = true - if (this.enableColors) geometry.getAttribute('color').needsUpdate = true // safety check + if (this.enableColors) geometry.getAttribute('color').needsUpdate = true + + // safety check - if (this.count / 3 > maxPolyCount) { + if (this.count / 3 > maxPolyCount) console.warn( - 'MarchingCubes: Geometry buffers too small for rendering. Please create an instance with a higher poly count.', + 'THREE.MarchingCubes: Geometry buffers too small for rendering. Please create an instance with a higher poly count.', ) - } } this.init(resolution) } } -MarchingCubes.prototype.isMarchingCubes = true ///////////////////////////////////// +///////////////////////////////////// // Marching cubes lookup tables ///////////////////////////////////// + // These tables are straight from Paul Bourke's page: // http://paulbourke.net/geometry/polygonise/ // who in turn got them from Cory Gene Bloyd. // prettier-ignore -const edgeTable = new Int32Array( [ 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, 0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c, 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, 0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac, 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, 0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c, 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc, 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c, 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, 0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, 0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, 0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, 0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460, 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0, 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230, 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190, 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ] ); +const edgeTable = new Int32Array( [ + 0x0, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c, + 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00, + 0x190, 0x99, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c, + 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90, + 0x230, 0x339, 0x33, 0x13a, 0x636, 0x73f, 0x435, 0x53c, + 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30, + 0x3a0, 0x2a9, 0x1a3, 0xaa, 0x7a6, 0x6af, 0x5a5, 0x4ac, + 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0, + 0x460, 0x569, 0x663, 0x76a, 0x66, 0x16f, 0x265, 0x36c, + 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60, + 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff, 0x3f5, 0x2fc, + 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0, + 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55, 0x15c, + 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950, + 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc, + 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0, + 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc, + 0xcc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0, + 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c, + 0x15c, 0x55, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650, + 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc, + 0x2fc, 0x3f5, 0xff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0, + 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c, + 0x36c, 0x265, 0x16f, 0x66, 0x76a, 0x663, 0x569, 0x460, + 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac, + 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa, 0x1a3, 0x2a9, 0x3a0, + 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c, + 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33, 0x339, 0x230, + 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c, + 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99, 0x190, + 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c, + 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ] ); + // prettier-ignore -const triTable = new Int32Array( [ - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 1, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 8, 3, 9, 8, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 3, 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 2, 10, 0, 2, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 8, 3, 2, 10, 8, 10, 9, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 11, 2, 8, 11, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 9, 0, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 11, 2, 1, 9, 11, 9, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 10, 1, 11, 10, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 10, 1, 0, 8, 10, 8, 11, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 9, 0, 3, 11, 9, 11, 10, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 8, 10, 10, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 3, 0, 7, 3, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 1, 9, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 1, 9, 4, 7, 1, 7, 3, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 10, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 4, 7, 3, 0, 4, 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 2, 10, 9, 0, 2, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, - 1, - 1, - 1, - 1, 8, 4, 7, 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 11, 4, 7, 11, 2, 4, 2, 0, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 0, 1, 8, 4, 7, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, - 1, - 1, - 1, - 1, 3, 10, 1, 3, 11, 10, 7, 8, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, - 1, - 1, - 1, - 1, 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, - 1, - 1, - 1, - 1, 4, 7, 11, 4, 11, 9, 9, 11, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 5, 4, 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 5, 4, 1, 5, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 5, 4, 8, 3, 5, 3, 1, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 10, 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 0, 8, 1, 2, 10, 4, 9, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 2, 10, 5, 4, 2, 4, 0, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, - 1, - 1, - 1, - 1, 9, 5, 4, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 11, 2, 0, 8, 11, 4, 9, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 5, 4, 0, 1, 5, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, - 1, - 1, - 1, - 1, 10, 3, 11, 10, 1, 3, 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, - 1, - 1, - 1, - 1, 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, - 1, - 1, - 1, - 1, 5, 4, 8, 5, 8, 10, 10, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 7, 8, 5, 7, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 3, 0, 9, 5, 3, 5, 7, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 7, 8, 0, 1, 7, 1, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 5, 3, 3, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 7, 8, 9, 5, 7, 10, 1, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, - 1, - 1, - 1, - 1, 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, - 1, - 1, - 1, - 1, 2, 10, 5, 2, 5, 3, 3, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 7, 9, 5, 7, 8, 9, 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, - 1, - 1, - 1, - 1, 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, - 1, - 1, - 1, - 1, 11, 2, 1, 11, 1, 7, 7, 1, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, - 1, - 1, - 1, - 1, 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, - 1, 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, - 1, 11, 10, 5, 7, 11, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 3, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 0, 1, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 8, 3, 1, 9, 8, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 6, 5, 2, 6, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 6, 5, 1, 2, 6, 3, 0, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 6, 5, 9, 0, 6, 0, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, - 1, - 1, - 1, - 1, 2, 3, 11, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 11, 0, 8, 11, 2, 0, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 1, 9, 2, 3, 11, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, - 1, - 1, - 1, - 1, 6, 3, 11, 6, 5, 3, 5, 1, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, - 1, - 1, - 1, - 1, 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, - 1, - 1, - 1, - 1, 6, 5, 9, 6, 9, 11, 11, 9, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 10, 6, 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 3, 0, 4, 7, 3, 6, 5, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 9, 0, 5, 10, 6, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, - 1, - 1, - 1, - 1, 6, 1, 2, 6, 5, 1, 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, - 1, - 1, - 1, - 1, 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, - 1, - 1, - 1, - 1, 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, - 1, 3, 11, 2, 7, 8, 4, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, - 1, - 1, - 1, - 1, 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, - 1, - 1, - 1, - 1, 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, - 1, 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, - 1, - 1, - 1, - 1, 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, - 1, 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, - 1, 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, - 1, - 1, - 1, - 1, 10, 4, 9, 6, 4, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 10, 6, 4, 9, 10, 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 0, 1, 10, 6, 0, 6, 4, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, - 1, - 1, - 1, - 1, 1, 4, 9, 1, 2, 4, 2, 6, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, - 1, - 1, - 1, - 1, 0, 2, 4, 4, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 3, 2, 8, 2, 4, 4, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 4, 9, 10, 6, 4, 11, 2, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, - 1, - 1, - 1, - 1, 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, - 1, - 1, - 1, - 1, 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, - 1, 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, - 1, - 1, - 1, - 1, 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, - 1, 3, 11, 6, 3, 6, 0, 0, 6, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 6, 4, 8, 11, 6, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 7, 10, 6, 7, 8, 10, 8, 9, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, - 1, - 1, - 1, - 1, 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, - 1, - 1, - 1, - 1, 10, 6, 7, 10, 7, 1, 1, 7, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, - 1, - 1, - 1, - 1, 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, - 1, 7, 8, 0, 7, 0, 6, 6, 0, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 7, 3, 2, 6, 7, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, - 1, - 1, - 1, - 1, 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, - 1, 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, - 1, 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, - 1, - 1, - 1, - 1, 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, - 1, 0, 9, 1, 11, 6, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, - 1, - 1, - 1, - 1, 7, 11, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 0, 8, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 1, 9, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 1, 9, 8, 3, 1, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 1, 2, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 10, 3, 0, 8, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 9, 0, 2, 10, 9, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, - 1, - 1, - 1, - 1, 7, 2, 3, 6, 2, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 7, 0, 8, 7, 6, 0, 6, 2, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 7, 6, 2, 3, 7, 0, 1, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, - 1, - 1, - 1, - 1, 10, 7, 6, 10, 1, 7, 1, 3, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, - 1, - 1, - 1, - 1, 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, - 1, - 1, - 1, - 1, 7, 6, 10, 7, 10, 8, 8, 10, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 6, 8, 4, 11, 8, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 6, 11, 3, 0, 6, 0, 4, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 6, 11, 8, 4, 6, 9, 0, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, - 1, - 1, - 1, - 1, 6, 8, 4, 6, 11, 8, 2, 10, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, - 1, - 1, - 1, - 1, 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, - 1, - 1, - 1, - 1, 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, - 1, 8, 2, 3, 8, 4, 2, 4, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 4, 2, 4, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, - 1, - 1, - 1, - 1, 1, 9, 4, 1, 4, 2, 2, 4, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, - 1, - 1, - 1, - 1, 10, 1, 0, 10, 0, 6, 6, 0, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, - 1, 10, 9, 4, 6, 10, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 9, 5, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 3, 4, 9, 5, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 0, 1, 5, 4, 0, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, - 1, - 1, - 1, - 1, 9, 5, 4, 10, 1, 2, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, - 1, - 1, - 1, - 1, 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, - 1, - 1, - 1, - 1, 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, - 1, 7, 2, 3, 7, 6, 2, 5, 4, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, - 1, - 1, - 1, - 1, 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, - 1, - 1, - 1, - 1, 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, - 1, 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, - 1, - 1, - 1, - 1, 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, - 1, 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, - 1, 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, - 1, - 1, - 1, - 1, 6, 9, 5, 6, 11, 9, 11, 8, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, - 1, - 1, - 1, - 1, 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, - 1, - 1, - 1, - 1, 6, 11, 3, 6, 3, 5, 5, 3, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, - 1, - 1, - 1, - 1, 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, - 1, 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, - 1, 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, - 1, - 1, - 1, - 1, 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, - 1, - 1, - 1, - 1, 9, 5, 6, 9, 6, 0, 0, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, - 1, 1, 5, 6, 2, 1, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, - 1, 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, - 1, - 1, - 1, - 1, 0, 3, 8, 5, 6, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 5, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 11, 5, 10, 7, 5, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 11, 5, 10, 11, 7, 5, 8, 3, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 11, 7, 5, 10, 11, 1, 9, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, - 1, - 1, - 1, - 1, 11, 1, 2, 11, 7, 1, 7, 5, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, - 1, - 1, - 1, - 1, 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, - 1, - 1, - 1, - 1, 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, - 1, 2, 5, 10, 2, 3, 5, 3, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, - 1, - 1, - 1, - 1, 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, - 1, - 1, - 1, - 1, 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, - 1, 1, 3, 5, 3, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 7, 0, 7, 1, 1, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 0, 3, 9, 3, 5, 5, 3, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 8, 7, 5, 9, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 8, 4, 5, 10, 8, 10, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, - 1, - 1, - 1, - 1, 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, - 1, - 1, - 1, - 1, 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, - 1, 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, - 1, - 1, - 1, - 1, 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, - 1, 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, - 1, 9, 4, 5, 2, 11, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, - 1, - 1, - 1, - 1, 5, 10, 2, 5, 2, 4, 4, 2, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, - 1, 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, - 1, - 1, - 1, - 1, 8, 4, 5, 8, 5, 3, 3, 5, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 4, 5, 1, 0, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, - 1, - 1, - 1, - 1, 9, 4, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 11, 7, 4, 9, 11, 9, 10, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, - 1, - 1, - 1, - 1, 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, - 1, - 1, - 1, - 1, 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, - 1, 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, - 1, - 1, - 1, - 1, 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, - 1, 11, 7, 4, 11, 4, 2, 2, 4, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, - 1, - 1, - 1, - 1, 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, - 1, - 1, - 1, - 1, 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, - 1, 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, - 1, 1, 10, 2, 8, 7, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 9, 1, 4, 1, 7, 7, 1, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, - 1, - 1, - 1, - 1, 4, 0, 3, 7, 4, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 4, 8, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 10, 8, 10, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 0, 9, 3, 9, 11, 11, 9, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 1, 10, 0, 10, 8, 8, 10, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 1, 10, 11, 3, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 2, 11, 1, 11, 9, 9, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, - 1, - 1, - 1, - 1, 0, 2, 11, 8, 0, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 3, 2, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 3, 8, 2, 8, 10, 10, 8, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 9, 10, 2, 0, 9, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, - 1, - 1, - 1, - 1, 1, 10, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 1, 3, 8, 9, 1, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 9, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, 0, 3, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 ] ); +const triTable = new Int32Array( [ + - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 1, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 8, 3, 9, 8, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 3, 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 2, 10, 0, 2, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 8, 3, 2, 10, 8, 10, 9, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 11, 2, 8, 11, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 9, 0, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 11, 2, 1, 9, 11, 9, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 10, 1, 11, 10, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 10, 1, 0, 8, 10, 8, 11, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 9, 0, 3, 11, 9, 11, 10, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 8, 10, 10, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 3, 0, 7, 3, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 1, 9, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 1, 9, 4, 7, 1, 7, 3, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 10, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 4, 7, 3, 0, 4, 1, 2, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 2, 10, 9, 0, 2, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4, - 1, - 1, - 1, - 1, + 8, 4, 7, 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 11, 4, 7, 11, 2, 4, 2, 0, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 0, 1, 8, 4, 7, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 7, 11, 9, 4, 11, 9, 11, 2, 9, 2, 1, - 1, - 1, - 1, - 1, + 3, 10, 1, 3, 11, 10, 7, 8, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 11, 10, 1, 4, 11, 1, 0, 4, 7, 11, 4, - 1, - 1, - 1, - 1, + 4, 7, 8, 9, 0, 11, 9, 11, 10, 11, 0, 3, - 1, - 1, - 1, - 1, + 4, 7, 11, 4, 11, 9, 9, 11, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 5, 4, 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 5, 4, 1, 5, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 5, 4, 8, 3, 5, 3, 1, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 10, 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 0, 8, 1, 2, 10, 4, 9, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 2, 10, 5, 4, 2, 4, 0, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8, - 1, - 1, - 1, - 1, + 9, 5, 4, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 11, 2, 0, 8, 11, 4, 9, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 5, 4, 0, 1, 5, 2, 3, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 1, 5, 2, 5, 8, 2, 8, 11, 4, 8, 5, - 1, - 1, - 1, - 1, + 10, 3, 11, 10, 1, 3, 9, 5, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 9, 5, 0, 8, 1, 8, 10, 1, 8, 11, 10, - 1, - 1, - 1, - 1, + 5, 4, 0, 5, 0, 11, 5, 11, 10, 11, 0, 3, - 1, - 1, - 1, - 1, + 5, 4, 8, 5, 8, 10, 10, 8, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 7, 8, 5, 7, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 3, 0, 9, 5, 3, 5, 7, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 7, 8, 0, 1, 7, 1, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 5, 3, 3, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 7, 8, 9, 5, 7, 10, 1, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3, - 1, - 1, - 1, - 1, + 8, 0, 2, 8, 2, 5, 8, 5, 7, 10, 5, 2, - 1, - 1, - 1, - 1, + 2, 10, 5, 2, 5, 3, 3, 5, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 7, 9, 5, 7, 8, 9, 3, 11, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7, 11, - 1, - 1, - 1, - 1, + 2, 3, 11, 0, 1, 8, 1, 7, 8, 1, 5, 7, - 1, - 1, - 1, - 1, + 11, 2, 1, 11, 1, 7, 7, 1, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 5, 8, 8, 5, 7, 10, 1, 3, 10, 3, 11, - 1, - 1, - 1, - 1, + 5, 7, 0, 5, 0, 9, 7, 11, 0, 1, 0, 10, 11, 10, 0, - 1, + 11, 10, 0, 11, 0, 3, 10, 5, 0, 8, 0, 7, 5, 7, 0, - 1, + 11, 10, 5, 7, 11, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 3, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 0, 1, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 8, 3, 1, 9, 8, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 6, 5, 2, 6, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 6, 5, 1, 2, 6, 3, 0, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 6, 5, 9, 0, 6, 0, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8, - 1, - 1, - 1, - 1, + 2, 3, 11, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 11, 0, 8, 11, 2, 0, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 1, 9, 2, 3, 11, 5, 10, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 10, 6, 1, 9, 2, 9, 11, 2, 9, 8, 11, - 1, - 1, - 1, - 1, + 6, 3, 11, 6, 5, 3, 5, 1, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 11, 0, 11, 5, 0, 5, 1, 5, 11, 6, - 1, - 1, - 1, - 1, + 3, 11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9, - 1, - 1, - 1, - 1, + 6, 5, 9, 6, 9, 11, 11, 9, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 10, 6, 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 3, 0, 4, 7, 3, 6, 5, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 9, 0, 5, 10, 6, 8, 4, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4, - 1, - 1, - 1, - 1, + 6, 1, 2, 6, 5, 1, 4, 7, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7, - 1, - 1, - 1, - 1, + 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6, - 1, - 1, - 1, - 1, + 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9, - 1, + 3, 11, 2, 7, 8, 4, 10, 6, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 10, 6, 4, 7, 2, 4, 2, 0, 2, 7, 11, - 1, - 1, - 1, - 1, + 0, 1, 9, 4, 7, 8, 2, 3, 11, 5, 10, 6, - 1, - 1, - 1, - 1, + 9, 2, 1, 9, 11, 2, 9, 4, 11, 7, 11, 4, 5, 10, 6, - 1, + 8, 4, 7, 3, 11, 5, 3, 5, 1, 5, 11, 6, - 1, - 1, - 1, - 1, + 5, 1, 11, 5, 11, 6, 1, 0, 11, 7, 11, 4, 0, 4, 11, - 1, + 0, 5, 9, 0, 6, 5, 0, 3, 6, 11, 6, 3, 8, 4, 7, - 1, + 6, 5, 9, 6, 9, 11, 4, 7, 9, 7, 11, 9, - 1, - 1, - 1, - 1, + 10, 4, 9, 6, 4, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 10, 6, 4, 9, 10, 0, 8, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 0, 1, 10, 6, 0, 6, 4, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1, 10, - 1, - 1, - 1, - 1, + 1, 4, 9, 1, 2, 4, 2, 6, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4, - 1, - 1, - 1, - 1, + 0, 2, 4, 4, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 3, 2, 8, 2, 4, 4, 2, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 4, 9, 10, 6, 4, 11, 2, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 2, 2, 8, 11, 4, 9, 10, 4, 10, 6, - 1, - 1, - 1, - 1, + 3, 11, 2, 0, 1, 6, 0, 6, 4, 6, 1, 10, - 1, - 1, - 1, - 1, + 6, 4, 1, 6, 1, 10, 4, 8, 1, 2, 1, 11, 8, 11, 1, - 1, + 9, 6, 4, 9, 3, 6, 9, 1, 3, 11, 6, 3, - 1, - 1, - 1, - 1, + 8, 11, 1, 8, 1, 0, 11, 6, 1, 9, 1, 4, 6, 4, 1, - 1, + 3, 11, 6, 3, 6, 0, 0, 6, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 6, 4, 8, 11, 6, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 7, 10, 6, 7, 8, 10, 8, 9, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 7, 3, 0, 10, 7, 0, 9, 10, 6, 7, 10, - 1, - 1, - 1, - 1, + 10, 6, 7, 1, 10, 7, 1, 7, 8, 1, 8, 0, - 1, - 1, - 1, - 1, + 10, 6, 7, 10, 7, 1, 1, 7, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7, - 1, - 1, - 1, - 1, + 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9, - 1, + 7, 8, 0, 7, 0, 6, 6, 0, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 7, 3, 2, 6, 7, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 3, 11, 10, 6, 8, 10, 8, 9, 8, 6, 7, - 1, - 1, - 1, - 1, + 2, 0, 7, 2, 7, 11, 0, 9, 7, 6, 7, 10, 9, 10, 7, - 1, + 1, 8, 0, 1, 7, 8, 1, 10, 7, 6, 7, 10, 2, 3, 11, - 1, + 11, 2, 1, 11, 1, 7, 10, 6, 1, 6, 7, 1, - 1, - 1, - 1, - 1, + 8, 9, 6, 8, 6, 7, 9, 1, 6, 11, 6, 3, 1, 3, 6, - 1, + 0, 9, 1, 11, 6, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 7, 8, 0, 7, 0, 6, 3, 11, 0, 11, 6, 0, - 1, - 1, - 1, - 1, + 7, 11, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 0, 8, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 1, 9, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 1, 9, 8, 3, 1, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 1, 2, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 10, 3, 0, 8, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 9, 0, 2, 10, 9, 6, 11, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 6, 11, 7, 2, 10, 3, 10, 8, 3, 10, 9, 8, - 1, - 1, - 1, - 1, + 7, 2, 3, 6, 2, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 7, 0, 8, 7, 6, 0, 6, 2, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 7, 6, 2, 3, 7, 0, 1, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6, - 1, - 1, - 1, - 1, + 10, 7, 6, 10, 1, 7, 1, 3, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 7, 6, 1, 7, 10, 1, 8, 7, 1, 0, 8, - 1, - 1, - 1, - 1, + 0, 3, 7, 0, 7, 10, 0, 10, 9, 6, 10, 7, - 1, - 1, - 1, - 1, + 7, 6, 10, 7, 10, 8, 8, 10, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 6, 8, 4, 11, 8, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 6, 11, 3, 0, 6, 0, 4, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 6, 11, 8, 4, 6, 9, 0, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 4, 6, 9, 6, 3, 9, 3, 1, 11, 3, 6, - 1, - 1, - 1, - 1, + 6, 8, 4, 6, 11, 8, 2, 10, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 10, 3, 0, 11, 0, 6, 11, 0, 4, 6, - 1, - 1, - 1, - 1, + 4, 11, 8, 4, 6, 11, 0, 2, 9, 2, 10, 9, - 1, - 1, - 1, - 1, + 10, 9, 3, 10, 3, 2, 9, 4, 3, 11, 3, 6, 4, 6, 3, - 1, + 8, 2, 3, 8, 4, 2, 4, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 4, 2, 4, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8, - 1, - 1, - 1, - 1, + 1, 9, 4, 1, 4, 2, 2, 4, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 1, 3, 8, 6, 1, 8, 4, 6, 6, 10, 1, - 1, - 1, - 1, - 1, + 10, 1, 0, 10, 0, 6, 6, 0, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 6, 3, 4, 3, 8, 6, 10, 3, 0, 3, 9, 10, 9, 3, - 1, + 10, 9, 4, 6, 10, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 9, 5, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 3, 4, 9, 5, 11, 7, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 0, 1, 5, 4, 0, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5, - 1, - 1, - 1, - 1, + 9, 5, 4, 10, 1, 2, 7, 6, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 6, 11, 7, 1, 2, 10, 0, 8, 3, 4, 9, 5, - 1, - 1, - 1, - 1, + 7, 6, 11, 5, 4, 10, 4, 2, 10, 4, 0, 2, - 1, - 1, - 1, - 1, + 3, 4, 8, 3, 5, 4, 3, 2, 5, 10, 5, 2, 11, 7, 6, - 1, + 7, 2, 3, 7, 6, 2, 5, 4, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7, - 1, - 1, - 1, - 1, + 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0, - 1, - 1, - 1, - 1, + 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8, - 1, + 9, 5, 4, 10, 1, 6, 1, 7, 6, 1, 3, 7, - 1, - 1, - 1, - 1, + 1, 6, 10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4, - 1, + 4, 0, 10, 4, 10, 5, 0, 3, 10, 6, 10, 7, 3, 7, 10, - 1, + 7, 6, 10, 7, 10, 8, 5, 4, 10, 4, 8, 10, - 1, - 1, - 1, - 1, + 6, 9, 5, 6, 11, 9, 11, 8, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 6, 11, 0, 6, 3, 0, 5, 6, 0, 9, 5, - 1, - 1, - 1, - 1, + 0, 11, 8, 0, 5, 11, 0, 1, 5, 5, 6, 11, - 1, - 1, - 1, - 1, + 6, 11, 3, 6, 3, 5, 5, 3, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 10, 9, 5, 11, 9, 11, 8, 11, 5, 6, - 1, - 1, - 1, - 1, + 0, 11, 3, 0, 6, 11, 0, 9, 6, 5, 6, 9, 1, 2, 10, - 1, + 11, 8, 5, 11, 5, 6, 8, 0, 5, 10, 5, 2, 0, 2, 5, - 1, + 6, 11, 3, 6, 3, 5, 2, 10, 3, 10, 5, 3, - 1, - 1, - 1, - 1, + 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2, - 1, - 1, - 1, - 1, + 9, 5, 6, 9, 6, 0, 0, 6, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8, - 1, + 1, 5, 6, 2, 1, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 3, 6, 1, 6, 10, 3, 8, 6, 5, 6, 9, 8, 9, 6, - 1, + 10, 1, 0, 10, 0, 6, 9, 5, 0, 5, 6, 0, - 1, - 1, - 1, - 1, + 0, 3, 8, 5, 6, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 5, 6, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 11, 5, 10, 7, 5, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 11, 5, 10, 11, 7, 5, 8, 3, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 11, 7, 5, 10, 11, 1, 9, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 10, 7, 5, 10, 11, 7, 9, 8, 1, 8, 3, 1, - 1, - 1, - 1, - 1, + 11, 1, 2, 11, 7, 1, 7, 5, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2, 11, - 1, - 1, - 1, - 1, + 9, 7, 5, 9, 2, 7, 9, 0, 2, 2, 11, 7, - 1, - 1, - 1, - 1, + 7, 5, 2, 7, 2, 11, 5, 9, 2, 3, 2, 8, 9, 8, 2, - 1, + 2, 5, 10, 2, 3, 5, 3, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 2, 0, 8, 5, 2, 8, 7, 5, 10, 2, 5, - 1, - 1, - 1, - 1, + 9, 0, 1, 5, 10, 3, 5, 3, 7, 3, 10, 2, - 1, - 1, - 1, - 1, + 9, 8, 2, 9, 2, 1, 8, 7, 2, 10, 2, 5, 7, 5, 2, - 1, + 1, 3, 5, 3, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 7, 0, 7, 1, 1, 7, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 0, 3, 9, 3, 5, 5, 3, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 8, 7, 5, 9, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 8, 4, 5, 10, 8, 10, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 5, 0, 4, 5, 11, 0, 5, 10, 11, 11, 3, 0, - 1, - 1, - 1, - 1, + 0, 1, 9, 8, 4, 10, 8, 10, 11, 10, 4, 5, - 1, - 1, - 1, - 1, + 10, 11, 4, 10, 4, 5, 11, 3, 4, 9, 4, 1, 3, 1, 4, - 1, + 2, 5, 1, 2, 8, 5, 2, 11, 8, 4, 5, 8, - 1, - 1, - 1, - 1, + 0, 4, 11, 0, 11, 3, 4, 5, 11, 2, 11, 1, 5, 1, 11, - 1, + 0, 2, 5, 0, 5, 9, 2, 11, 5, 4, 5, 8, 11, 8, 5, - 1, + 9, 4, 5, 2, 11, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 5, 10, 3, 5, 2, 3, 4, 5, 3, 8, 4, - 1, - 1, - 1, - 1, + 5, 10, 2, 5, 2, 4, 4, 2, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 10, 2, 3, 5, 10, 3, 8, 5, 4, 5, 8, 0, 1, 9, - 1, + 5, 10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2, - 1, - 1, - 1, - 1, + 8, 4, 5, 8, 5, 3, 3, 5, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 4, 5, 1, 0, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5, - 1, - 1, - 1, - 1, + 9, 4, 5, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 11, 7, 4, 9, 11, 9, 10, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 8, 3, 4, 9, 7, 9, 11, 7, 9, 10, 11, - 1, - 1, - 1, - 1, + 1, 10, 11, 1, 11, 4, 1, 4, 0, 7, 4, 11, - 1, - 1, - 1, - 1, + 3, 1, 4, 3, 4, 8, 1, 10, 4, 7, 4, 11, 10, 11, 4, - 1, + 4, 11, 7, 9, 11, 4, 9, 2, 11, 9, 1, 2, - 1, - 1, - 1, - 1, + 9, 7, 4, 9, 11, 7, 9, 1, 11, 2, 11, 1, 0, 8, 3, - 1, + 11, 7, 4, 11, 4, 2, 2, 4, 0, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 11, 7, 4, 11, 4, 2, 8, 3, 4, 3, 2, 4, - 1, - 1, - 1, - 1, + 2, 9, 10, 2, 7, 9, 2, 3, 7, 7, 4, 9, - 1, - 1, - 1, - 1, + 9, 10, 7, 9, 7, 4, 10, 2, 7, 8, 7, 0, 2, 0, 7, - 1, + 3, 7, 10, 3, 10, 2, 7, 4, 10, 1, 10, 0, 4, 0, 10, - 1, + 1, 10, 2, 8, 7, 4, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 9, 1, 4, 1, 7, 7, 1, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1, - 1, - 1, - 1, - 1, + 4, 0, 3, 7, 4, 3, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 4, 8, 7, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 10, 8, 10, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 0, 9, 3, 9, 11, 11, 9, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 1, 10, 0, 10, 8, 8, 10, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 1, 10, 11, 3, 10, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 2, 11, 1, 11, 9, 9, 11, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 0, 9, 3, 9, 11, 1, 2, 9, 2, 11, 9, - 1, - 1, - 1, - 1, + 0, 2, 11, 8, 0, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 3, 2, 11, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 3, 8, 2, 8, 10, 10, 8, 9, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 9, 10, 2, 0, 9, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 2, 3, 8, 2, 8, 10, 0, 1, 8, 1, 10, 8, - 1, - 1, - 1, - 1, + 1, 10, 2, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 1, 3, 8, 9, 1, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 9, 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + 0, 3, 8, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, + - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1, - 1 ] ); export { MarchingCubes, edgeTable, triTable } diff --git a/src/objects/Reflector.d.ts b/src/objects/Reflector.d.ts index 10284144..bdae2e9e 100644 --- a/src/objects/Reflector.d.ts +++ b/src/objects/Reflector.d.ts @@ -1,7 +1,7 @@ -import { Mesh, BufferGeometry, ColorRepresentation, TextureEncoding, WebGLRenderTarget } from 'three' +import { Mesh, BufferGeometry, Color, TextureEncoding, WebGLRenderTarget, PerspectiveCamera } from 'three' export interface ReflectorOptions { - color?: ColorRepresentation + color?: Color | string | number textureWidth?: number textureHeight?: number clipBias?: number @@ -11,6 +11,9 @@ export interface ReflectorOptions { } export class Reflector extends Mesh { + type: 'Reflector' + camera: PerspectiveCamera + constructor(geometry?: BufferGeometry, options?: ReflectorOptions) getRenderTarget(): WebGLRenderTarget diff --git a/src/objects/Reflector.js b/src/objects/Reflector.js index f911825b..b63f5887 100644 --- a/src/objects/Reflector.js +++ b/src/objects/Reflector.js @@ -9,13 +9,82 @@ import { Vector3, Vector4, WebGLRenderTarget, + HalfFloatType, + NoToneMapping, } from 'three' class Reflector extends Mesh { + static ReflectorShader = { + uniforms: { + color: { + value: null, + }, + + tDiffuse: { + value: null, + }, + + textureMatrix: { + value: null, + }, + }, + + vertexShader: /* glsl */ ` + uniform mat4 textureMatrix; + varying vec4 vUv; + + #include + #include + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + #include + + }`, + + fragmentShader: /* glsl */ ` + uniform vec3 color; + uniform sampler2D tDiffuse; + varying vec4 vUv; + + #include + + float blendOverlay( float base, float blend ) { + + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + + } + + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } + + void main() { + + #include + + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + + #include + #include + + }`, + } + constructor(geometry, options = {}) { super(geometry) + this.isReflector = true + this.type = 'Reflector' + this.camera = new PerspectiveCamera() const scope = this @@ -24,6 +93,7 @@ class Reflector extends Mesh { const textureHeight = options.textureHeight || 512 const clipBias = options.clipBias || 0 const shader = options.shader || Reflector.ReflectorShader + const multisample = options.multisample !== undefined ? options.multisample : 4 // @@ -40,9 +110,12 @@ class Reflector extends Mesh { const q = new Vector4() const textureMatrix = new Matrix4() - const virtualCamera = new PerspectiveCamera() + const virtualCamera = this.camera - const renderTarget = new WebGLRenderTarget(textureWidth, textureHeight) + const renderTarget = new WebGLRenderTarget(textureWidth, textureHeight, { + samples: multisample, + type: HalfFloatType, + }) const material = new ShaderMaterial({ uniforms: UniformsUtils.clone(shader.uniforms), @@ -125,19 +198,23 @@ class Reflector extends Mesh { projectionMatrix.elements[14] = clipPlane.w // Render - - if ('colorSpace' in renderTarget.texture) renderTarget.texture.colorSpace = renderer.outputColorSpace - else renderTarget.texture.encoding = renderer.outputEncoding - scope.visible = false const currentRenderTarget = renderer.getRenderTarget() const currentXrEnabled = renderer.xr.enabled const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate + const currentToneMapping = renderer.toneMapping + + let isSRGB = false + if ('outputColorSpace' in renderer) isSRGB = renderer.outputColorSpace === 'srgb' + else isSRGB = renderer.outputEncoding === 3001 // sRGBEncoding renderer.xr.enabled = false // Avoid camera modification renderer.shadowMap.autoUpdate = false // Avoid re-computing shadows + if ('outputColorSpace' in renderer) renderer.outputColorSpace = 'linear-srgb' + else renderer.outputEncoding = 3000 // LinearEncoding + renderer.toneMapping = NoToneMapping renderer.setRenderTarget(renderTarget) @@ -148,6 +225,10 @@ class Reflector extends Mesh { renderer.xr.enabled = currentXrEnabled renderer.shadowMap.autoUpdate = currentShadowAutoUpdate + renderer.toneMapping = currentToneMapping + + if ('outputColorSpace' in renderer) renderer.outputColorSpace = isSRGB ? 'srgb' : 'srgb-linear' + else renderer.outputEncoding = isSRGB ? 3001 : 3000 renderer.setRenderTarget(currentRenderTarget) @@ -173,67 +254,4 @@ class Reflector extends Mesh { } } -Reflector.prototype.isReflector = true - -Reflector.ReflectorShader = { - uniforms: { - color: { - value: null, - }, - - tDiffuse: { - value: null, - }, - - textureMatrix: { - value: null, - }, - }, - - vertexShader: /* glsl */ ` - uniform mat4 textureMatrix; - varying vec4 vUv; - - #include - #include - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - #include - - }`, - - fragmentShader: /* glsl */ ` - uniform vec3 color; - uniform sampler2D tDiffuse; - varying vec4 vUv; - - #include - - float blendOverlay( float base, float blend ) { - - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - - } - - vec3 blendOverlay( vec3 base, vec3 blend ) { - - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - - } - - void main() { - - #include - - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - - }`, -} - export { Reflector } diff --git a/src/objects/ReflectorForSSRPass.js b/src/objects/ReflectorForSSRPass.js index ca79fd44..c201a598 100644 --- a/src/objects/ReflectorForSSRPass.js +++ b/src/objects/ReflectorForSSRPass.js @@ -12,12 +12,109 @@ import { UnsignedShortType, NearestFilter, Plane, + HalfFloatType, } from 'three' class ReflectorForSSRPass extends Mesh { + static ReflectorShader = { + defines: { + DISTANCE_ATTENUATION: true, + FRESNEL: true, + }, + + uniforms: { + color: { value: null }, + tDiffuse: { value: null }, + tDepth: { value: null }, + textureMatrix: { value: /* @__PURE__ */ new Matrix4() }, + maxDistance: { value: 180 }, + opacity: { value: 0.5 }, + fresnelCoe: { value: null }, + virtualCameraNear: { value: null }, + virtualCameraFar: { value: null }, + virtualCameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraMatrixWorld: { value: /* @__PURE__ */ new Matrix4() }, + virtualCameraProjectionMatrixInverse: { value: /* @__PURE__ */ new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + }, + + vertexShader: /* glsl */ ` + uniform mat4 textureMatrix; + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */ ` + uniform vec3 color; + uniform sampler2D tDiffuse; + uniform sampler2D tDepth; + uniform float maxDistance; + uniform float opacity; + uniform float fresnelCoe; + uniform float virtualCameraNear; + uniform float virtualCameraFar; + uniform mat4 virtualCameraProjectionMatrix; + uniform mat4 virtualCameraProjectionMatrixInverse; + uniform mat4 virtualCameraMatrixWorld; + uniform vec2 resolution; + varying vec4 vUv; + #include + float blendOverlay( float base, float blend ) { + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + } + vec3 blendOverlay( vec3 base, vec3 blend ) { + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + } + float getDepth( const in vec2 uv ) { + return texture2D( tDepth, uv ).x; + } + float getViewZ( const in float depth ) { + return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); + } + vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { + vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc + clipPosition *= clipW; //clip + return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view + } + void main() { + vec4 base = texture2DProj( tDiffuse, vUv ); + #ifdef useDepthTexture + vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; + uv.x=1.-uv.x; + float depth = texture2DProj( tDepth, vUv ).r; + float viewZ = getViewZ( depth ); + float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; + vec3 viewPosition=getViewPosition( uv, depth, clipW ); + vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; + if(worldPosition.y>maxDistance) discard; + float op=opacity; + #ifdef DISTANCE_ATTENUATION + float ratio=1.-(worldPosition.y/maxDistance); + float attenuation=ratio*ratio; + op=opacity*attenuation; + #endif + #ifdef FRESNEL + op*=fresnelCoe; + #endif + gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); + #else + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + #endif + } + `, + } + constructor(geometry, options = {}) { super(geometry) + this.isReflectorForSSRPass = true + this.type = 'ReflectorForSSRPass' const scope = this @@ -89,6 +186,7 @@ class ReflectorForSSRPass extends Mesh { const parameters = { depthTexture: useDepthTexture ? depthTexture : null, + type: HalfFloatType, } const renderTarget = new WebGLRenderTarget(textureWidth, textureHeight, parameters) @@ -175,11 +273,6 @@ class ReflectorForSSRPass extends Mesh { textureMatrix.multiply(virtualCamera.matrixWorldInverse) textureMatrix.multiply(scope.matrixWorld) - // Render - - if ('colorSpace' in renderTarget.texture) renderTarget.texture.colorSpace = renderer.outputColorSpace - else renderTarget.texture.encoding = renderer.outputEncoding - // scope.visible = false; const currentRenderTarget = renderer.getRenderTarget() @@ -222,100 +315,4 @@ class ReflectorForSSRPass extends Mesh { } } -ReflectorForSSRPass.prototype.isReflectorForSSRPass = true - -ReflectorForSSRPass.ReflectorShader = { - defines: { - DISTANCE_ATTENUATION: true, - FRESNEL: true, - }, - - uniforms: { - color: { value: null }, - tDiffuse: { value: null }, - tDepth: { value: null }, - textureMatrix: { value: new Matrix4() }, - maxDistance: { value: 180 }, - opacity: { value: 0.5 }, - fresnelCoe: { value: null }, - virtualCameraNear: { value: null }, - virtualCameraFar: { value: null }, - virtualCameraProjectionMatrix: { value: new Matrix4() }, - virtualCameraMatrixWorld: { value: new Matrix4() }, - virtualCameraProjectionMatrixInverse: { value: new Matrix4() }, - resolution: { value: new Vector2() }, - }, - - vertexShader: /* glsl */ ` - uniform mat4 textureMatrix; - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */ ` - uniform vec3 color; - uniform sampler2D tDiffuse; - uniform sampler2D tDepth; - uniform float maxDistance; - uniform float opacity; - uniform float fresnelCoe; - uniform float virtualCameraNear; - uniform float virtualCameraFar; - uniform mat4 virtualCameraProjectionMatrix; - uniform mat4 virtualCameraProjectionMatrixInverse; - uniform mat4 virtualCameraMatrixWorld; - uniform vec2 resolution; - varying vec4 vUv; - #include - float blendOverlay( float base, float blend ) { - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - } - vec3 blendOverlay( vec3 base, vec3 blend ) { - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - } - float getDepth( const in vec2 uv ) { - return texture2D( tDepth, uv ).x; - } - float getViewZ( const in float depth ) { - return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar ); - } - vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) { - vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc - clipPosition *= clipW; //clip - return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view - } - void main() { - vec4 base = texture2DProj( tDiffuse, vUv ); - #ifdef useDepthTexture - vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy; - uv.x=1.-uv.x; - float depth = texture2DProj( tDepth, vUv ).r; - float viewZ = getViewZ( depth ); - float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3]; - vec3 viewPosition=getViewPosition( uv, depth, clipW ); - vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz; - if(worldPosition.y>maxDistance) discard; - float op=opacity; - #ifdef DISTANCE_ATTENUATION - float ratio=1.-(worldPosition.y/maxDistance); - float attenuation=ratio*ratio; - op=opacity*attenuation; - #endif - #ifdef FRESNEL - op*=fresnelCoe; - #endif - gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); - #else - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - #endif - } - `, -} - export { ReflectorForSSRPass } diff --git a/src/objects/ReflectorRTT.js b/src/objects/ReflectorRTT.js deleted file mode 100644 index ac5d6607..00000000 --- a/src/objects/ReflectorRTT.js +++ /dev/null @@ -1,10 +0,0 @@ -import { Reflector } from '../objects/Reflector.js' - -class ReflectorRTT extends Reflector { - constructor(geometry, options) { - super(geometry, options) - this.geometry.setDrawRange(0, 0) // avoid rendering geometry - } -} - -export { ReflectorRTT } diff --git a/src/objects/ReflectorRTT.ts b/src/objects/ReflectorRTT.ts new file mode 100644 index 00000000..9d78c86c --- /dev/null +++ b/src/objects/ReflectorRTT.ts @@ -0,0 +1,11 @@ +import { BufferGeometry } from 'three' +import { Reflector, ReflectorOptions } from '../objects/Reflector' + +class ReflectorRTT extends Reflector { + constructor(geometry?: BufferGeometry, options?: ReflectorOptions) { + super(geometry, options) + this.geometry.setDrawRange(0, 0) + } +} + +export { ReflectorRTT } diff --git a/src/objects/Refractor.d.ts b/src/objects/Refractor.d.ts index 002ebc7a..37b5bb7f 100644 --- a/src/objects/Refractor.d.ts +++ b/src/objects/Refractor.d.ts @@ -1,7 +1,7 @@ -import { Mesh, BufferGeometry, ColorRepresentation, TextureEncoding, WebGLRenderTarget } from 'three' +import { Mesh, BufferGeometry, Color, TextureEncoding, WebGLRenderTarget, PerspectiveCamera } from 'three' export interface RefractorOptions { - color?: ColorRepresentation + color?: Color | string | number textureWidth?: number textureHeight?: number clipBias?: number @@ -11,6 +11,9 @@ export interface RefractorOptions { } export class Refractor extends Mesh { + type: 'Refractor' + camera: PerspectiveCamera + constructor(geometry?: BufferGeometry, options?: RefractorOptions) getRenderTarget(): WebGLRenderTarget diff --git a/src/objects/Refractor.js b/src/objects/Refractor.js index 47d15cd6..429f3eb1 100644 --- a/src/objects/Refractor.js +++ b/src/objects/Refractor.js @@ -10,13 +10,76 @@ import { Vector3, Vector4, WebGLRenderTarget, + NoToneMapping, + HalfFloatType, } from 'three' class Refractor extends Mesh { + static RefractorShader = { + uniforms: { + color: { + value: null, + }, + + tDiffuse: { + value: null, + }, + + textureMatrix: { + value: null, + }, + }, + + vertexShader: /* glsl */ ` + + uniform mat4 textureMatrix; + + varying vec4 vUv; + + void main() { + + vUv = textureMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: /* glsl */ ` + + uniform vec3 color; + uniform sampler2D tDiffuse; + + varying vec4 vUv; + + float blendOverlay( float base, float blend ) { + + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); + + } + + vec3 blendOverlay( vec3 base, vec3 blend ) { + + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); + + } + + void main() { + + vec4 base = texture2DProj( tDiffuse, vUv ); + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); + + #include + #include + + }`, + } + constructor(geometry, options = {}) { super(geometry) + this.isRefractor = true + this.type = 'Refractor' + this.camera = new PerspectiveCamera() const scope = this @@ -25,10 +88,11 @@ class Refractor extends Mesh { const textureHeight = options.textureHeight || 512 const clipBias = options.clipBias || 0 const shader = options.shader || Refractor.RefractorShader + const multisample = options.multisample !== undefined ? options.multisample : 4 // - const virtualCamera = new PerspectiveCamera() + const virtualCamera = this.camera virtualCamera.matrixAutoUpdate = false virtualCamera.userData.refractor = true @@ -39,7 +103,10 @@ class Refractor extends Mesh { // render target - const renderTarget = new WebGLRenderTarget(textureWidth, textureHeight) + const renderTarget = new WebGLRenderTarget(textureWidth, textureHeight, { + samples: multisample, + type: HalfFloatType, + }) // material @@ -165,9 +232,17 @@ class Refractor extends Mesh { const currentRenderTarget = renderer.getRenderTarget() const currentXrEnabled = renderer.xr.enabled const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate + const currentToneMapping = renderer.toneMapping + + let isSRGB = false + if ('outputColorSpace' in renderer) isSRGB = renderer.outputColorSpace === 'srgb' + else isSRGB = renderer.outputEncoding === 3001 // sRGBEncoding renderer.xr.enabled = false // avoid camera modification renderer.shadowMap.autoUpdate = false // avoid re-computing shadows + if ('outputColorSpace' in renderer) renderer.outputColorSpace = 'linear-srgb' + else renderer.outputEncoding = 3000 // LinearEncoding + renderer.toneMapping = NoToneMapping renderer.setRenderTarget(renderTarget) if (renderer.autoClear === false) renderer.clear() @@ -175,8 +250,12 @@ class Refractor extends Mesh { renderer.xr.enabled = currentXrEnabled renderer.shadowMap.autoUpdate = currentShadowAutoUpdate + renderer.toneMapping = currentToneMapping renderer.setRenderTarget(currentRenderTarget) + if ('outputColorSpace' in renderer) renderer.outputColorSpace = isSRGB ? 'srgb' : 'srgb-linear' + else renderer.outputEncoding = isSRGB ? 3001 : 3000 + // restore viewport const viewport = camera.viewport @@ -191,11 +270,6 @@ class Refractor extends Mesh { // this.onBeforeRender = function (renderer, scene, camera) { - // Render - - if ('colorSpace' in renderTarget.texture) renderTarget.texture.colorSpace = renderer.outputColorSpace - else renderTarget.texture.encoding = renderer.outputEncoding - // ensure refractors are rendered only once per frame if (camera.userData.refractor === true) return @@ -226,61 +300,4 @@ class Refractor extends Mesh { } } -Refractor.prototype.isRefractor = true - -Refractor.RefractorShader = { - uniforms: { - color: { - value: null, - }, - - tDiffuse: { - value: null, - }, - - textureMatrix: { - value: null, - }, - }, - - vertexShader: /* glsl */ ` - - uniform mat4 textureMatrix; - - varying vec4 vUv; - - void main() { - - vUv = textureMatrix * vec4( position, 1.0 ); - gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); - - }`, - - fragmentShader: /* glsl */ ` - - uniform vec3 color; - uniform sampler2D tDiffuse; - - varying vec4 vUv; - - float blendOverlay( float base, float blend ) { - - return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); - - } - - vec3 blendOverlay( vec3 base, vec3 blend ) { - - return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); - - } - - void main() { - - vec4 base = texture2DProj( tDiffuse, vUv ); - gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); - - }`, -} - export { Refractor } diff --git a/src/objects/ShadowMesh.d.ts b/src/objects/ShadowMesh.d.ts index 8dd5aff9..0445649e 100644 --- a/src/objects/ShadowMesh.d.ts +++ b/src/objects/ShadowMesh.d.ts @@ -1,7 +1,10 @@ -import { Mesh, Plane, Vector4 } from 'three' +import { Mesh, Plane, Vector4, Matrix4, MeshBasicMaterial, BufferGeometry } from 'three' -export class ShadowMesh extends Mesh { - constructor() +export class ShadowMesh extends Mesh { + readonly isShadowMesh: true + meshMatrix: Matrix4 + + constructor(mesh: Mesh) update(plane: Plane, lightPosition4D: Vector4): void } diff --git a/src/objects/ShadowMesh.js b/src/objects/ShadowMesh.js index 7eae5d9a..38f91193 100644 --- a/src/objects/ShadowMesh.js +++ b/src/objects/ShadowMesh.js @@ -1,10 +1,10 @@ -import { Matrix4, Mesh, MeshBasicMaterial } from 'three' +import { Matrix4, Mesh, MeshBasicMaterial, EqualStencilFunc, IncrementStencilOp } from 'three' /** * A shadow Mesh that follows a shadow-casting Mesh in the scene, but is confined to a single plane. */ -const _shadowMatrix = new Matrix4() +const _shadowMatrix = /* @__PURE__ */ new Matrix4() class ShadowMesh extends Mesh { constructor(mesh) { @@ -13,10 +13,16 @@ class ShadowMesh extends Mesh { transparent: true, opacity: 0.6, depthWrite: false, + stencilWrite: true, + stencilFunc: EqualStencilFunc, + stencilRef: 0, + stencilZPass: IncrementStencilOp, }) super(mesh.geometry, shadowMaterial) + this.isShadowMesh = true + this.meshMatrix = mesh.matrixWorld this.frustumCulled = false @@ -58,6 +64,4 @@ class ShadowMesh extends Mesh { } } -ShadowMesh.prototype.isShadowMesh = true - export { ShadowMesh } diff --git a/src/objects/Sky.ts b/src/objects/Sky.ts index f7cace39..af40a899 100644 --- a/src/objects/Sky.ts +++ b/src/objects/Sky.ts @@ -1,203 +1,206 @@ import { BackSide, BoxGeometry, Mesh, ShaderMaterial, UniformsUtils, Vector3 } from 'three' -/** - * Based on "A Practical Analytic Model for Daylight" - * aka The Preetham Model, the de facto standard analytic skydome model - * https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight - * - * First implemented by Simon Wallner - * http://www.simonwallner.at/projects/atmospheric-scattering - * - * Improved by Martin Upitis - * http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR - * - * Three.js integration by zz85 http://twitter.com/blurspline - */ -class Sky extends Mesh { - constructor() { - super(new BoxGeometry(1, 1, 1), Sky.material) - } - - public static SkyShader = { - uniforms: { - turbidity: { value: 2 }, - rayleigh: { value: 1 }, - mieCoefficient: { value: 0.005 }, - mieDirectionalG: { value: 0.8 }, - sunPosition: { value: new Vector3() }, - up: { value: new Vector3(0, 1, 0) }, - }, - - vertexShader: [ - 'uniform vec3 sunPosition;', - 'uniform float rayleigh;', - 'uniform float turbidity;', - 'uniform float mieCoefficient;', - 'uniform vec3 up;', - - 'varying vec3 vWorldPosition;', - 'varying vec3 vSunDirection;', - 'varying float vSunfade;', - 'varying vec3 vBetaR;', - 'varying vec3 vBetaM;', - 'varying float vSunE;', +const SkyShader = { + uniforms: { + turbidity: { value: 2 }, + rayleigh: { value: 1 }, + mieCoefficient: { value: 0.005 }, + mieDirectionalG: { value: 0.8 }, + sunPosition: { value: /* @__PURE__ */ new Vector3() }, + up: { value: /* @__PURE__ */ new Vector3(0, 1, 0) }, + }, + + vertexShader: /* glsl */ ` + uniform vec3 sunPosition; + uniform float rayleigh; + uniform float turbidity; + uniform float mieCoefficient; + uniform vec3 up; + + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; // constants for atmospheric scattering - 'const float e = 2.71828182845904523536028747135266249775724709369995957;', - 'const float pi = 3.141592653589793238462643383279502884197169;', + const float e = 2.71828182845904523536028747135266249775724709369995957; + const float pi = 3.141592653589793238462643383279502884197169; // wavelength of used primaries, according to preetham - 'const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 );', + const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 ); // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) - 'const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 );', + const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); // mie stuff // K coefficient for the primaries - 'const float v = 4.0;', - 'const vec3 K = vec3( 0.686, 0.678, 0.666 );', + const float v = 4.0; + const vec3 K = vec3( 0.686, 0.678, 0.666 ); // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K - 'const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 );', + const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); // earth shadow hack // cutoffAngle = pi / 1.95; - 'const float cutoffAngle = 1.6110731556870734;', - 'const float steepness = 1.5;', - 'const float EE = 1000.0;', + const float cutoffAngle = 1.6110731556870734; + const float steepness = 1.5; + const float EE = 1000.0; - 'float sunIntensity( float zenithAngleCos ) {', - ' zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 );', - ' return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) );', - '}', + float sunIntensity( float zenithAngleCos ) { + zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 ); + return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) ); + } - 'vec3 totalMie( float T ) {', - ' float c = ( 0.2 * T ) * 10E-18;', - ' return 0.434 * c * MieConst;', - '}', + vec3 totalMie( float T ) { + float c = ( 0.2 * T ) * 10E-18; + return 0.434 * c * MieConst; + } - 'void main() {', + void main() { - ' vec4 worldPosition = modelMatrix * vec4( position, 1.0 );', - ' vWorldPosition = worldPosition.xyz;', + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vWorldPosition = worldPosition.xyz; - ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', - ' gl_Position.z = gl_Position.w;', // set z to camera.far + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + gl_Position.z = gl_Position.w; // set z to camera.far - ' vSunDirection = normalize( sunPosition );', + vSunDirection = normalize( sunPosition ); - ' vSunE = sunIntensity( dot( vSunDirection, up ) );', + vSunE = sunIntensity( dot( vSunDirection, up ) ); - ' vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 );', + vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 ); - ' float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) );', + float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) ); // extinction (absorbtion + out scattering) // rayleigh coefficients - ' vBetaR = totalRayleigh * rayleighCoefficient;', + vBetaR = totalRayleigh * rayleighCoefficient; // mie coefficients - ' vBetaM = totalMie( turbidity ) * mieCoefficient;', + vBetaM = totalMie( turbidity ) * mieCoefficient; - '}', - ].join('\n'), + } + `, - fragmentShader: [ - 'varying vec3 vWorldPosition;', - 'varying vec3 vSunDirection;', - 'varying float vSunfade;', - 'varying vec3 vBetaR;', - 'varying vec3 vBetaM;', - 'varying float vSunE;', + fragmentShader: /* glsl */ ` + varying vec3 vWorldPosition; + varying vec3 vSunDirection; + varying float vSunfade; + varying vec3 vBetaR; + varying vec3 vBetaM; + varying float vSunE; - 'uniform float mieDirectionalG;', - 'uniform vec3 up;', + uniform float mieDirectionalG; + uniform vec3 up; - 'const vec3 cameraPos = vec3( 0.0, 0.0, 0.0 );', + const vec3 cameraPos = vec3( 0.0, 0.0, 0.0 ); // constants for atmospheric scattering - 'const float pi = 3.141592653589793238462643383279502884197169;', + const float pi = 3.141592653589793238462643383279502884197169; - 'const float n = 1.0003;', // refractive index of air - 'const float N = 2.545E25;', // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) + const float n = 1.0003; // refractive index of air + const float N = 2.545E25; // number of molecules per unit volume for air at 288.15K and 1013mb (sea level -45 celsius) // optical length at zenith for molecules - 'const float rayleighZenithLength = 8.4E3;', - 'const float mieZenithLength = 1.25E3;', + const float rayleighZenithLength = 8.4E3; + const float mieZenithLength = 1.25E3; // 66 arc seconds -> degrees, and the cosine of that - 'const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324;', + const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324; // 3.0 / ( 16.0 * pi ) - 'const float THREE_OVER_SIXTEENPI = 0.05968310365946075;', + const float THREE_OVER_SIXTEENPI = 0.05968310365946075; // 1.0 / ( 4.0 * pi ) - 'const float ONE_OVER_FOURPI = 0.07957747154594767;', + const float ONE_OVER_FOURPI = 0.07957747154594767; - 'float rayleighPhase( float cosTheta ) {', - ' return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) );', - '}', + float rayleighPhase( float cosTheta ) { + return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) ); + } - 'float hgPhase( float cosTheta, float g ) {', - ' float g2 = pow( g, 2.0 );', - ' float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 );', - ' return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse );', - '}', + float hgPhase( float cosTheta, float g ) { + float g2 = pow( g, 2.0 ); + float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 ); + return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse ); + } - 'void main() {', + void main() { - ' vec3 direction = normalize( vWorldPosition - cameraPos );', + vec3 direction = normalize( vWorldPosition - cameraPos ); // optical length // cutoff angle at 90 to avoid singularity in next formula. - ' float zenithAngle = acos( max( 0.0, dot( up, direction ) ) );', - ' float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) );', - ' float sR = rayleighZenithLength * inverse;', - ' float sM = mieZenithLength * inverse;', + float zenithAngle = acos( max( 0.0, dot( up, direction ) ) ); + float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) ); + float sR = rayleighZenithLength * inverse; + float sM = mieZenithLength * inverse; // combined extinction factor - ' vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) );', + vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) ); // in scattering - ' float cosTheta = dot( direction, vSunDirection );', + float cosTheta = dot( direction, vSunDirection ); - ' float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 );', - ' vec3 betaRTheta = vBetaR * rPhase;', + float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 ); + vec3 betaRTheta = vBetaR * rPhase; - ' float mPhase = hgPhase( cosTheta, mieDirectionalG );', - ' vec3 betaMTheta = vBetaM * mPhase;', + float mPhase = hgPhase( cosTheta, mieDirectionalG ); + vec3 betaMTheta = vBetaM * mPhase; - ' vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) );', - ' Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) );', + vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) ); + Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) ); // nightsky - ' float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2]', - ' float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2]', - ' vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 );', - ' vec3 L0 = vec3( 0.1 ) * Fex;', + float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2] + float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2] + vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 ); + vec3 L0 = vec3( 0.1 ) * Fex; // composition + solar disc - ' float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta );', - ' L0 += ( vSunE * 19000.0 * Fex ) * sundisk;', + float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta ); + L0 += ( vSunE * 19000.0 * Fex ) * sundisk; - ' vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 );', + vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 ); - ' vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) );', + vec3 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) ); - ' gl_FragColor = vec4( retColor, 1.0 );', + gl_FragColor = vec4( retColor, 1.0 ); - '#include ', - '#include ', + #include + #include - '}', - ].join('\n'), + } + `, +} + +const material = /* @__PURE__ */ new ShaderMaterial({ + name: 'SkyShader', + fragmentShader: SkyShader.fragmentShader, + vertexShader: SkyShader.vertexShader, + uniforms: /* @__PURE__ */ UniformsUtils.clone(SkyShader.uniforms), + side: BackSide, + depthWrite: false, +}) + +/** + * Based on "A Practical Analytic Model for Daylight" + * aka The Preetham Model, the de facto standard analytic skydome model + * https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight + * + * First implemented by Simon Wallner + * http://www.simonwallner.at/projects/atmospheric-scattering + * + * Improved by Martin Upitis + * http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR + * + * Three.js integration by zz85 http://twitter.com/blurspline + */ +class Sky extends Mesh { + constructor() { + super(new BoxGeometry(1, 1, 1), material) } - public static material = new ShaderMaterial({ - name: 'SkyShader', - fragmentShader: Sky.SkyShader.fragmentShader, - vertexShader: Sky.SkyShader.vertexShader, - uniforms: UniformsUtils.clone(Sky.SkyShader.uniforms), - side: BackSide, - depthWrite: false, - }) + static SkyShader = SkyShader + public static material = material } export { Sky } diff --git a/src/objects/Water.d.ts b/src/objects/Water.d.ts index 7c95487b..e7c5f0e7 100644 --- a/src/objects/Water.d.ts +++ b/src/objects/Water.d.ts @@ -1,4 +1,4 @@ -import { BufferGeometry, ColorRepresentation, Mesh, ShaderMaterial, Side, Texture, Vector3 } from 'three' +import { BufferGeometry, Color, Mesh, ShaderMaterial, Side, Texture, Vector3 } from 'three' export interface WaterOptions { textureWidth?: number @@ -8,8 +8,8 @@ export interface WaterOptions { time?: number waterNormals?: Texture sunDirection?: Vector3 - sunColor?: ColorRepresentation - waterColor?: ColorRepresentation + sunColor?: Color | string | number + waterColor?: Color | string | number eye?: Vector3 distortionScale?: number side?: Side diff --git a/src/objects/Water.js b/src/objects/Water.js index cc7cf583..41e6343f 100644 --- a/src/objects/Water.js +++ b/src/objects/Water.js @@ -24,6 +24,8 @@ class Water extends Mesh { constructor(geometry, options = {}) { super(geometry) + this.isWater = true + const scope = this const textureWidth = options.textureWidth !== undefined ? options.textureWidth : 512 @@ -176,7 +178,8 @@ class Water extends Mesh { gl_FragColor = vec4( outgoingLight, alpha ); #include - #include + #include + #include }`, } @@ -309,6 +312,4 @@ class Water extends Mesh { } } -Water.prototype.isWater = true - export { Water } diff --git a/src/objects/Water2.d.ts b/src/objects/Water2.d.ts index 9fec9b70..23a53e91 100644 --- a/src/objects/Water2.d.ts +++ b/src/objects/Water2.d.ts @@ -1,7 +1,7 @@ -import { BufferGeometry, ColorRepresentation, Mesh, ShaderMaterial, Texture, TextureEncoding, Vector2 } from 'three' +import { BufferGeometry, Color, Mesh, ShaderMaterial, Texture, TextureEncoding, Vector2 } from 'three' export interface Water2Options { - color?: ColorRepresentation + color?: Color | string | number textureWidth?: number textureHeight?: number clipBias?: number diff --git a/src/objects/Water2.js b/src/objects/Water2.js index a5f75219..246d4578 100644 --- a/src/objects/Water2.js +++ b/src/objects/Water2.js @@ -5,7 +5,6 @@ import { Mesh, RepeatWrapping, ShaderMaterial, - TextureLoader, UniformsLib, UniformsUtils, Vector2, @@ -22,9 +21,151 @@ import { Refractor } from './Refractor' */ class Water2 extends Mesh { + static WaterShader = { + uniforms: { + color: { + value: null, + }, + + reflectivity: { + value: 0, + }, + + tReflectionMap: { + value: null, + }, + + tRefractionMap: { + value: null, + }, + + tNormalMap0: { + value: null, + }, + + tNormalMap1: { + value: null, + }, + + textureMatrix: { + value: null, + }, + + config: { + value: /* @__PURE__ */ new Vector4(), + }, + }, + + vertexShader: /* glsl */ ` + + #include + #include + #include + + uniform mat4 textureMatrix; + + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; + + void main() { + + vUv = uv; + vCoord = textureMatrix * vec4( position, 1.0 ); + + vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); + vToEye = cameraPosition - worldPosition.xyz; + + vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex + gl_Position = projectionMatrix * mvPosition; + + #include + #include + + }`, + + fragmentShader: /* glsl */ ` + + #include + #include + #include + + uniform sampler2D tReflectionMap; + uniform sampler2D tRefractionMap; + uniform sampler2D tNormalMap0; + uniform sampler2D tNormalMap1; + + #ifdef USE_FLOWMAP + uniform sampler2D tFlowMap; + #else + uniform vec2 flowDirection; + #endif + + uniform vec3 color; + uniform float reflectivity; + uniform vec4 config; + + varying vec4 vCoord; + varying vec2 vUv; + varying vec3 vToEye; + + void main() { + + #include + + float flowMapOffset0 = config.x; + float flowMapOffset1 = config.y; + float halfCycle = config.z; + float scale = config.w; + + vec3 toEye = normalize( vToEye ); + + // determine flow direction + vec2 flow; + #ifdef USE_FLOWMAP + flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; + #else + flow = flowDirection; + #endif + flow.x *= - 1.0; + + // sample normal maps (distort uvs with flowdata) + vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); + vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); + + // linear interpolate to get the final normal color + float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; + vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); + + // calculate normal vector + vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); + + // calculate the fresnel term to blend reflection and refraction maps + float theta = max( dot( toEye, normal ), 0.0 ); + float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); + + // calculate final uv coords + vec3 coord = vCoord.xyz / vCoord.w; + vec2 uv = coord.xy + coord.z * normal.xz * 0.05; + + vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); + vec4 refractColor = texture2D( tRefractionMap, uv ); + + // multiply water color with the mix of both textures + gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); + + #include + #include + #include + + }`, + } + constructor(geometry, options = {}) { super(geometry) + this.isWater = true + this.type = 'Water' const scope = this @@ -40,11 +181,9 @@ class Water2 extends Mesh { const shader = options.shader || Water2.WaterShader const encoding = options.encoding !== undefined ? options.encoding : 3000 - const textureLoader = new TextureLoader() - const flowMap = options.flowMap || undefined - const normalMap0 = options.normalMap0 || textureLoader.load('textures/water/Water_1_M_Normal.jpg') - const normalMap1 = options.normalMap1 || textureLoader.load('textures/water/Water_2_M_Normal.jpg') + const normalMap0 = options.normalMap0 + const normalMap1 = options.normalMap1 const cycle = 0.15 // a cycle of a flow map phase const halfCycle = cycle * 0.5 @@ -174,154 +313,4 @@ class Water2 extends Mesh { } } -Water2.prototype.isWater = true - -Water2.WaterShader = { - uniforms: { - color: { - type: 'c', - value: null, - }, - - reflectivity: { - type: 'f', - value: 0, - }, - - tReflectionMap: { - type: 't', - value: null, - }, - - tRefractionMap: { - type: 't', - value: null, - }, - - tNormalMap0: { - type: 't', - value: null, - }, - - tNormalMap1: { - type: 't', - value: null, - }, - - textureMatrix: { - type: 'm4', - value: null, - }, - - config: { - type: 'v4', - value: new Vector4(), - }, - }, - - vertexShader: /* glsl */ ` - - #include - #include - #include - - uniform mat4 textureMatrix; - - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; - - void main() { - - vUv = uv; - vCoord = textureMatrix * vec4( position, 1.0 ); - - vec4 worldPosition = modelMatrix * vec4( position, 1.0 ); - vToEye = cameraPosition - worldPosition.xyz; - - vec4 mvPosition = viewMatrix * worldPosition; // used in fog_vertex - gl_Position = projectionMatrix * mvPosition; - - #include - #include - - }`, - - fragmentShader: /* glsl */ ` - - #include - #include - #include - - uniform sampler2D tReflectionMap; - uniform sampler2D tRefractionMap; - uniform sampler2D tNormalMap0; - uniform sampler2D tNormalMap1; - - #ifdef USE_FLOWMAP - uniform sampler2D tFlowMap; - #else - uniform vec2 flowDirection; - #endif - - uniform vec3 color; - uniform float reflectivity; - uniform vec4 config; - - varying vec4 vCoord; - varying vec2 vUv; - varying vec3 vToEye; - - void main() { - - #include - - float flowMapOffset0 = config.x; - float flowMapOffset1 = config.y; - float halfCycle = config.z; - float scale = config.w; - - vec3 toEye = normalize( vToEye ); - - // determine flow direction - vec2 flow; - #ifdef USE_FLOWMAP - flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0; - #else - flow = flowDirection; - #endif - flow.x *= - 1.0; - - // sample normal maps (distort uvs with flowdata) - vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 ); - vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 ); - - // linear interpolate to get the final normal color - float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle; - vec4 normalColor = mix( normalColor0, normalColor1, flowLerp ); - - // calculate normal vector - vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) ); - - // calculate the fresnel term to blend reflection and refraction maps - float theta = max( dot( toEye, normal ), 0.0 ); - float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 ); - - // calculate final uv coords - vec3 coord = vCoord.xyz / vCoord.w; - vec2 uv = coord.xy + coord.z * normal.xz * 0.05; - - vec4 reflectColor = texture2D( tReflectionMap, vec2( 1.0 - uv.x, uv.y ) ); - vec4 refractColor = texture2D( tRefractionMap, uv ); - - // multiply water color with the mix of both textures - gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance ); - - #include - #include - #include - - }`, -} - export { Water2 } diff --git a/src/offscreen/jank.js b/src/offscreen/jank.js deleted file mode 100644 index cdcff8e6..00000000 --- a/src/offscreen/jank.js +++ /dev/null @@ -1,33 +0,0 @@ -var interval = null -var result = null - -function initJank() { - var button = document.getElementById('button') - button.addEventListener('click', function () { - if (interval === null) { - interval = setInterval(jank, 1000 / 60) - - button.textContent = 'STOP JANK' - } else { - clearInterval(interval) - interval = null - - button.textContent = 'START JANK' - result.textContent = '' - } - }) - - result = document.getElementById('result') -} - -function jank() { - var number = 0 - - for (let i = 0; i < 10000000; i++) { - number += Math.random() - } - - result.textContent = number -} - -export default initJank diff --git a/src/offscreen/offscreen.js b/src/offscreen/offscreen.js deleted file mode 100644 index 94ea4bb2..00000000 --- a/src/offscreen/offscreen.js +++ /dev/null @@ -1,8 +0,0 @@ -import init from './scene' - -if (typeof self !== 'undefined') { - self.onmessage = function (message) { - const data = message.data - init(data.drawingSurface, data.width, data.height, data.pixelRatio, data.path) - } -} diff --git a/src/offscreen/scene.js b/src/offscreen/scene.js deleted file mode 100644 index 0ead98cd..00000000 --- a/src/offscreen/scene.js +++ /dev/null @@ -1,72 +0,0 @@ -import * as THREE from 'three' - -var camera, scene, renderer, group - -function init(canvas, width, height, pixelRatio, path) { - camera = new THREE.PerspectiveCamera(40, width / height, 1, 1000) - camera.position.z = 200 - - scene = new THREE.Scene() - scene.fog = new THREE.Fog(0x444466, 100, 400) - scene.background = new THREE.Color(0x444466) - - group = new THREE.Group() - scene.add(group) - - // we don't use ImageLoader since it has a DOM dependency (HTML5 image element) - - var loader = new THREE.ImageBitmapLoader().setPath(path) - loader.setOptions({ imageOrientation: 'flipY' }) - loader.load('textures/matcaps/matcap-porcelain-white.jpg', function (imageBitmap) { - var texture = new THREE.CanvasTexture(imageBitmap) - - var geometry = new THREE.IcosahedronGeometry(5, 8) - var materials = [ - new THREE.MeshMatcapMaterial({ color: 0xaa24df, matcap: texture }), - new THREE.MeshMatcapMaterial({ color: 0x605d90, matcap: texture }), - new THREE.MeshMatcapMaterial({ color: 0xe04a3f, matcap: texture }), - new THREE.MeshMatcapMaterial({ color: 0xe30456, matcap: texture }), - ] - - for (let i = 0; i < 100; i++) { - var material = materials[i % materials.length] - var mesh = new THREE.Mesh(geometry, material) - mesh.position.x = random() * 200 - 100 - mesh.position.y = random() * 200 - 100 - mesh.position.z = random() * 200 - 100 - mesh.scale.setScalar(random() + 1) - group.add(mesh) - } - - renderer = new THREE.WebGLRenderer({ antialias: true, canvas: canvas }) - renderer.setPixelRatio(pixelRatio) - renderer.setSize(width, height, false) - - animate() - }) -} - -function animate() { - // group.rotation.x = Date.now() / 4000; - group.rotation.y = -Date.now() / 4000 - - renderer.render(scene, camera) - - if (self.requestAnimationFrame) { - self.requestAnimationFrame(animate) - } else { - // Firefox - } -} - -// PRNG - -var seed = 1 - -function random() { - var x = Math.sin(seed++) * 10000 - - return x - Math.floor(x) -} - -export default init diff --git a/src/postprocessing/AdaptiveToneMappingPass.d.ts b/src/postprocessing/AdaptiveToneMappingPass.d.ts index a3d2fa97..dd1cb1c1 100644 --- a/src/postprocessing/AdaptiveToneMappingPass.d.ts +++ b/src/postprocessing/AdaptiveToneMappingPass.d.ts @@ -1,6 +1,6 @@ import { WebGLRenderTarget, ShaderMaterial } from 'three' -import { Pass } from './Pass' +import { Pass, FullScreenQuad } from './Pass' export class AdaptiveToneMappingPass extends Pass { constructor(adaptive?: boolean, resolution?: number) @@ -16,7 +16,7 @@ export class AdaptiveToneMappingPass extends Pass { adaptLuminanceShader: object materialAdaptiveLum: ShaderMaterial materialToneMap: ShaderMaterial - fsQuad: object + fsQuad: FullScreenQuad reset(): void setAdaptive(adaptive: boolean): void diff --git a/src/postprocessing/AdaptiveToneMappingPass.js b/src/postprocessing/AdaptiveToneMappingPass.js index 4381a40d..7bf40f41 100644 --- a/src/postprocessing/AdaptiveToneMappingPass.js +++ b/src/postprocessing/AdaptiveToneMappingPass.js @@ -1,14 +1,12 @@ import { - LinearFilter, LinearMipmapLinearFilter, MeshBasicMaterial, NoBlending, - RGBAFormat, ShaderMaterial, UniformsUtils, WebGLRenderTarget, } from 'three' -import { Pass, FullScreenQuad } from '../postprocessing/Pass' +import { Pass, FullScreenQuad } from './Pass' import { CopyShader } from '../shaders/CopyShader' import { LuminosityShader } from '../shaders/LuminosityShader' import { ToneMapShader } from '../shaders/ToneMapShader' @@ -21,112 +19,103 @@ import { ToneMapShader } from '../shaders/ToneMapShader' * Full-screen tone-mapping shader based on http://www.graphics.cornell.edu/~jaf/publications/sig02_paper.pdf */ -var AdaptiveToneMappingPass = function (adaptive, resolution) { - this.resolution = resolution !== undefined ? resolution : 256 - this.needsInit = true - this.adaptive = adaptive !== undefined ? !!adaptive : true - - this.luminanceRT = null - this.previousLuminanceRT = null - this.currentLuminanceRT = null - - if (CopyShader === undefined) console.error('THREE.AdaptiveToneMappingPass relies on CopyShader') - - var copyShader = CopyShader - - this.copyUniforms = UniformsUtils.clone(copyShader.uniforms) - - this.materialCopy = new ShaderMaterial({ - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: NoBlending, - depthTest: false, - }) - - if (LuminosityShader === undefined) console.error('THREE.AdaptiveToneMappingPass relies on LuminosityShader') - - this.materialLuminance = new ShaderMaterial({ - uniforms: UniformsUtils.clone(LuminosityShader.uniforms), - vertexShader: LuminosityShader.vertexShader, - fragmentShader: LuminosityShader.fragmentShader, - blending: NoBlending, - }) - - this.adaptLuminanceShader = { - defines: { - MIP_LEVEL_1X1: (Math.log(this.resolution) / Math.log(2.0)).toFixed(1), - }, - uniforms: { - lastLum: { value: null }, - currentLum: { value: null }, - minLuminance: { value: 0.01 }, - delta: { value: 0.016 }, - tau: { value: 1.0 }, - }, - vertexShader: [ - 'varying vec2 vUv;', - - 'void main() {', - - ' vUv = uv;', - ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', - - '}', - ].join('\n'), - fragmentShader: [ - 'varying vec2 vUv;', - - 'uniform sampler2D lastLum;', - 'uniform sampler2D currentLum;', - 'uniform float minLuminance;', - 'uniform float delta;', - 'uniform float tau;', - - 'void main() {', - - ' vec4 lastLum = texture2D( lastLum, vUv, MIP_LEVEL_1X1 );', - ' vec4 currentLum = texture2D( currentLum, vUv, MIP_LEVEL_1X1 );', - - ' float fLastLum = max( minLuminance, lastLum.r );', - ' float fCurrentLum = max( minLuminance, currentLum.r );', - - //The adaption seems to work better in extreme lighting differences - //if the input luminance is squared. - ' fCurrentLum *= fCurrentLum;', - - // Adapt the luminance using Pattanaik's technique - ' float fAdaptedLum = fLastLum + (fCurrentLum - fLastLum) * (1.0 - exp(-delta * tau));', - // "fAdaptedLum = sqrt(fAdaptedLum);", - ' gl_FragColor.r = fAdaptedLum;', - '}', - ].join('\n'), - } - - this.materialAdaptiveLum = new ShaderMaterial({ - uniforms: UniformsUtils.clone(this.adaptLuminanceShader.uniforms), - vertexShader: this.adaptLuminanceShader.vertexShader, - fragmentShader: this.adaptLuminanceShader.fragmentShader, - defines: Object.assign({}, this.adaptLuminanceShader.defines), - blending: NoBlending, - }) - - if (ToneMapShader === undefined) console.error('THREE.AdaptiveToneMappingPass relies on ToneMapShader') - - this.materialToneMap = new ShaderMaterial({ - uniforms: UniformsUtils.clone(ToneMapShader.uniforms), - vertexShader: ToneMapShader.vertexShader, - fragmentShader: ToneMapShader.fragmentShader, - blending: NoBlending, - }) - - this.fsQuad = new FullScreenQuad(null) -} +class AdaptiveToneMappingPass extends Pass { + constructor(adaptive, resolution) { + super() + + this.resolution = resolution !== undefined ? resolution : 256 + this.needsInit = true + this.adaptive = adaptive !== undefined ? !!adaptive : true + + this.luminanceRT = null + this.previousLuminanceRT = null + this.currentLuminanceRT = null + + const copyShader = CopyShader + + this.copyUniforms = UniformsUtils.clone(copyShader.uniforms) + + this.materialCopy = new ShaderMaterial({ + uniforms: this.copyUniforms, + vertexShader: copyShader.vertexShader, + fragmentShader: copyShader.fragmentShader, + blending: NoBlending, + depthTest: false, + }) + + this.materialLuminance = new ShaderMaterial({ + uniforms: UniformsUtils.clone(LuminosityShader.uniforms), + vertexShader: LuminosityShader.vertexShader, + fragmentShader: LuminosityShader.fragmentShader, + blending: NoBlending, + }) + + this.adaptLuminanceShader = { + defines: { + MIP_LEVEL_1X1: (Math.log(this.resolution) / Math.log(2.0)).toFixed(1), + }, + uniforms: { + lastLum: { value: null }, + currentLum: { value: null }, + minLuminance: { value: 0.01 }, + delta: { value: 0.016 }, + tau: { value: 1.0 }, + }, + vertexShader: `varying vec2 vUv; + + void main() { + + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + + }`, + + fragmentShader: `varying vec2 vUv; + + uniform sampler2D lastLum; + uniform sampler2D currentLum; + uniform float minLuminance; + uniform float delta; + uniform float tau; + + void main() { + + vec4 lastLum = texture2D( lastLum, vUv, MIP_LEVEL_1X1 ); + vec4 currentLum = texture2D( currentLum, vUv, MIP_LEVEL_1X1 ); + + float fLastLum = max( minLuminance, lastLum.r ); + float fCurrentLum = max( minLuminance, currentLum.r ); + + //The adaption seems to work better in extreme lighting differences + //if the input luminance is squared. + fCurrentLum *= fCurrentLum; + + // Adapt the luminance using Pattanaik's technique + float fAdaptedLum = fLastLum + (fCurrentLum - fLastLum) * (1.0 - exp(-delta * tau)); + // "fAdaptedLum = sqrt(fAdaptedLum); + gl_FragColor.r = fAdaptedLum; + }`, + } -AdaptiveToneMappingPass.prototype = Object.assign(Object.create(Pass.prototype), { - constructor: AdaptiveToneMappingPass, + this.materialAdaptiveLum = new ShaderMaterial({ + uniforms: UniformsUtils.clone(this.adaptLuminanceShader.uniforms), + vertexShader: this.adaptLuminanceShader.vertexShader, + fragmentShader: this.adaptLuminanceShader.fragmentShader, + defines: Object.assign({}, this.adaptLuminanceShader.defines), + blending: NoBlending, + }) + + this.materialToneMap = new ShaderMaterial({ + uniforms: UniformsUtils.clone(ToneMapShader.uniforms), + vertexShader: ToneMapShader.vertexShader, + fragmentShader: ToneMapShader.fragmentShader, + blending: NoBlending, + }) + + this.fsQuad = new FullScreenQuad(null) + } - render: function (renderer, writeBuffer, readBuffer, deltaTime /*, maskActive*/) { + render(renderer, writeBuffer, readBuffer, deltaTime /*, maskActive*/) { if (this.needsInit) { this.reset(renderer) @@ -172,9 +161,9 @@ AdaptiveToneMappingPass.prototype = Object.assign(Object.create(Pass.prototype), this.fsQuad.render(renderer) } - }, + } - reset: function () { + reset() { // render targets if (this.luminanceRT) { this.luminanceRT.dispose() @@ -188,23 +177,18 @@ AdaptiveToneMappingPass.prototype = Object.assign(Object.create(Pass.prototype), this.previousLuminanceRT.dispose() } - var pars = { - minFilter: LinearFilter, - magFilter: LinearFilter, - format: RGBAFormat, - } // was RGB format. changed to RGBA format. see discussion in #8415 / #8450 - - this.luminanceRT = new WebGLRenderTarget(this.resolution, this.resolution, pars) + this.luminanceRT = new WebGLRenderTarget(this.resolution, this.resolution) this.luminanceRT.texture.name = 'AdaptiveToneMappingPass.l' this.luminanceRT.texture.generateMipmaps = false - this.previousLuminanceRT = new WebGLRenderTarget(this.resolution, this.resolution, pars) + this.previousLuminanceRT = new WebGLRenderTarget(this.resolution, this.resolution) this.previousLuminanceRT.texture.name = 'AdaptiveToneMappingPass.pl' this.previousLuminanceRT.texture.generateMipmaps = false // We only need mipmapping for the current luminosity because we want a down-sampled version to sample in our adaptive shader - pars.minFilter = LinearMipmapLinearFilter - pars.generateMipmaps = true + + const pars = { minFilter: LinearMipmapLinearFilter, generateMipmaps: true } + this.currentLuminanceRT = new WebGLRenderTarget(this.resolution, this.resolution, pars) this.currentLuminanceRT.texture.name = 'AdaptiveToneMappingPass.cl' @@ -221,9 +205,9 @@ AdaptiveToneMappingPass.prototype = Object.assign(Object.create(Pass.prototype), // renderer.render( this.scene, this.camera, this.luminanceRT ); // renderer.render( this.scene, this.camera, this.previousLuminanceRT ); // renderer.render( this.scene, this.camera, this.currentLuminanceRT ); - }, + } - setAdaptive: function (adaptive) { + setAdaptive(adaptive) { if (adaptive) { this.adaptive = true this.materialToneMap.defines['ADAPTED_LUMINANCE'] = '' @@ -235,40 +219,40 @@ AdaptiveToneMappingPass.prototype = Object.assign(Object.create(Pass.prototype), } this.materialToneMap.needsUpdate = true - }, + } - setAdaptionRate: function (rate) { + setAdaptionRate(rate) { if (rate) { this.materialAdaptiveLum.uniforms.tau.value = Math.abs(rate) } - }, + } - setMinLuminance: function (minLum) { + setMinLuminance(minLum) { if (minLum) { this.materialToneMap.uniforms.minLuminance.value = minLum this.materialAdaptiveLum.uniforms.minLuminance.value = minLum } - }, + } - setMaxLuminance: function (maxLum) { + setMaxLuminance(maxLum) { if (maxLum) { this.materialToneMap.uniforms.maxLuminance.value = maxLum } - }, + } - setAverageLuminance: function (avgLum) { + setAverageLuminance(avgLum) { if (avgLum) { this.materialToneMap.uniforms.averageLuminance.value = avgLum } - }, + } - setMiddleGrey: function (middleGrey) { + setMiddleGrey(middleGrey) { if (middleGrey) { this.materialToneMap.uniforms.middleGrey.value = middleGrey } - }, + } - dispose: function () { + dispose() { if (this.luminanceRT) { this.luminanceRT.dispose() } @@ -296,7 +280,7 @@ AdaptiveToneMappingPass.prototype = Object.assign(Object.create(Pass.prototype), if (this.materialToneMap) { this.materialToneMap.dispose() } - }, -}) + } +} export { AdaptiveToneMappingPass } diff --git a/src/postprocessing/ClearPass.ts b/src/postprocessing/ClearPass.ts index 2d008e9d..919ca933 100644 --- a/src/postprocessing/ClearPass.ts +++ b/src/postprocessing/ClearPass.ts @@ -1,13 +1,13 @@ import { Pass } from './Pass' -import { Color, ColorRepresentation, WebGLRenderer, WebGLRenderTarget } from 'three' +import { Color, WebGLRenderer, WebGLRenderTarget } from 'three' class ClearPass extends Pass { - public clearColor: ColorRepresentation + public clearColor: Color | string | number public clearAlpha: number private _oldClearColor: Color - constructor(clearColor?: ColorRepresentation, clearAlpha?: number) { + constructor(clearColor?: Color | string | number, clearAlpha?: number) { super() this.needsSwap = false this.clearColor = clearColor !== undefined ? clearColor : 0x000000 diff --git a/src/postprocessing/CubeTexturePass.js b/src/postprocessing/CubeTexturePass.js index 883e7598..4deaaa89 100644 --- a/src/postprocessing/CubeTexturePass.js +++ b/src/postprocessing/CubeTexturePass.js @@ -1,51 +1,51 @@ import { BackSide, BoxGeometry, Mesh, PerspectiveCamera, Scene, ShaderLib, ShaderMaterial, UniformsUtils } from 'three' -import { Pass } from '../postprocessing/Pass' - -var CubeTexturePass = function (camera, envMap, opacity) { - this.camera = camera - - this.needsSwap = false - - this.cubeShader = ShaderLib['cube'] - this.cubeMesh = new Mesh( - new BoxGeometry(10, 10, 10), - new ShaderMaterial({ - uniforms: UniformsUtils.clone(this.cubeShader.uniforms), - vertexShader: this.cubeShader.vertexShader, - fragmentShader: this.cubeShader.fragmentShader, - depthTest: false, - depthWrite: false, - side: BackSide, - }), - ) - - Object.defineProperty(this.cubeMesh.material, 'envMap', { - get: function () { - return this.uniforms.envMap.value - }, - }) - - this.envMap = envMap - this.opacity = opacity !== undefined ? opacity : 1.0 - - this.cubeScene = new Scene() - this.cubeCamera = new PerspectiveCamera() - this.cubeScene.add(this.cubeMesh) -} +import { Pass } from './Pass' + +class CubeTexturePass extends Pass { + constructor(camera, tCube, opacity = 1) { + super() + + this.camera = camera + + this.needsSwap = false + + this.cubeShader = ShaderLib['cube'] + this.cubeMesh = new Mesh( + new BoxGeometry(10, 10, 10), + new ShaderMaterial({ + uniforms: UniformsUtils.clone(this.cubeShader.uniforms), + vertexShader: this.cubeShader.vertexShader, + fragmentShader: this.cubeShader.fragmentShader, + depthTest: false, + depthWrite: false, + side: BackSide, + }), + ) -CubeTexturePass.prototype = Object.assign(Object.create(Pass.prototype), { - constructor: CubeTexturePass, + Object.defineProperty(this.cubeMesh.material, 'envMap', { + get: function () { + return this.uniforms.tCube.value + }, + }) - render: function (renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { - var oldAutoClear = renderer.autoClear + this.tCube = tCube + this.opacity = opacity + + this.cubeScene = new Scene() + this.cubeCamera = new PerspectiveCamera() + this.cubeScene.add(this.cubeMesh) + } + + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { + const oldAutoClear = renderer.autoClear renderer.autoClear = false this.cubeCamera.projectionMatrix.copy(this.camera.projectionMatrix) this.cubeCamera.quaternion.setFromRotationMatrix(this.camera.matrixWorld) - this.cubeMesh.material.uniforms.envMap.value = this.envMap - this.cubeMesh.material.uniforms.flipEnvMap.value = - this.envMap.isCubeTexture && this.envMap._needsFlipEnvMap ? -1 : 1 + this.cubeMesh.material.uniforms.tCube.value = this.tCube + this.cubeMesh.material.uniforms.tFlip.value = + this.tCube.isCubeTexture && this.tCube.isRenderTargetTexture === false ? -1 : 1 this.cubeMesh.material.uniforms.opacity.value = this.opacity this.cubeMesh.material.transparent = this.opacity < 1.0 @@ -54,7 +54,12 @@ CubeTexturePass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.render(this.cubeScene, this.cubeCamera) renderer.autoClear = oldAutoClear - }, -}) + } + + dispose() { + this.cubeMesh.geometry.dispose() + this.cubeMesh.material.dispose() + } +} export { CubeTexturePass } diff --git a/src/postprocessing/LUTPass.d.ts b/src/postprocessing/LUTPass.d.ts index ce2bd632..00f9b70f 100644 --- a/src/postprocessing/LUTPass.d.ts +++ b/src/postprocessing/LUTPass.d.ts @@ -1,13 +1,13 @@ -import { DataTexture, Data3DTexture } from 'three' +import { DataTexture, Texture } from 'three' import { ShaderPass } from './ShaderPass' export interface LUTPassParameters { - lut?: DataTexture | Data3DTexture + lut?: DataTexture | Texture // Data3DTexture intensity?: number } export class LUTPass extends ShaderPass { - lut?: DataTexture | Data3DTexture + lut?: DataTexture | Texture // Data3DTexture intensity?: number constructor(params: LUTPassParameters) } diff --git a/src/postprocessing/RenderPixelatedPass.d.ts b/src/postprocessing/RenderPixelatedPass.d.ts new file mode 100644 index 00000000..45ce00bf --- /dev/null +++ b/src/postprocessing/RenderPixelatedPass.d.ts @@ -0,0 +1,28 @@ +import { Scene, Camera, ShaderMaterial, Vector2, MeshNormalMaterial, WebGLRenderTarget } from 'three' + +import { Pass, FullScreenQuad } from './Pass' + +export interface RenderPixelatedPassParameters { + normalEdgeStrength?: number + depthEdgeStrength?: number +} + +export class RenderPixelatedPass extends Pass { + constructor(pixelSize: number, scene: Scene, camera: Camera, options?: RenderPixelatedPassParameters) + pixelSize: number + resolution: Vector2 + renderResolution: Vector2 + + pixelatedMaterial: ShaderMaterial + normalMaterial: MeshNormalMaterial + + fsQuad: FullScreenQuad + scene: Scene + camera: Camera + + normalEdgeStrength: RenderPixelatedPassParameters['normalEdgeStrength'] + depthEdgeStrength: RenderPixelatedPassParameters['depthEdgeStrength'] + + beautyRenderTarget: WebGLRenderTarget + normalRenderTarget: WebGLRenderTarget +} diff --git a/src/postprocessing/SAOPass.d.ts b/src/postprocessing/SAOPass.d.ts index 80ef8254..0502b2fc 100644 --- a/src/postprocessing/SAOPass.d.ts +++ b/src/postprocessing/SAOPass.d.ts @@ -9,10 +9,9 @@ import { Vector2, WebGLRenderer, WebGLRenderTarget, - ColorRepresentation, } from 'three' -import { Pass } from './Pass' +import { Pass, FullScreenQuad } from './Pass' export enum OUTPUT { Beauty, @@ -57,23 +56,23 @@ export class SAOPass extends Pass { hBlurMaterial: ShaderMaterial materialCopy: ShaderMaterial depthCopy: ShaderMaterial - fsQuad: object + fsQuad: FullScreenQuad params: SAOPassParams - static OUTPUT: OUTPUT + static OUTPUT: typeof OUTPUT renderPass( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, - clearColor?: ColorRepresentation, + clearColor?: Color | string | number, clearAlpha?: number, ): void renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, - clearColor?: ColorRepresentation, + clearColor?: Color | string | number, clearAlpha?: number, ): void } diff --git a/src/postprocessing/SAOPass.js b/src/postprocessing/SAOPass.js index cc822315..6125d3b8 100644 --- a/src/postprocessing/SAOPass.js +++ b/src/postprocessing/SAOPass.js @@ -5,13 +5,12 @@ import { DepthTexture, DstAlphaFactor, DstColorFactor, - LinearFilter, + HalfFloatType, MeshDepthMaterial, MeshNormalMaterial, NearestFilter, NoBlending, RGBADepthPacking, - RGBAFormat, ShaderMaterial, UniformsUtils, UnsignedShortType, @@ -19,179 +18,161 @@ import { WebGLRenderTarget, ZeroFactor, } from 'three' -import { Pass, FullScreenQuad } from '../postprocessing/Pass' +import { Pass, FullScreenQuad } from './Pass' import { SAOShader } from '../shaders/SAOShader' -import { DepthLimitedBlurShader, BlurShaderUtils } from '../shaders/DepthLimitedBlurShader' +import { DepthLimitedBlurShader } from '../shaders/DepthLimitedBlurShader' +import { BlurShaderUtils } from '../shaders/DepthLimitedBlurShader' import { CopyShader } from '../shaders/CopyShader' import { UnpackDepthRGBAShader } from '../shaders/UnpackDepthRGBAShader' /** * SAO implementation inspired from bhouston previous SAO work */ - -var SAOPass = function (scene, camera, depthTexture, useNormals, resolution) { - this.scene = scene - this.camera = camera - - this.clear = true - this.needsSwap = false - - this.supportsDepthTextureExtension = depthTexture !== undefined ? depthTexture : false - this.supportsNormalTexture = useNormals !== undefined ? useNormals : false - - this.originalClearColor = new Color() - this._oldClearColor = new Color() - this.oldClearAlpha = 1 - - this.params = { - output: 0, - saoBias: 0.5, - saoIntensity: 0.18, - saoScale: 1, - saoKernelRadius: 100, - saoMinResolution: 0, - saoBlur: true, - saoBlurRadius: 8, - saoBlurStdDev: 4, - saoBlurDepthCutoff: 0.01, - } - - this.resolution = resolution !== undefined ? new Vector2(resolution.x, resolution.y) : new Vector2(256, 256) - - this.saoRenderTarget = new WebGLRenderTarget(this.resolution.x, this.resolution.y, { - minFilter: LinearFilter, - magFilter: LinearFilter, - format: RGBAFormat, - }) - this.blurIntermediateRenderTarget = this.saoRenderTarget.clone() - this.beautyRenderTarget = this.saoRenderTarget.clone() - - this.normalRenderTarget = new WebGLRenderTarget(this.resolution.x, this.resolution.y, { - minFilter: NearestFilter, - magFilter: NearestFilter, - format: RGBAFormat, - }) - this.depthRenderTarget = this.normalRenderTarget.clone() - - if (this.supportsDepthTextureExtension) { - var depthTexture = new DepthTexture() - depthTexture.type = UnsignedShortType - - this.beautyRenderTarget.depthTexture = depthTexture - this.beautyRenderTarget.depthBuffer = true +class SAOPass extends Pass { + static OUTPUT = { + Beauty: 1, + Default: 0, + SAO: 2, + Depth: 3, + Normal: 4, } - this.depthMaterial = new MeshDepthMaterial() - this.depthMaterial.depthPacking = RGBADepthPacking - this.depthMaterial.blending = NoBlending - - this.normalMaterial = new MeshNormalMaterial() - this.normalMaterial.blending = NoBlending - - if (SAOShader === undefined) { - console.error('THREE.SAOPass relies on SAOShader') - } + constructor(scene, camera, useDepthTexture = false, useNormals = false, resolution = new Vector2(256, 256)) { + super() + + this.scene = scene + this.camera = camera + + this.clear = true + this.needsSwap = false + + this.supportsDepthTextureExtension = useDepthTexture + this.supportsNormalTexture = useNormals + + this.originalClearColor = new Color() + this._oldClearColor = new Color() + this.oldClearAlpha = 1 + + this.params = { + output: 0, + saoBias: 0.5, + saoIntensity: 0.18, + saoScale: 1, + saoKernelRadius: 100, + saoMinResolution: 0, + saoBlur: true, + saoBlurRadius: 8, + saoBlurStdDev: 4, + saoBlurDepthCutoff: 0.01, + } - this.saoMaterial = new ShaderMaterial({ - defines: Object.assign({}, SAOShader.defines), - fragmentShader: SAOShader.fragmentShader, - vertexShader: SAOShader.vertexShader, - uniforms: UniformsUtils.clone(SAOShader.uniforms), - }) - this.saoMaterial.extensions.derivatives = true - this.saoMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension ? 0 : 1 - this.saoMaterial.defines['NORMAL_TEXTURE'] = this.supportsNormalTexture ? 1 : 0 - this.saoMaterial.defines['PERSPECTIVE_CAMERA'] = this.camera.isPerspectiveCamera ? 1 : 0 - this.saoMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension - ? depthTexture - : this.depthRenderTarget.texture - this.saoMaterial.uniforms['tNormal'].value = this.normalRenderTarget.texture - this.saoMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) - this.saoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy(this.camera.projectionMatrixInverse) - this.saoMaterial.uniforms['cameraProjectionMatrix'].value = this.camera.projectionMatrix - this.saoMaterial.blending = NoBlending - - if (DepthLimitedBlurShader === undefined) { - console.error('THREE.SAOPass relies on DepthLimitedBlurShader') - } + this.resolution = new Vector2(resolution.x, resolution.y) - this.vBlurMaterial = new ShaderMaterial({ - uniforms: UniformsUtils.clone(DepthLimitedBlurShader.uniforms), - defines: Object.assign({}, DepthLimitedBlurShader.defines), - vertexShader: DepthLimitedBlurShader.vertexShader, - fragmentShader: DepthLimitedBlurShader.fragmentShader, - }) - this.vBlurMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension ? 0 : 1 - this.vBlurMaterial.defines['PERSPECTIVE_CAMERA'] = this.camera.isPerspectiveCamera ? 1 : 0 - this.vBlurMaterial.uniforms['tDiffuse'].value = this.saoRenderTarget.texture - this.vBlurMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension - ? depthTexture - : this.depthRenderTarget.texture - this.vBlurMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) - this.vBlurMaterial.blending = NoBlending - - this.hBlurMaterial = new ShaderMaterial({ - uniforms: UniformsUtils.clone(DepthLimitedBlurShader.uniforms), - defines: Object.assign({}, DepthLimitedBlurShader.defines), - vertexShader: DepthLimitedBlurShader.vertexShader, - fragmentShader: DepthLimitedBlurShader.fragmentShader, - }) - this.hBlurMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension ? 0 : 1 - this.hBlurMaterial.defines['PERSPECTIVE_CAMERA'] = this.camera.isPerspectiveCamera ? 1 : 0 - this.hBlurMaterial.uniforms['tDiffuse'].value = this.blurIntermediateRenderTarget.texture - this.hBlurMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension - ? depthTexture - : this.depthRenderTarget.texture - this.hBlurMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) - this.hBlurMaterial.blending = NoBlending - - if (CopyShader === undefined) { - console.error('THREE.SAOPass relies on CopyShader') - } + this.saoRenderTarget = new WebGLRenderTarget(this.resolution.x, this.resolution.y, { type: HalfFloatType }) + this.blurIntermediateRenderTarget = this.saoRenderTarget.clone() + this.beautyRenderTarget = this.saoRenderTarget.clone() - this.materialCopy = new ShaderMaterial({ - uniforms: UniformsUtils.clone(CopyShader.uniforms), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - blending: NoBlending, - }) - this.materialCopy.transparent = true - this.materialCopy.depthTest = false - this.materialCopy.depthWrite = false - this.materialCopy.blending = CustomBlending - this.materialCopy.blendSrc = DstColorFactor - this.materialCopy.blendDst = ZeroFactor - this.materialCopy.blendEquation = AddEquation - this.materialCopy.blendSrcAlpha = DstAlphaFactor - this.materialCopy.blendDstAlpha = ZeroFactor - this.materialCopy.blendEquationAlpha = AddEquation - - if (UnpackDepthRGBAShader === undefined) { - console.error('THREE.SAOPass relies on UnpackDepthRGBAShader') - } + this.normalRenderTarget = new WebGLRenderTarget(this.resolution.x, this.resolution.y, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + }) + this.depthRenderTarget = this.normalRenderTarget.clone() - this.depthCopy = new ShaderMaterial({ - uniforms: UniformsUtils.clone(UnpackDepthRGBAShader.uniforms), - vertexShader: UnpackDepthRGBAShader.vertexShader, - fragmentShader: UnpackDepthRGBAShader.fragmentShader, - blending: NoBlending, - }) + let depthTexture - this.fsQuad = new FullScreenQuad(null) -} + if (this.supportsDepthTextureExtension) { + depthTexture = new DepthTexture() + depthTexture.type = UnsignedShortType -SAOPass.OUTPUT = { - Beauty: 1, - Default: 0, - SAO: 2, - Depth: 3, - Normal: 4, -} + this.beautyRenderTarget.depthTexture = depthTexture + this.beautyRenderTarget.depthBuffer = true + } -SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { - constructor: SAOPass, + this.depthMaterial = new MeshDepthMaterial() + this.depthMaterial.depthPacking = RGBADepthPacking + this.depthMaterial.blending = NoBlending + + this.normalMaterial = new MeshNormalMaterial() + this.normalMaterial.blending = NoBlending + + this.saoMaterial = new ShaderMaterial({ + defines: Object.assign({}, SAOShader.defines), + fragmentShader: SAOShader.fragmentShader, + vertexShader: SAOShader.vertexShader, + uniforms: UniformsUtils.clone(SAOShader.uniforms), + }) + this.saoMaterial.extensions.derivatives = true + this.saoMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension ? 0 : 1 + this.saoMaterial.defines['NORMAL_TEXTURE'] = this.supportsNormalTexture ? 1 : 0 + this.saoMaterial.defines['PERSPECTIVE_CAMERA'] = this.camera.isPerspectiveCamera ? 1 : 0 + this.saoMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension + ? depthTexture + : this.depthRenderTarget.texture + this.saoMaterial.uniforms['tNormal'].value = this.normalRenderTarget.texture + this.saoMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) + this.saoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy(this.camera.projectionMatrixInverse) + this.saoMaterial.uniforms['cameraProjectionMatrix'].value = this.camera.projectionMatrix + this.saoMaterial.blending = NoBlending + + this.vBlurMaterial = new ShaderMaterial({ + uniforms: UniformsUtils.clone(DepthLimitedBlurShader.uniforms), + defines: Object.assign({}, DepthLimitedBlurShader.defines), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader, + }) + this.vBlurMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension ? 0 : 1 + this.vBlurMaterial.defines['PERSPECTIVE_CAMERA'] = this.camera.isPerspectiveCamera ? 1 : 0 + this.vBlurMaterial.uniforms['tDiffuse'].value = this.saoRenderTarget.texture + this.vBlurMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension + ? depthTexture + : this.depthRenderTarget.texture + this.vBlurMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) + this.vBlurMaterial.blending = NoBlending + + this.hBlurMaterial = new ShaderMaterial({ + uniforms: UniformsUtils.clone(DepthLimitedBlurShader.uniforms), + defines: Object.assign({}, DepthLimitedBlurShader.defines), + vertexShader: DepthLimitedBlurShader.vertexShader, + fragmentShader: DepthLimitedBlurShader.fragmentShader, + }) + this.hBlurMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension ? 0 : 1 + this.hBlurMaterial.defines['PERSPECTIVE_CAMERA'] = this.camera.isPerspectiveCamera ? 1 : 0 + this.hBlurMaterial.uniforms['tDiffuse'].value = this.blurIntermediateRenderTarget.texture + this.hBlurMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension + ? depthTexture + : this.depthRenderTarget.texture + this.hBlurMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) + this.hBlurMaterial.blending = NoBlending + + this.materialCopy = new ShaderMaterial({ + uniforms: UniformsUtils.clone(CopyShader.uniforms), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + blending: NoBlending, + }) + this.materialCopy.transparent = true + this.materialCopy.depthTest = false + this.materialCopy.depthWrite = false + this.materialCopy.blending = CustomBlending + this.materialCopy.blendSrc = DstColorFactor + this.materialCopy.blendDst = ZeroFactor + this.materialCopy.blendEquation = AddEquation + this.materialCopy.blendSrcAlpha = DstAlphaFactor + this.materialCopy.blendDstAlpha = ZeroFactor + this.materialCopy.blendEquationAlpha = AddEquation + + this.depthCopy = new ShaderMaterial({ + uniforms: UniformsUtils.clone(UnpackDepthRGBAShader.uniforms), + vertexShader: UnpackDepthRGBAShader.vertexShader, + fragmentShader: UnpackDepthRGBAShader.fragmentShader, + blending: NoBlending, + }) + + this.fsQuad = new FullScreenQuad(null) + } - render: function (renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { // Rendering readBuffer first when rendering to screen if (this.renderToScreen) { this.materialCopy.blending = NoBlending @@ -206,7 +187,7 @@ SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.getClearColor(this._oldClearColor) this.oldClearAlpha = renderer.getClearAlpha() - var oldAutoClear = renderer.autoClear + const oldAutoClear = renderer.autoClear renderer.autoClear = false renderer.setRenderTarget(this.depthRenderTarget) @@ -221,7 +202,7 @@ SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { this.saoMaterial.uniforms['cameraFar'].value = this.camera.far // this.saoMaterial.uniforms['randomSeed'].value = Math.random(); - var depthCutoff = this.params.saoBlurDepthCutoff * (this.camera.far - this.camera.near) + const depthCutoff = this.params.saoBlurDepthCutoff * (this.camera.far - this.camera.near) this.vBlurMaterial.uniforms['depthCutoff'].value = depthCutoff this.hBlurMaterial.uniforms['depthCutoff'].value = depthCutoff @@ -274,7 +255,7 @@ SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { this.renderPass(renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0) } - var outputMaterial = this.materialCopy + let outputMaterial = this.materialCopy // Setting up SAO rendering if (this.params.output === 3) { if (this.supportsDepthTextureExtension) { @@ -305,13 +286,13 @@ SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.setClearColor(this._oldClearColor, this.oldClearAlpha) renderer.autoClear = oldAutoClear - }, + } - renderPass: function (renderer, passMaterial, renderTarget, clearColor, clearAlpha) { + renderPass(renderer, passMaterial, renderTarget, clearColor, clearAlpha) { // save original state renderer.getClearColor(this.originalClearColor) - var originalClearAlpha = renderer.getClearAlpha() - var originalAutoClear = renderer.autoClear + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear renderer.setRenderTarget(renderTarget) @@ -330,12 +311,12 @@ SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.autoClear = originalAutoClear renderer.setClearColor(this.originalClearColor) renderer.setClearAlpha(originalClearAlpha) - }, + } - renderOverride: function (renderer, overrideMaterial, renderTarget, clearColor, clearAlpha) { + renderOverride(renderer, overrideMaterial, renderTarget, clearColor, clearAlpha) { renderer.getClearColor(this.originalClearColor) - var originalClearAlpha = renderer.getClearAlpha() - var originalAutoClear = renderer.autoClear + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear renderer.setRenderTarget(renderTarget) renderer.autoClear = false @@ -356,9 +337,9 @@ SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.autoClear = originalAutoClear renderer.setClearColor(this.originalClearColor) renderer.setClearAlpha(originalClearAlpha) - }, + } - setSize: function (width, height) { + setSize(width, height) { this.beautyRenderTarget.setSize(width, height) this.saoRenderTarget.setSize(width, height) this.blurIntermediateRenderTarget.setSize(width, height) @@ -375,7 +356,25 @@ SAOPass.prototype = Object.assign(Object.create(Pass.prototype), { this.hBlurMaterial.uniforms['size'].value.set(width, height) this.hBlurMaterial.needsUpdate = true - }, -}) + } + + dispose() { + this.saoRenderTarget.dispose() + this.blurIntermediateRenderTarget.dispose() + this.beautyRenderTarget.dispose() + this.normalRenderTarget.dispose() + this.depthRenderTarget.dispose() + + this.depthMaterial.dispose() + this.normalMaterial.dispose() + this.saoMaterial.dispose() + this.vBlurMaterial.dispose() + this.hBlurMaterial.dispose() + this.materialCopy.dispose() + this.depthCopy.dispose() + + this.fsQuad.dispose() + } +} export { SAOPass } diff --git a/src/postprocessing/SSAARenderPass.d.ts b/src/postprocessing/SSAARenderPass.d.ts index 02532ca4..42616fb6 100644 --- a/src/postprocessing/SSAARenderPass.d.ts +++ b/src/postprocessing/SSAARenderPass.d.ts @@ -1,17 +1,17 @@ -import { Scene, Camera, ColorRepresentation, ShaderMaterial, WebGLRenderTarget } from 'three' +import { Scene, Camera, Color, ShaderMaterial, WebGLRenderTarget } from 'three' -import { Pass } from './Pass' +import { Pass, FullScreenQuad } from './Pass' export class SSAARenderPass extends Pass { - constructor(scene: Scene, camera: Camera, clearColor?: ColorRepresentation, clearAlpha?: number) + constructor(scene: Scene, camera: Camera, clearColor?: Color | string | number, clearAlpha?: number) scene: Scene camera: Camera sampleLevel: number unbiased: boolean - clearColor: ColorRepresentation + clearColor: Color | string | number clearAlpha: number copyUniforms: object copyMaterial: ShaderMaterial - fsQuad: object + fsQuad: FullScreenQuad sampleRenderTarget: undefined | WebGLRenderTarget } diff --git a/src/postprocessing/SSAARenderPass.js b/src/postprocessing/SSAARenderPass.js index dec46823..744db7b3 100644 --- a/src/postprocessing/SSAARenderPass.js +++ b/src/postprocessing/SSAARenderPass.js @@ -1,13 +1,15 @@ import { - AdditiveBlending, + CustomBlending, + OneFactor, + AddEquation, + SrcAlphaFactor, Color, - LinearFilter, - RGBAFormat, + HalfFloatType, ShaderMaterial, UniformsUtils, WebGLRenderTarget, } from 'three' -import { Pass, FullScreenQuad } from '../postprocessing/Pass' +import { Pass, FullScreenQuad } from './Pass' import { CopyShader } from '../shaders/CopyShader' /** @@ -20,99 +22,115 @@ import { CopyShader } from '../shaders/CopyShader' * */ -var SSAARenderPass = function (scene, camera, clearColor, clearAlpha) { - this.scene = scene - this.camera = camera - - this.sampleLevel = 4 // specified as n, where the number of samples is 2^n, so sampleLevel = 4, is 2^4 samples, 16. - this.unbiased = true - - // as we need to clear the buffer in this pass, clearColor must be set to something, defaults to black. - this.clearColor = clearColor !== undefined ? clearColor : 0x000000 - this.clearAlpha = clearAlpha !== undefined ? clearAlpha : 0 - this._oldClearColor = new Color() - - if (CopyShader === undefined) console.error('THREE.SSAARenderPass relies on CopyShader') - - var copyShader = CopyShader - this.copyUniforms = UniformsUtils.clone(copyShader.uniforms) - - this.copyMaterial = new ShaderMaterial({ - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - premultipliedAlpha: true, - transparent: true, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - }) - - this.fsQuad = new FullScreenQuad(this.copyMaterial) -} - -SSAARenderPass.prototype = Object.assign(Object.create(Pass.prototype), { - constructor: SSAARenderPass, - - dispose: function () { +class SSAARenderPass extends Pass { + constructor(scene, camera, clearColor, clearAlpha) { + super() + + this.scene = scene + this.camera = camera + + this.sampleLevel = 4 // specified as n, where the number of samples is 2^n, so sampleLevel = 4, is 2^4 samples, 16. + this.unbiased = true + + // as we need to clear the buffer in this pass, clearColor must be set to something, defaults to black. + this.clearColor = clearColor !== undefined ? clearColor : 0x000000 + this.clearAlpha = clearAlpha !== undefined ? clearAlpha : 0 + this._oldClearColor = new Color() + + const copyShader = CopyShader + this.copyUniforms = UniformsUtils.clone(copyShader.uniforms) + + this.copyMaterial = new ShaderMaterial({ + uniforms: this.copyUniforms, + vertexShader: copyShader.vertexShader, + fragmentShader: copyShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + + // do not use AdditiveBlending because it mixes the alpha channel instead of adding + blending: CustomBlending, + blendEquation: AddEquation, + blendDst: OneFactor, + blendDstAlpha: OneFactor, + blendSrc: SrcAlphaFactor, + blendSrcAlpha: OneFactor, + }) + + this.fsQuad = new FullScreenQuad(this.copyMaterial) + } + + dispose() { if (this.sampleRenderTarget) { this.sampleRenderTarget.dispose() this.sampleRenderTarget = null } - }, - setSize: function (width, height) { + this.copyMaterial.dispose() + + this.fsQuad.dispose() + } + + setSize(width, height) { if (this.sampleRenderTarget) this.sampleRenderTarget.setSize(width, height) - }, + } - render: function (renderer, writeBuffer, readBuffer) { + render(renderer, writeBuffer, readBuffer) { if (!this.sampleRenderTarget) { - this.sampleRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, { - minFilter: LinearFilter, - magFilter: LinearFilter, - format: RGBAFormat, - }) + this.sampleRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, { type: HalfFloatType }) this.sampleRenderTarget.texture.name = 'SSAARenderPass.sample' } - var jitterOffsets = SSAARenderPass.JitterVectors[Math.max(0, Math.min(this.sampleLevel, 5))] + const jitterOffsets = _JitterVectors[Math.max(0, Math.min(this.sampleLevel, 5))] - var autoClear = renderer.autoClear + const autoClear = renderer.autoClear renderer.autoClear = false renderer.getClearColor(this._oldClearColor) - var oldClearAlpha = renderer.getClearAlpha() + const oldClearAlpha = renderer.getClearAlpha() - var baseSampleWeight = 1.0 / jitterOffsets.length - var roundingRange = 1 / 32 + const baseSampleWeight = 1.0 / jitterOffsets.length + const roundingRange = 1 / 32 this.copyUniforms['tDiffuse'].value = this.sampleRenderTarget.texture - var width = readBuffer.width, - height = readBuffer.height + const viewOffset = { + fullWidth: readBuffer.width, + fullHeight: readBuffer.height, + offsetX: 0, + offsetY: 0, + width: readBuffer.width, + height: readBuffer.height, + } + + const originalViewOffset = Object.assign({}, this.camera.view) + + if (originalViewOffset.enabled) Object.assign(viewOffset, originalViewOffset) // render the scene multiple times, each slightly jitter offset from the last and accumulate the results. for (let i = 0; i < jitterOffsets.length; i++) { - var jitterOffset = jitterOffsets[i] + const jitterOffset = jitterOffsets[i] if (this.camera.setViewOffset) { this.camera.setViewOffset( - width, - height, - jitterOffset[0] * 0.0625, - jitterOffset[1] * 0.0625, // 0.0625 = 1 / 16 - width, - height, + viewOffset.fullWidth, + viewOffset.fullHeight, + + viewOffset.offsetX + jitterOffset[0] * 0.0625, + viewOffset.offsetY + jitterOffset[1] * 0.0625, // 0.0625 = 1 / 16 + + viewOffset.width, + viewOffset.height, ) } - var sampleWeight = baseSampleWeight + let sampleWeight = baseSampleWeight if (this.unbiased) { // the theory is that equal weights for each sample lead to an accumulation of rounding errors. // The following equation varies the sampleWeight per sample so that it is uniformly distributed // across a range of values whose rounding errors cancel each other out. - var uniformCenteredDistribution = -0.5 + (i + 0.5) / jitterOffsets.length + const uniformCenteredDistribution = -0.5 + (i + 0.5) / jitterOffsets.length sampleWeight += roundingRange * uniformCenteredDistribution } @@ -132,92 +150,62 @@ SSAARenderPass.prototype = Object.assign(Object.create(Pass.prototype), { this.fsQuad.render(renderer) } - if (this.camera.clearViewOffset) this.camera.clearViewOffset() + if (this.camera.setViewOffset && originalViewOffset.enabled) { + this.camera.setViewOffset( + originalViewOffset.fullWidth, + originalViewOffset.fullHeight, + + originalViewOffset.offsetX, + originalViewOffset.offsetY, + + originalViewOffset.width, + originalViewOffset.height, + ) + } else if (this.camera.clearViewOffset) { + this.camera.clearViewOffset() + } renderer.autoClear = autoClear renderer.setClearColor(this._oldClearColor, oldClearAlpha) - }, -}) + } +} // These jitter vectors are specified in integers because it is easier. // I am assuming a [-8,8) integer grid, but it needs to be mapped onto [-0.5,0.5) // before being used, thus these integers need to be scaled by 1/16. // // Sample patterns reference: https://msdn.microsoft.com/en-us/library/windows/desktop/ff476218%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 -SSAARenderPass.JitterVectors = [ - [[0, 0]], - [ - [4, 4], - [-4, -4], - ], - [ - [-2, -6], - [6, -2], - [-6, 2], - [2, 6], - ], - [ - [1, -3], - [-1, 3], - [5, 1], - [-3, -5], - [-5, 5], - [-7, -1], - [3, 7], - [7, -7], - ], - [ - [1, 1], - [-1, -3], - [-3, 2], - [4, -1], - [-5, -2], - [2, 5], - [5, 3], - [3, -5], - [-2, 6], - [0, -7], - [-4, -6], - [-6, 4], - [-8, 0], - [7, -4], - [6, 7], - [-7, -8], - ], - [ - [-4, -7], - [-7, -5], - [-3, -5], - [-5, -4], - [-1, -4], - [-2, -2], - [-6, -1], - [-4, 0], - [-7, 1], - [-1, 2], - [-6, 3], - [-3, 3], - [-7, 6], - [-3, 6], - [-5, 7], - [-1, 7], - [5, -7], - [1, -6], - [6, -5], - [4, -4], - [2, -3], - [7, -2], - [1, -1], - [4, -1], - [2, 1], - [6, 2], - [0, 4], - [4, 4], - [2, 5], - [7, 5], - [5, 6], - [3, 7], - ], -] +// prettier-ignore +const _JitterVectors = [ + [ + [ 0, 0 ] + ], + [ + [ 4, 4 ], [ - 4, - 4 ] + ], + [ + [ - 2, - 6 ], [ 6, - 2 ], [ - 6, 2 ], [ 2, 6 ] + ], + [ + [ 1, - 3 ], [ - 1, 3 ], [ 5, 1 ], [ - 3, - 5 ], + [ - 5, 5 ], [ - 7, - 1 ], [ 3, 7 ], [ 7, - 7 ] + ], + [ + [ 1, 1 ], [ - 1, - 3 ], [ - 3, 2 ], [ 4, - 1 ], + [ - 5, - 2 ], [ 2, 5 ], [ 5, 3 ], [ 3, - 5 ], + [ - 2, 6 ], [ 0, - 7 ], [ - 4, - 6 ], [ - 6, 4 ], + [ - 8, 0 ], [ 7, - 4 ], [ 6, 7 ], [ - 7, - 8 ] + ], + [ + [ - 4, - 7 ], [ - 7, - 5 ], [ - 3, - 5 ], [ - 5, - 4 ], + [ - 1, - 4 ], [ - 2, - 2 ], [ - 6, - 1 ], [ - 4, 0 ], + [ - 7, 1 ], [ - 1, 2 ], [ - 6, 3 ], [ - 3, 3 ], + [ - 7, 6 ], [ - 3, 6 ], [ - 5, 7 ], [ - 1, 7 ], + [ 5, - 7 ], [ 1, - 6 ], [ 6, - 5 ], [ 4, - 4 ], + [ 2, - 3 ], [ 7, - 2 ], [ 1, - 1 ], [ 4, - 1 ], + [ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ], + [ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ] + ] +]; export { SSAARenderPass } diff --git a/src/postprocessing/SSAOPass.d.ts b/src/postprocessing/SSAOPass.d.ts index cf5fd35c..7f74a488 100644 --- a/src/postprocessing/SSAOPass.d.ts +++ b/src/postprocessing/SSAOPass.d.ts @@ -9,10 +9,9 @@ import { Vector3, WebGLRenderer, WebGLRenderTarget, - ColorRepresentation, } from 'three' -import { Pass } from './Pass' +import { Pass, FullScreenQuad } from './Pass' export enum SSAOPassOUTPUT { Default, @@ -46,7 +45,7 @@ export class SSAOPass extends Pass { blurMaterial: ShaderMaterial depthRenderMaterial: ShaderMaterial copyMaterial: ShaderMaterial - fsQuad: object + fsQuad: FullScreenQuad originalClearColor: Color static OUTPUT: SSAOPassOUTPUT @@ -58,14 +57,14 @@ export class SSAOPass extends Pass { renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, - clearColor?: ColorRepresentation, + clearColor?: Color | string | number, clearAlpha?: number, ): void renderOverride( renderer: WebGLRenderer, overrideMaterial: Material, renderTarget: WebGLRenderTarget, - clearColor?: ColorRepresentation, + clearColor?: Color | string | number, clearAlpha?: number, ): void } diff --git a/src/postprocessing/SSAOPass.js b/src/postprocessing/SSAOPass.js index 8a9bfb91..52a5d0ed 100644 --- a/src/postprocessing/SSAOPass.js +++ b/src/postprocessing/SSAOPass.js @@ -29,6 +29,15 @@ import { SSAOShader, SSAOBlurShader, SSAODepthShader } from '../shaders/SSAOShad import { CopyShader } from '../shaders/CopyShader' class SSAOPass extends Pass { + static OUTPUT = { + Default: 0, + SSAO: 1, + Blur: 2, + Beauty: 3, + Depth: 4, + Normal: 5, + } + constructor(scene, camera, width, height) { super() @@ -392,13 +401,4 @@ class SSAOPass extends Pass { } } -SSAOPass.OUTPUT = { - Default: 0, - SSAO: 1, - Blur: 2, - Beauty: 3, - Depth: 4, - Normal: 5, -} - export { SSAOPass } diff --git a/src/postprocessing/SSRPass.d.ts b/src/postprocessing/SSRPass.d.ts index 361089b3..683703a0 100644 --- a/src/postprocessing/SSRPass.d.ts +++ b/src/postprocessing/SSRPass.d.ts @@ -9,7 +9,6 @@ import { Camera, Mesh, Material, - ColorRepresentation, } from 'three' import { Pass, FullScreenQuad } from '../postprocessing/Pass' import { ReflectorForSSRPass } from '../objects/ReflectorForSSRPass' @@ -102,23 +101,23 @@ export class SSRPass extends Pass { renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, - clearColor: ColorRepresentation, - clearAlpha: ColorRepresentation, + clearColor: Color | string | number, + clearAlpha: Color | string | number, ) => void renderOverride: ( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, - clearColor: ColorRepresentation, - clearAlpha: ColorRepresentation, + clearColor: Color | string | number, + clearAlpha: Color | string | number, ) => void renderMetalness: ( renderer: WebGLRenderer, passMaterial: Material, renderTarget: WebGLRenderTarget, - clearColor: ColorRepresentation, - clearAlpha: ColorRepresentation, + clearColor: Color | string | number, + clearAlpha: Color | string | number, ) => void } diff --git a/src/postprocessing/SSRPass.js b/src/postprocessing/SSRPass.js index 15144fce..c46038c5 100644 --- a/src/postprocessing/SSRPass.js +++ b/src/postprocessing/SSRPass.js @@ -5,323 +5,295 @@ import { DepthTexture, SrcAlphaFactor, OneMinusSrcAlphaFactor, - LinearFilter, MeshNormalMaterial, MeshBasicMaterial, NearestFilter, NoBlending, - RGBAFormat, ShaderMaterial, UniformsUtils, UnsignedShortType, WebGLRenderTarget, HalfFloatType, } from 'three' -import { Pass, FullScreenQuad } from '../postprocessing/Pass' -import { SSRShader, SSRBlurShader, SSRDepthShader } from '../shaders/SSRShader' +import { Pass, FullScreenQuad } from './Pass' +import { SSRShader } from '../shaders/SSRShader' +import { SSRBlurShader } from '../shaders/SSRShader' +import { SSRDepthShader } from '../shaders/SSRShader' import { CopyShader } from '../shaders/CopyShader' -var SSRPass = function ({ - renderer, - scene, - camera, - width, - height, - selects, - encoding, - isPerspectiveCamera = true, - isBouncing = false, - morphTargets = false, - groundReflector, -}) { - this.width = width !== undefined ? width : 512 - this.height = height !== undefined ? height : 512 - - this.clear = true - - this.renderer = renderer - this.scene = scene - this.camera = camera - this.groundReflector = groundReflector - - this.opacity = SSRShader.uniforms.opacity.value - this.output = 0 - - this.maxDistance = SSRShader.uniforms.maxDistance.value - this.surfDist = SSRShader.uniforms.surfDist.value - - this.encoding = encoding - - this.tempColor = new Color() - - this._selects = selects - this.isSelective = Array.isArray(this._selects) - Object.defineProperty(this, 'selects', { - get() { - return this._selects - }, - set(val) { - if (this._selects === val) return - this._selects = val - if (Array.isArray(val)) { - this.isSelective = true - this.ssrMaterial.defines.isSelective = true +class SSRPass extends Pass { + static OUTPUT = { + Default: 0, + SSR: 1, + Beauty: 3, + Depth: 4, + Normal: 5, + Metalness: 7, + } + constructor({ renderer, scene, camera, width, height, selects, bouncing = false, groundReflector }) { + super() + + this.width = width !== undefined ? width : 512 + this.height = height !== undefined ? height : 512 + + this.clear = true + + this.renderer = renderer + this.scene = scene + this.camera = camera + this.groundReflector = groundReflector + + this.opacity = SSRShader.uniforms.opacity.value + this.output = 0 + + this.maxDistance = SSRShader.uniforms.maxDistance.value + this.thickness = SSRShader.uniforms.thickness.value + + this.tempColor = new Color() + + this._selects = selects + this.selective = Array.isArray(this._selects) + Object.defineProperty(this, 'selects', { + get() { + return this._selects + }, + set(val) { + if (this._selects === val) return + this._selects = val + if (Array.isArray(val)) { + this.selective = true + this.ssrMaterial.defines.SELECTIVE = true + this.ssrMaterial.needsUpdate = true + } else { + this.selective = false + this.ssrMaterial.defines.SELECTIVE = false + this.ssrMaterial.needsUpdate = true + } + }, + }) + + this._bouncing = bouncing + Object.defineProperty(this, 'bouncing', { + get() { + return this._bouncing + }, + set(val) { + if (this._bouncing === val) return + this._bouncing = val + if (val) { + this.ssrMaterial.uniforms['tDiffuse'].value = this.prevRenderTarget.texture + } else { + this.ssrMaterial.uniforms['tDiffuse'].value = this.beautyRenderTarget.texture + } + }, + }) + + this.blur = true + + this._distanceAttenuation = SSRShader.defines.DISTANCE_ATTENUATION + Object.defineProperty(this, 'distanceAttenuation', { + get() { + return this._distanceAttenuation + }, + set(val) { + if (this._distanceAttenuation === val) return + this._distanceAttenuation = val + this.ssrMaterial.defines.DISTANCE_ATTENUATION = val this.ssrMaterial.needsUpdate = true - } else { - this.isSelective = false - this.ssrMaterial.defines.isSelective = false + }, + }) + + this._fresnel = SSRShader.defines.FRESNEL + Object.defineProperty(this, 'fresnel', { + get() { + return this._fresnel + }, + set(val) { + if (this._fresnel === val) return + this._fresnel = val + this.ssrMaterial.defines.FRESNEL = val this.ssrMaterial.needsUpdate = true - } - }, - }) - - this._isBouncing = isBouncing ///todo: don't need defineProperty - Object.defineProperty(this, 'isBouncing', { - get() { - return this._isBouncing - }, - set(val) { - if (this._isBouncing === val) return - this._isBouncing = val - if (val) { - this.ssrMaterial.uniforms['tDiffuse'].value = this.prevRenderTarget.texture - } else { - this.ssrMaterial.uniforms['tDiffuse'].value = this.beautyRenderTarget.texture - } - }, - }) - - this.isBlur = true - - this._isDistanceAttenuation = SSRShader.defines.isDistanceAttenuation - Object.defineProperty(this, 'isDistanceAttenuation', { - get() { - return this._isDistanceAttenuation - }, - set(val) { - if (this._isDistanceAttenuation === val) return - this._isDistanceAttenuation = val - this.ssrMaterial.defines.isDistanceAttenuation = val - this.ssrMaterial.needsUpdate = true - }, - }) - - this._isFresnel = SSRShader.defines.isFresnel - Object.defineProperty(this, 'isFresnel', { - get() { - return this._isFresnel - }, - set(val) { - if (this._isFresnel === val) return - this._isFresnel = val - this.ssrMaterial.defines.isFresnel = val - this.ssrMaterial.needsUpdate = true - }, - }) - - this._isInfiniteThick = SSRShader.defines.isInfiniteThick - Object.defineProperty(this, 'isInfiniteThick', { - get() { - return this._isInfiniteThick - }, - set(val) { - if (this._isInfiniteThick === val) return - this._isInfiniteThick = val - this.ssrMaterial.defines.isInfiniteThick = val - this.ssrMaterial.needsUpdate = true - }, - }) - this.thickTolerance = SSRShader.uniforms.thickTolerance.value - - // beauty render target with depth buffer - - var depthTexture = new DepthTexture() - depthTexture.type = UnsignedShortType - depthTexture.minFilter = NearestFilter - depthTexture.maxFilter = NearestFilter - - this.beautyRenderTarget = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: LinearFilter, - format: RGBAFormat, - depthTexture: depthTexture, - depthBuffer: true, - }) - - //for bouncing - this.prevRenderTarget = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: LinearFilter, - format: RGBAFormat, - }) - - // normal render target - - this.normalRenderTarget = new WebGLRenderTarget(this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - format: RGBAFormat, - type: HalfFloatType, - }) - - // metalness render target - - // if (this.isSelective) { - this.metalnessRenderTarget = new WebGLRenderTarget(this.width, this.height, { - minFilter: NearestFilter, - magFilter: NearestFilter, - format: RGBAFormat, - }) - // } - - // ssr render target - - this.ssrRenderTarget = new WebGLRenderTarget(this.width, this.height, { - minFilter: LinearFilter, - magFilter: LinearFilter, - format: RGBAFormat, - }) - - this.blurRenderTarget = this.ssrRenderTarget.clone() - this.blurRenderTarget2 = this.ssrRenderTarget.clone() - // this.blurRenderTarget3 = this.ssrRenderTarget.clone(); - - // ssr material - - if (SSRShader === undefined) { - console.error('THREE.SSRPass: The pass relies on SSRShader.') - } + }, + }) - this.ssrMaterial = new ShaderMaterial({ - defines: Object.assign( - { - MAX_STEP: Math.sqrt(window.innerWidth * window.innerWidth + window.innerHeight * window.innerHeight), + this._infiniteThick = SSRShader.defines.INFINITE_THICK + Object.defineProperty(this, 'infiniteThick', { + get() { + return this._infiniteThick }, - SSRShader.defines, - ), - uniforms: UniformsUtils.clone(SSRShader.uniforms), - vertexShader: SSRShader.vertexShader, - fragmentShader: SSRShader.fragmentShader, - blending: NoBlending, - }) - if (!isPerspectiveCamera) { - this.ssrMaterial.defines.isPerspectiveCamera = isPerspectiveCamera + set(val) { + if (this._infiniteThick === val) return + this._infiniteThick = val + this.ssrMaterial.defines.INFINITE_THICK = val + this.ssrMaterial.needsUpdate = true + }, + }) + + // beauty render target with depth buffer + + const depthTexture = new DepthTexture() + depthTexture.type = UnsignedShortType + depthTexture.minFilter = NearestFilter + depthTexture.magFilter = NearestFilter + + this.beautyRenderTarget = new WebGLRenderTarget(this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + depthTexture: depthTexture, + depthBuffer: true, + }) + + //for bouncing + this.prevRenderTarget = new WebGLRenderTarget(this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + }) + + // normal render target + + this.normalRenderTarget = new WebGLRenderTarget(this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + }) + + // metalness render target + + this.metalnessRenderTarget = new WebGLRenderTarget(this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + type: HalfFloatType, + }) + + // ssr render target + + this.ssrRenderTarget = new WebGLRenderTarget(this.width, this.height, { + minFilter: NearestFilter, + magFilter: NearestFilter, + }) + + this.blurRenderTarget = this.ssrRenderTarget.clone() + this.blurRenderTarget2 = this.ssrRenderTarget.clone() + // this.blurRenderTarget3 = this.ssrRenderTarget.clone(); + + // ssr material + + this.ssrMaterial = new ShaderMaterial({ + defines: Object.assign({}, SSRShader.defines, { + MAX_STEP: Math.sqrt(this.width * this.width + this.height * this.height), + }), + uniforms: UniformsUtils.clone(SSRShader.uniforms), + vertexShader: SSRShader.vertexShader, + fragmentShader: SSRShader.fragmentShader, + blending: NoBlending, + }) + + this.ssrMaterial.uniforms['tDiffuse'].value = this.beautyRenderTarget.texture + this.ssrMaterial.uniforms['tNormal'].value = this.normalRenderTarget.texture + this.ssrMaterial.defines.SELECTIVE = this.selective this.ssrMaterial.needsUpdate = true - } + this.ssrMaterial.uniforms['tMetalness'].value = this.metalnessRenderTarget.texture + this.ssrMaterial.uniforms['tDepth'].value = this.beautyRenderTarget.depthTexture + this.ssrMaterial.uniforms['cameraNear'].value = this.camera.near + this.ssrMaterial.uniforms['cameraFar'].value = this.camera.far + this.ssrMaterial.uniforms['thickness'].value = this.thickness + this.ssrMaterial.uniforms['resolution'].value.set(this.width, this.height) + this.ssrMaterial.uniforms['cameraProjectionMatrix'].value.copy(this.camera.projectionMatrix) + this.ssrMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy(this.camera.projectionMatrixInverse) - this.ssrMaterial.uniforms['tDiffuse'].value = this.beautyRenderTarget.texture - this.ssrMaterial.uniforms['tNormal'].value = this.normalRenderTarget.texture - // if (this.isSelective) { - this.ssrMaterial.defines.isSelective = this.isSelective - this.ssrMaterial.needsUpdate = true - this.ssrMaterial.uniforms['tMetalness'].value = this.metalnessRenderTarget.texture - // } - this.ssrMaterial.uniforms['tDepth'].value = this.beautyRenderTarget.depthTexture - this.ssrMaterial.uniforms['cameraNear'].value = this.camera.near - this.ssrMaterial.uniforms['cameraFar'].value = this.camera.far - this.ssrMaterial.uniforms['surfDist'].value = this.surfDist - this.ssrMaterial.uniforms['resolution'].value.set(this.width, this.height) - this.ssrMaterial.uniforms['cameraProjectionMatrix'].value.copy(this.camera.projectionMatrix) - this.ssrMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy(this.camera.projectionMatrixInverse) - - // normal material - - this.normalMaterial = new MeshNormalMaterial({ morphTargets }) - this.normalMaterial.blending = NoBlending - - // if (this.isSelective) { - // metalnessOn material - - this.metalnessOnMaterial = new MeshBasicMaterial({ - color: 'white', - }) - - // metalnessOff material - - this.metalnessOffMaterial = new MeshBasicMaterial({ - color: 'black', - }) - // } - - // blur material - - this.blurMaterial = new ShaderMaterial({ - defines: Object.assign({}, SSRBlurShader.defines), - uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), - vertexShader: SSRBlurShader.vertexShader, - fragmentShader: SSRBlurShader.fragmentShader, - }) - this.blurMaterial.uniforms['tDiffuse'].value = this.ssrRenderTarget.texture - this.blurMaterial.uniforms['resolution'].value.set(this.width, this.height) - - // blur material 2 - - this.blurMaterial2 = new ShaderMaterial({ - defines: Object.assign({}, SSRBlurShader.defines), - uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), - vertexShader: SSRBlurShader.vertexShader, - fragmentShader: SSRBlurShader.fragmentShader, - }) - this.blurMaterial2.uniforms['tDiffuse'].value = this.blurRenderTarget.texture - this.blurMaterial2.uniforms['resolution'].value.set(this.width, this.height) - - // // blur material 3 - - // this.blurMaterial3 = new ShaderMaterial({ - // defines: Object.assign({}, SSRBlurShader.defines), - // uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), - // vertexShader: SSRBlurShader.vertexShader, - // fragmentShader: SSRBlurShader.fragmentShader - // }); - // this.blurMaterial3.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture; - // this.blurMaterial3.uniforms['resolution'].value.set(this.width, this.height); - - // material for rendering the depth - - this.depthRenderMaterial = new ShaderMaterial({ - defines: Object.assign({}, SSRDepthShader.defines), - uniforms: UniformsUtils.clone(SSRDepthShader.uniforms), - vertexShader: SSRDepthShader.vertexShader, - fragmentShader: SSRDepthShader.fragmentShader, - blending: NoBlending, - }) - this.depthRenderMaterial.uniforms['tDepth'].value = this.beautyRenderTarget.depthTexture - this.depthRenderMaterial.uniforms['cameraNear'].value = this.camera.near - this.depthRenderMaterial.uniforms['cameraFar'].value = this.camera.far - - // material for rendering the content of a render target - - this.copyMaterial = new ShaderMaterial({ - uniforms: UniformsUtils.clone(CopyShader.uniforms), - vertexShader: CopyShader.vertexShader, - fragmentShader: CopyShader.fragmentShader, - transparent: true, - depthTest: false, - depthWrite: false, - blendSrc: SrcAlphaFactor, - blendDst: OneMinusSrcAlphaFactor, - blendEquation: AddEquation, - blendSrcAlpha: SrcAlphaFactor, - blendDstAlpha: OneMinusSrcAlphaFactor, - blendEquationAlpha: AddEquation, - // premultipliedAlpha:true, - }) - - this.fsQuad = new FullScreenQuad(null) - - this.originalClearColor = new Color() -} + // normal material + + this.normalMaterial = new MeshNormalMaterial() + this.normalMaterial.blending = NoBlending -SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { - constructor: SSRPass, + // metalnessOn material + + this.metalnessOnMaterial = new MeshBasicMaterial({ + color: 'white', + }) + + // metalnessOff material + + this.metalnessOffMaterial = new MeshBasicMaterial({ + color: 'black', + }) - dispose: function () { + // blur material + + this.blurMaterial = new ShaderMaterial({ + defines: Object.assign({}, SSRBlurShader.defines), + uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), + vertexShader: SSRBlurShader.vertexShader, + fragmentShader: SSRBlurShader.fragmentShader, + }) + this.blurMaterial.uniforms['tDiffuse'].value = this.ssrRenderTarget.texture + this.blurMaterial.uniforms['resolution'].value.set(this.width, this.height) + + // blur material 2 + + this.blurMaterial2 = new ShaderMaterial({ + defines: Object.assign({}, SSRBlurShader.defines), + uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), + vertexShader: SSRBlurShader.vertexShader, + fragmentShader: SSRBlurShader.fragmentShader, + }) + this.blurMaterial2.uniforms['tDiffuse'].value = this.blurRenderTarget.texture + this.blurMaterial2.uniforms['resolution'].value.set(this.width, this.height) + + // // blur material 3 + + // this.blurMaterial3 = new ShaderMaterial({ + // defines: Object.assign({}, SSRBlurShader.defines), + // uniforms: UniformsUtils.clone(SSRBlurShader.uniforms), + // vertexShader: SSRBlurShader.vertexShader, + // fragmentShader: SSRBlurShader.fragmentShader + // }); + // this.blurMaterial3.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture; + // this.blurMaterial3.uniforms['resolution'].value.set(this.width, this.height); + + // material for rendering the depth + + this.depthRenderMaterial = new ShaderMaterial({ + defines: Object.assign({}, SSRDepthShader.defines), + uniforms: UniformsUtils.clone(SSRDepthShader.uniforms), + vertexShader: SSRDepthShader.vertexShader, + fragmentShader: SSRDepthShader.fragmentShader, + blending: NoBlending, + }) + this.depthRenderMaterial.uniforms['tDepth'].value = this.beautyRenderTarget.depthTexture + this.depthRenderMaterial.uniforms['cameraNear'].value = this.camera.near + this.depthRenderMaterial.uniforms['cameraFar'].value = this.camera.far + + // material for rendering the content of a render target + + this.copyMaterial = new ShaderMaterial({ + uniforms: UniformsUtils.clone(CopyShader.uniforms), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + transparent: true, + depthTest: false, + depthWrite: false, + blendSrc: SrcAlphaFactor, + blendDst: OneMinusSrcAlphaFactor, + blendEquation: AddEquation, + blendSrcAlpha: SrcAlphaFactor, + blendDstAlpha: OneMinusSrcAlphaFactor, + blendEquationAlpha: AddEquation, + // premultipliedAlpha:true, + }) + + this.fsQuad = new FullScreenQuad(null) + + this.originalClearColor = new Color() + } + + dispose() { // dispose render targets this.beautyRenderTarget.dispose() this.prevRenderTarget.dispose() this.normalRenderTarget.dispose() - // if (this.isSelective) this.metalnessRenderTarget.dispose() this.ssrRenderTarget.dispose() this.blurRenderTarget.dispose() @@ -331,10 +303,8 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { // dispose materials this.normalMaterial.dispose() - // if (this.isSelective) { this.metalnessOnMaterial.dispose() this.metalnessOffMaterial.dispose() - // } this.blurMaterial.dispose() this.blurMaterial2.dispose() this.copyMaterial.dispose() @@ -343,24 +313,19 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { // dipsose full screen quad this.fsQuad.dispose() - }, + } - render: function (renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */) { + render(renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */) { // render beauty and depth - if (this.encoding) { - if ('colorSpace' in this.beautyRenderTarget.texture) { - this.beautyRenderTarget.texture.colorSpace = this.encoding === 3001 ? 'srgb' : 'srgb-linear' - } else { - this.beautyRenderTarget.texture.encoding = this.encoding - } - } renderer.setRenderTarget(this.beautyRenderTarget) renderer.clear() if (this.groundReflector) { + this.groundReflector.visible = false this.groundReflector.doRender(this.renderer, this.scene, this.camera) this.groundReflector.visible = true } + renderer.render(this.scene, this.camera) if (this.groundReflector) this.groundReflector.visible = false @@ -370,7 +335,7 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { // render metalnesses - if (this.isSelective) { + if (this.selective) { this.renderMetalness(renderer, this.metalnessOnMaterial, this.metalnessRenderTarget, 0, 0) } @@ -378,13 +343,12 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { this.ssrMaterial.uniforms['opacity'].value = this.opacity this.ssrMaterial.uniforms['maxDistance'].value = this.maxDistance - this.ssrMaterial.uniforms['surfDist'].value = this.surfDist - this.ssrMaterial.uniforms['thickTolerance'].value = this.thickTolerance + this.ssrMaterial.uniforms['thickness'].value = this.thickness this.renderPass(renderer, this.ssrMaterial, this.ssrRenderTarget) // render blur - if (this.isBlur) { + if (this.blur) { this.renderPass(renderer, this.blurMaterial, this.blurRenderTarget) this.renderPass(renderer, this.blurMaterial2, this.blurRenderTarget2) // this.renderPass(renderer, this.blurMaterial3, this.blurRenderTarget3); @@ -394,12 +358,12 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { switch (this.output) { case SSRPass.OUTPUT.Default: - if (this.isBouncing) { + if (this.bouncing) { this.copyMaterial.uniforms['tDiffuse'].value = this.beautyRenderTarget.texture this.copyMaterial.blending = NoBlending this.renderPass(renderer, this.copyMaterial, this.prevRenderTarget) - if (this.isBlur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture + if (this.blur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture else this.copyMaterial.uniforms['tDiffuse'].value = this.ssrRenderTarget.texture this.copyMaterial.blending = NormalBlending this.renderPass(renderer, this.copyMaterial, this.prevRenderTarget) @@ -412,7 +376,7 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { this.copyMaterial.blending = NoBlending this.renderPass(renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer) - if (this.isBlur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture + if (this.blur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture else this.copyMaterial.uniforms['tDiffuse'].value = this.ssrRenderTarget.texture this.copyMaterial.blending = NormalBlending this.renderPass(renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer) @@ -420,13 +384,13 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { break case SSRPass.OUTPUT.SSR: - if (this.isBlur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture + if (this.blur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture else this.copyMaterial.uniforms['tDiffuse'].value = this.ssrRenderTarget.texture this.copyMaterial.blending = NoBlending this.renderPass(renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer) - if (this.isBouncing) { - if (this.isBlur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture + if (this.bouncing) { + if (this.blur) this.copyMaterial.uniforms['tDiffuse'].value = this.blurRenderTarget2.texture else this.copyMaterial.uniforms['tDiffuse'].value = this.beautyRenderTarget.texture this.copyMaterial.blending = NoBlending this.renderPass(renderer, this.copyMaterial, this.prevRenderTarget) @@ -467,13 +431,13 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { default: console.warn('THREE.SSRPass: Unknown output type.') } - }, + } - renderPass: function (renderer, passMaterial, renderTarget, clearColor, clearAlpha) { + renderPass(renderer, passMaterial, renderTarget, clearColor, clearAlpha) { // save original state this.originalClearColor.copy(renderer.getClearColor(this.tempColor)) - var originalClearAlpha = renderer.getClearAlpha(this.tempColor) - var originalAutoClear = renderer.autoClear + const originalClearAlpha = renderer.getClearAlpha(this.tempColor) + const originalAutoClear = renderer.autoClear renderer.setRenderTarget(renderTarget) @@ -492,12 +456,12 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.autoClear = originalAutoClear renderer.setClearColor(this.originalClearColor) renderer.setClearAlpha(originalClearAlpha) - }, + } - renderOverride: function (renderer, overrideMaterial, renderTarget, clearColor, clearAlpha) { + renderOverride(renderer, overrideMaterial, renderTarget, clearColor, clearAlpha) { this.originalClearColor.copy(renderer.getClearColor(this.tempColor)) - var originalClearAlpha = renderer.getClearAlpha(this.tempColor) - var originalAutoClear = renderer.autoClear + const originalClearAlpha = renderer.getClearAlpha(this.tempColor) + const originalAutoClear = renderer.autoClear renderer.setRenderTarget(renderTarget) renderer.autoClear = false @@ -520,12 +484,12 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.autoClear = originalAutoClear renderer.setClearColor(this.originalClearColor) renderer.setClearAlpha(originalClearAlpha) - }, + } - renderMetalness: function (renderer, overrideMaterial, renderTarget, clearColor, clearAlpha) { + renderMetalness(renderer, overrideMaterial, renderTarget, clearColor, clearAlpha) { this.originalClearColor.copy(renderer.getClearColor(this.tempColor)) - var originalClearAlpha = renderer.getClearAlpha(this.tempColor) - var originalAutoClear = renderer.autoClear + const originalClearAlpha = renderer.getClearAlpha(this.tempColor) + const originalAutoClear = renderer.autoClear renderer.setRenderTarget(renderTarget) renderer.autoClear = false @@ -540,7 +504,7 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { } this.scene.traverseVisible((child) => { - child._SSRPassMaterialBack = child.material + child._SSRPassBackupMaterial = child.material if (this._selects.includes(child)) { child.material = this.metalnessOnMaterial } else { @@ -549,7 +513,7 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { }) renderer.render(this.scene, this.camera) this.scene.traverseVisible((child) => { - child.material = child._SSRPassMaterialBack + child.material = child._SSRPassBackupMaterial }) // restore original state @@ -557,9 +521,9 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.autoClear = originalAutoClear renderer.setClearColor(this.originalClearColor) renderer.setClearAlpha(originalClearAlpha) - }, + } - setSize: function (width, height) { + setSize(width, height) { this.width = width this.height = height @@ -569,7 +533,6 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { this.prevRenderTarget.setSize(width, height) this.ssrRenderTarget.setSize(width, height) this.normalRenderTarget.setSize(width, height) - // if (this.isSelective) this.metalnessRenderTarget.setSize(width, height) this.blurRenderTarget.setSize(width, height) this.blurRenderTarget2.setSize(width, height) @@ -581,16 +544,7 @@ SSRPass.prototype = Object.assign(Object.create(Pass.prototype), { this.blurMaterial.uniforms['resolution'].value.set(width, height) this.blurMaterial2.uniforms['resolution'].value.set(width, height) - }, -}) - -SSRPass.OUTPUT = { - Default: 0, - SSR: 1, - Beauty: 3, - Depth: 4, - Normal: 5, - Metalness: 7, + } } export { SSRPass } diff --git a/src/postprocessing/TAARenderPass.d.ts b/src/postprocessing/TAARenderPass.d.ts index d3420998..96eab18e 100644 --- a/src/postprocessing/TAARenderPass.d.ts +++ b/src/postprocessing/TAARenderPass.d.ts @@ -1,8 +1,8 @@ -import { Scene, Camera, ColorRepresentation } from 'three' +import { Scene, Camera, Color } from 'three' import { SSAARenderPass } from './SSAARenderPass' export class TAARenderPass extends SSAARenderPass { - constructor(scene: Scene, camera: Camera, clearColor: ColorRepresentation, clearAlpha: number) + constructor(scene: Scene, camera: Camera, clearColor: Color | string | number, clearAlpha: number) accumulate: boolean } diff --git a/src/postprocessing/TAARenderPass.js b/src/postprocessing/TAARenderPass.js index 6183bde9..4450f4c7 100644 --- a/src/postprocessing/TAARenderPass.js +++ b/src/postprocessing/TAARenderPass.js @@ -1,5 +1,5 @@ -import { WebGLRenderTarget } from 'three' -import { SSAARenderPass } from '../postprocessing/SSAARenderPass' +import { HalfFloatType, WebGLRenderTarget } from 'three' +import { SSAARenderPass } from './SSAARenderPass' /** * @@ -13,62 +13,54 @@ import { SSAARenderPass } from '../postprocessing/SSAARenderPass' * */ -var TAARenderPass = function (scene, camera, clearColor, clearAlpha) { - if (SSAARenderPass === undefined) { - console.error('THREE.TAARenderPass relies on SSAARenderPass') - } - - SSAARenderPass.call(this, scene, camera, clearColor, clearAlpha) - - this.sampleLevel = 0 - this.accumulate = false -} - -TAARenderPass.JitterVectors = SSAARenderPass.JitterVectors +class TAARenderPass extends SSAARenderPass { + constructor(scene, camera, clearColor, clearAlpha) { + super(scene, camera, clearColor, clearAlpha) -TAARenderPass.prototype = Object.assign(Object.create(SSAARenderPass.prototype), { - constructor: TAARenderPass, + this.sampleLevel = 0 + this.accumulate = false + } - render: function (renderer, writeBuffer, readBuffer, deltaTime) { - if (!this.accumulate) { - SSAARenderPass.prototype.render.call(this, renderer, writeBuffer, readBuffer, deltaTime) + render(renderer, writeBuffer, readBuffer, deltaTime) { + if (this.accumulate === false) { + super.render(renderer, writeBuffer, readBuffer, deltaTime) this.accumulateIndex = -1 return } - var jitterOffsets = TAARenderPass.JitterVectors[5] + const jitterOffsets = _JitterVectors[5] - if (!this.sampleRenderTarget) { - this.sampleRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, this.params) + if (this.sampleRenderTarget === undefined) { + this.sampleRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, { type: HalfFloatType }) this.sampleRenderTarget.texture.name = 'TAARenderPass.sample' } - if (!this.holdRenderTarget) { - this.holdRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, this.params) + if (this.holdRenderTarget === undefined) { + this.holdRenderTarget = new WebGLRenderTarget(readBuffer.width, readBuffer.height, { type: HalfFloatType }) this.holdRenderTarget.texture.name = 'TAARenderPass.hold' } - if (this.accumulate && this.accumulateIndex === -1) { - SSAARenderPass.prototype.render.call(this, renderer, this.holdRenderTarget, readBuffer, deltaTime) + if (this.accumulateIndex === -1) { + super.render(renderer, this.holdRenderTarget, readBuffer, deltaTime) this.accumulateIndex = 0 } - var autoClear = renderer.autoClear + const autoClear = renderer.autoClear renderer.autoClear = false - var sampleWeight = 1.0 / jitterOffsets.length + const sampleWeight = 1.0 / jitterOffsets.length if (this.accumulateIndex >= 0 && this.accumulateIndex < jitterOffsets.length) { this.copyUniforms['opacity'].value = sampleWeight this.copyUniforms['tDiffuse'].value = writeBuffer.texture // render the scene multiple times, each slightly jitter offset from the last and accumulate the results. - var numSamplesPerFrame = Math.pow(2, this.sampleLevel) + const numSamplesPerFrame = Math.pow(2, this.sampleLevel) for (let i = 0; i < numSamplesPerFrame; i++) { - var j = this.accumulateIndex - var jitterOffset = jitterOffsets[j] + const j = this.accumulateIndex + const jitterOffset = jitterOffsets[j] if (this.camera.setViewOffset) { this.camera.setViewOffset( @@ -97,7 +89,7 @@ TAARenderPass.prototype = Object.assign(Object.create(SSAARenderPass.prototype), if (this.camera.clearViewOffset) this.camera.clearViewOffset() } - var accumulationWeight = this.accumulateIndex * sampleWeight + const accumulationWeight = this.accumulateIndex * sampleWeight if (accumulationWeight > 0) { this.copyUniforms['opacity'].value = 1.0 @@ -116,7 +108,47 @@ TAARenderPass.prototype = Object.assign(Object.create(SSAARenderPass.prototype), } renderer.autoClear = autoClear - }, -}) + } + + dispose() { + super.dispose() + + if (this.sampleRenderTarget !== undefined) this.sampleRenderTarget.dispose() + if (this.holdRenderTarget !== undefined) this.holdRenderTarget.dispose() + } +} + +// prettier-ignore +const _JitterVectors = [ + [ + [ 0, 0 ] + ], + [ + [ 4, 4 ], [ - 4, - 4 ] + ], + [ + [ - 2, - 6 ], [ 6, - 2 ], [ - 6, 2 ], [ 2, 6 ] + ], + [ + [ 1, - 3 ], [ - 1, 3 ], [ 5, 1 ], [ - 3, - 5 ], + [ - 5, 5 ], [ - 7, - 1 ], [ 3, 7 ], [ 7, - 7 ] + ], + [ + [ 1, 1 ], [ - 1, - 3 ], [ - 3, 2 ], [ 4, - 1 ], + [ - 5, - 2 ], [ 2, 5 ], [ 5, 3 ], [ 3, - 5 ], + [ - 2, 6 ], [ 0, - 7 ], [ - 4, - 6 ], [ - 6, 4 ], + [ - 8, 0 ], [ 7, - 4 ], [ 6, 7 ], [ - 7, - 8 ] + ], + [ + [ - 4, - 7 ], [ - 7, - 5 ], [ - 3, - 5 ], [ - 5, - 4 ], + [ - 1, - 4 ], [ - 2, - 2 ], [ - 6, - 1 ], [ - 4, 0 ], + [ - 7, 1 ], [ - 1, 2 ], [ - 6, 3 ], [ - 3, 3 ], + [ - 7, 6 ], [ - 3, 6 ], [ - 5, 7 ], [ - 1, 7 ], + [ 5, - 7 ], [ 1, - 6 ], [ 6, - 5 ], [ 4, - 4 ], + [ 2, - 3 ], [ 7, - 2 ], [ 1, - 1 ], [ 4, - 1 ], + [ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ], + [ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ] + ] +]; export { TAARenderPass } diff --git a/src/postprocessing/TexturePass.d.ts b/src/postprocessing/TexturePass.d.ts index accd7bd3..188185fa 100644 --- a/src/postprocessing/TexturePass.d.ts +++ b/src/postprocessing/TexturePass.d.ts @@ -1,6 +1,6 @@ import { Texture, ShaderMaterial } from 'three' -import { Pass } from './Pass' +import { Pass, FullScreenQuad } from './Pass' export class TexturePass extends Pass { constructor(map: Texture, opacity?: number) @@ -8,5 +8,5 @@ export class TexturePass extends Pass { opacity: number uniforms: object material: ShaderMaterial - fsQuad: object + fsQuad: FullScreenQuad } diff --git a/src/postprocessing/TexturePass.js b/src/postprocessing/TexturePass.js index 8b7c845f..5c1a4ecb 100644 --- a/src/postprocessing/TexturePass.js +++ b/src/postprocessing/TexturePass.js @@ -1,35 +1,33 @@ import { ShaderMaterial, UniformsUtils } from 'three' -import { Pass, FullScreenQuad } from '../postprocessing/Pass' +import { Pass, FullScreenQuad } from './Pass' import { CopyShader } from '../shaders/CopyShader' -var TexturePass = function (map, opacity) { - if (CopyShader === undefined) console.error('THREE.TexturePass relies on CopyShader') +class TexturePass extends Pass { + constructor(map, opacity) { + super() - var shader = CopyShader + const shader = CopyShader - this.map = map - this.opacity = opacity !== undefined ? opacity : 1.0 + this.map = map + this.opacity = opacity !== undefined ? opacity : 1.0 - this.uniforms = UniformsUtils.clone(shader.uniforms) + this.uniforms = UniformsUtils.clone(shader.uniforms) - this.material = new ShaderMaterial({ - uniforms: this.uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - depthTest: false, - depthWrite: false, - }) + this.material = new ShaderMaterial({ + uniforms: this.uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + depthTest: false, + depthWrite: false, + }) - this.needsSwap = false + this.needsSwap = false - this.fsQuad = new FullScreenQuad(null) -} - -TexturePass.prototype = Object.assign(Object.create(Pass.prototype), { - constructor: TexturePass, + this.fsQuad = new FullScreenQuad(null) + } - render: function (renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { - var oldAutoClear = renderer.autoClear + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { + const oldAutoClear = renderer.autoClear renderer.autoClear = false this.fsQuad.material = this.material @@ -43,7 +41,13 @@ TexturePass.prototype = Object.assign(Object.create(Pass.prototype), { this.fsQuad.render(renderer) renderer.autoClear = oldAutoClear - }, -}) + } + + dispose() { + this.material.dispose() + + this.fsQuad.dispose() + } +} export { TexturePass } diff --git a/src/postprocessing/UnrealBloomPass.js b/src/postprocessing/UnrealBloomPass.js index 26504d91..1ff2b789 100644 --- a/src/postprocessing/UnrealBloomPass.js +++ b/src/postprocessing/UnrealBloomPass.js @@ -1,16 +1,15 @@ import { AdditiveBlending, Color, - LinearFilter, + HalfFloatType, MeshBasicMaterial, - RGBAFormat, ShaderMaterial, UniformsUtils, Vector2, Vector3, WebGLRenderTarget, } from 'three' -import { Pass, FullScreenQuad } from '../postprocessing/Pass' +import { Pass, FullScreenQuad } from './Pass' import { CopyShader } from '../shaders/CopyShader' import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader' @@ -23,141 +22,134 @@ import { LuminosityHighPassShader } from '../shaders/LuminosityHighPassShader' * Reference: * - https://docs.unrealengine.com/latest/INT/Engine/Rendering/PostProcessEffects/Bloom/ */ -var UnrealBloomPass = function (resolution, strength, radius, threshold) { - this.strength = strength !== undefined ? strength : 1 - this.radius = radius - this.threshold = threshold - this.resolution = resolution !== undefined ? new Vector2(resolution.x, resolution.y) : new Vector2(256, 256) - - // create color only once here, reuse it later inside the render function - this.clearColor = new Color(0, 0, 0) - - // render targets - var pars = { - minFilter: LinearFilter, - magFilter: LinearFilter, - format: RGBAFormat, - } - this.renderTargetsHorizontal = [] - this.renderTargetsVertical = [] - this.nMips = 5 - var resx = Math.round(this.resolution.x / 2) - var resy = Math.round(this.resolution.y / 2) +class UnrealBloomPass extends Pass { + static BlurDirectionX = /* @__PURE__ */ new Vector2(1.0, 0.0) + static BlurDirectionY = /* @__PURE__ */ new Vector2(0.0, 1.0) - this.renderTargetBright = new WebGLRenderTarget(resx, resy, pars) - this.renderTargetBright.texture.name = 'UnrealBloomPass.bright' - this.renderTargetBright.texture.generateMipmaps = false + constructor(resolution, strength, radius, threshold) { + super() - for (let i = 0; i < this.nMips; i++) { - var renderTargetHorizonal = new WebGLRenderTarget(resx, resy, pars) + this.strength = strength !== undefined ? strength : 1 + this.radius = radius + this.threshold = threshold + this.resolution = resolution !== undefined ? new Vector2(resolution.x, resolution.y) : new Vector2(256, 256) - renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i - renderTargetHorizonal.texture.generateMipmaps = false + // create color only once here, reuse it later inside the render function + this.clearColor = new Color(0, 0, 0) - this.renderTargetsHorizontal.push(renderTargetHorizonal) + // render targets + this.renderTargetsHorizontal = [] + this.renderTargetsVertical = [] + this.nMips = 5 + let resx = Math.round(this.resolution.x / 2) + let resy = Math.round(this.resolution.y / 2) - var renderTargetVertical = new WebGLRenderTarget(resx, resy, pars) + this.renderTargetBright = new WebGLRenderTarget(resx, resy, { type: HalfFloatType }) + this.renderTargetBright.texture.name = 'UnrealBloomPass.bright' + this.renderTargetBright.texture.generateMipmaps = false - renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i - renderTargetVertical.texture.generateMipmaps = false + for (let i = 0; i < this.nMips; i++) { + const renderTargetHorizonal = new WebGLRenderTarget(resx, resy, { type: HalfFloatType }) - this.renderTargetsVertical.push(renderTargetVertical) + renderTargetHorizonal.texture.name = 'UnrealBloomPass.h' + i + renderTargetHorizonal.texture.generateMipmaps = false - resx = Math.round(resx / 2) + this.renderTargetsHorizontal.push(renderTargetHorizonal) - resy = Math.round(resy / 2) - } + const renderTargetVertical = new WebGLRenderTarget(resx, resy, { type: HalfFloatType }) - // luminosity high pass material + renderTargetVertical.texture.name = 'UnrealBloomPass.v' + i + renderTargetVertical.texture.generateMipmaps = false - if (LuminosityHighPassShader === undefined) console.error('THREE.UnrealBloomPass relies on LuminosityHighPassShader') + this.renderTargetsVertical.push(renderTargetVertical) - var highPassShader = LuminosityHighPassShader - this.highPassUniforms = UniformsUtils.clone(highPassShader.uniforms) + resx = Math.round(resx / 2) - this.highPassUniforms['luminosityThreshold'].value = threshold - this.highPassUniforms['smoothWidth'].value = 0.01 + resy = Math.round(resy / 2) + } - this.materialHighPassFilter = new ShaderMaterial({ - uniforms: this.highPassUniforms, - vertexShader: highPassShader.vertexShader, - fragmentShader: highPassShader.fragmentShader, - defines: {}, - }) + // luminosity high pass material - // Gaussian Blur Materials - this.separableBlurMaterials = [] - var kernelSizeArray = [3, 5, 7, 9, 11] - var resx = Math.round(this.resolution.x / 2) - var resy = Math.round(this.resolution.y / 2) + const highPassShader = LuminosityHighPassShader + this.highPassUniforms = UniformsUtils.clone(highPassShader.uniforms) - for (let i = 0; i < this.nMips; i++) { - this.separableBlurMaterials.push(this.getSeperableBlurMaterial(kernelSizeArray[i])) + this.highPassUniforms['luminosityThreshold'].value = threshold + this.highPassUniforms['smoothWidth'].value = 0.01 - this.separableBlurMaterials[i].uniforms['texSize'].value = new Vector2(resx, resy) + this.materialHighPassFilter = new ShaderMaterial({ + uniforms: this.highPassUniforms, + vertexShader: highPassShader.vertexShader, + fragmentShader: highPassShader.fragmentShader, + defines: {}, + }) - resx = Math.round(resx / 2) + // Gaussian Blur Materials + this.separableBlurMaterials = [] + const kernelSizeArray = [3, 5, 7, 9, 11] + resx = Math.round(this.resolution.x / 2) + resy = Math.round(this.resolution.y / 2) - resy = Math.round(resy / 2) - } + for (let i = 0; i < this.nMips; i++) { + this.separableBlurMaterials.push(this.getSeperableBlurMaterial(kernelSizeArray[i])) - // Composite material - this.compositeMaterial = this.getCompositeMaterial(this.nMips) - this.compositeMaterial.uniforms['blurTexture1'].value = this.renderTargetsVertical[0].texture - this.compositeMaterial.uniforms['blurTexture2'].value = this.renderTargetsVertical[1].texture - this.compositeMaterial.uniforms['blurTexture3'].value = this.renderTargetsVertical[2].texture - this.compositeMaterial.uniforms['blurTexture4'].value = this.renderTargetsVertical[3].texture - this.compositeMaterial.uniforms['blurTexture5'].value = this.renderTargetsVertical[4].texture - this.compositeMaterial.uniforms['bloomStrength'].value = strength - this.compositeMaterial.uniforms['bloomRadius'].value = 0.1 - this.compositeMaterial.needsUpdate = true - - var bloomFactors = [1.0, 0.8, 0.6, 0.4, 0.2] - this.compositeMaterial.uniforms['bloomFactors'].value = bloomFactors - this.bloomTintColors = [ - new Vector3(1, 1, 1), - new Vector3(1, 1, 1), - new Vector3(1, 1, 1), - new Vector3(1, 1, 1), - new Vector3(1, 1, 1), - ] - this.compositeMaterial.uniforms['bloomTintColors'].value = this.bloomTintColors - - // copy material - if (CopyShader === undefined) { - console.error('THREE.UnrealBloomPass relies on CopyShader') - } + this.separableBlurMaterials[i].uniforms['texSize'].value = new Vector2(resx, resy) - var copyShader = CopyShader + resx = Math.round(resx / 2) - this.copyUniforms = UniformsUtils.clone(copyShader.uniforms) - this.copyUniforms['opacity'].value = 1.0 + resy = Math.round(resy / 2) + } - this.materialCopy = new ShaderMaterial({ - uniforms: this.copyUniforms, - vertexShader: copyShader.vertexShader, - fragmentShader: copyShader.fragmentShader, - blending: AdditiveBlending, - depthTest: false, - depthWrite: false, - transparent: true, - }) + // Composite material + this.compositeMaterial = this.getCompositeMaterial(this.nMips) + this.compositeMaterial.uniforms['blurTexture1'].value = this.renderTargetsVertical[0].texture + this.compositeMaterial.uniforms['blurTexture2'].value = this.renderTargetsVertical[1].texture + this.compositeMaterial.uniforms['blurTexture3'].value = this.renderTargetsVertical[2].texture + this.compositeMaterial.uniforms['blurTexture4'].value = this.renderTargetsVertical[3].texture + this.compositeMaterial.uniforms['blurTexture5'].value = this.renderTargetsVertical[4].texture + this.compositeMaterial.uniforms['bloomStrength'].value = strength + this.compositeMaterial.uniforms['bloomRadius'].value = 0.1 + this.compositeMaterial.needsUpdate = true + + const bloomFactors = [1.0, 0.8, 0.6, 0.4, 0.2] + this.compositeMaterial.uniforms['bloomFactors'].value = bloomFactors + this.bloomTintColors = [ + new Vector3(1, 1, 1), + new Vector3(1, 1, 1), + new Vector3(1, 1, 1), + new Vector3(1, 1, 1), + new Vector3(1, 1, 1), + ] + this.compositeMaterial.uniforms['bloomTintColors'].value = this.bloomTintColors - this.enabled = true - this.needsSwap = false + // copy material - this._oldClearColor = new Color() - this.oldClearAlpha = 1 + const copyShader = CopyShader - this.basic = new MeshBasicMaterial() + this.copyUniforms = UniformsUtils.clone(copyShader.uniforms) + this.copyUniforms['opacity'].value = 1.0 - this.fsQuad = new FullScreenQuad(null) -} + this.materialCopy = new ShaderMaterial({ + uniforms: this.copyUniforms, + vertexShader: copyShader.vertexShader, + fragmentShader: copyShader.fragmentShader, + blending: AdditiveBlending, + depthTest: false, + depthWrite: false, + transparent: true, + }) -UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { - constructor: UnrealBloomPass, + this.enabled = true + this.needsSwap = false - dispose: function () { + this._oldClearColor = new Color() + this.oldClearAlpha = 1 + + this.basic = new MeshBasicMaterial() + + this.fsQuad = new FullScreenQuad(null) + } + + dispose() { for (let i = 0; i < this.renderTargetsHorizontal.length; i++) { this.renderTargetsHorizontal[i].dispose() } @@ -167,11 +159,25 @@ UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { } this.renderTargetBright.dispose() - }, - setSize: function (width, height) { - var resx = Math.round(width / 2) - var resy = Math.round(height / 2) + // + + for (let i = 0; i < this.separableBlurMaterials.length; i++) { + this.separableBlurMaterials[i].dispose() + } + + this.compositeMaterial.dispose() + this.materialCopy.dispose() + this.basic.dispose() + + // + + this.fsQuad.dispose() + } + + setSize(width, height) { + let resx = Math.round(width / 2) + let resy = Math.round(height / 2) this.renderTargetBright.setSize(resx, resy) @@ -184,12 +190,12 @@ UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { resx = Math.round(resx / 2) resy = Math.round(resy / 2) } - }, + } - render: function (renderer, writeBuffer, readBuffer, deltaTime, maskActive) { + render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) { renderer.getClearColor(this._oldClearColor) this.oldClearAlpha = renderer.getClearAlpha() - var oldAutoClear = renderer.autoClear + const oldAutoClear = renderer.autoClear renderer.autoClear = false renderer.setClearColor(this.clearColor, 0) @@ -219,7 +225,7 @@ UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { // 2. Blur All the mips progressively - var inputRenderTarget = this.renderTargetBright + let inputRenderTarget = this.renderTargetBright for (let i = 0; i < this.nMips; i++) { this.fsQuad.material = this.separableBlurMaterials[i] @@ -269,9 +275,9 @@ UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { renderer.setClearColor(this._oldClearColor, this.oldClearAlpha) renderer.autoClear = oldAutoClear - }, + } - getSeperableBlurMaterial: function (kernelRadius) { + getSeperableBlurMaterial(kernelRadius) { return new ShaderMaterial({ defines: { KERNEL_RADIUS: kernelRadius, @@ -284,42 +290,41 @@ UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { direction: { value: new Vector2(0.5, 0.5) }, }, - vertexShader: - 'varying vec2 vUv;\n' + - 'void main() {\n' + - ' vUv = uv;\n' + - ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n' + - '}', - fragmentShader: - '#include ' + - 'varying vec2 vUv;\n' + - 'uniform sampler2D colorTexture;\n' + - 'uniform vec2 texSize;' + - 'uniform vec2 direction;' + - '\n' + - 'float gaussianPdf(in float x, in float sigma) {' + - ' return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;' + - '}' + - 'void main() {\n' + - ' vec2 invSize = 1.0 / texSize;' + - ' float fSigma = float(SIGMA);' + - ' float weightSum = gaussianPdf(0.0, fSigma);' + - ' vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;' + - ' for( int i = 1; i < KERNEL_RADIUS; i ++ ) {' + - ' float x = float(i);' + - ' float w = gaussianPdf(x, fSigma);' + - ' vec2 uvOffset = direction * invSize * x;' + - ' vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;' + - ' vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;' + - ' diffuseSum += (sample1 + sample2) * w;' + - ' weightSum += 2.0 * w;' + - ' }' + - ' gl_FragColor = vec4(diffuseSum/weightSum, 1.0);\n' + - '}', + vertexShader: `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: `#include + varying vec2 vUv; + uniform sampler2D colorTexture; + uniform vec2 texSize; + uniform vec2 direction; + + float gaussianPdf(in float x, in float sigma) { + return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma; + } + void main() { + vec2 invSize = 1.0 / texSize; + float fSigma = float(SIGMA); + float weightSum = gaussianPdf(0.0, fSigma); + vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum; + for( int i = 1; i < KERNEL_RADIUS; i ++ ) { + float x = float(i); + float w = gaussianPdf(x, fSigma); + vec2 uvOffset = direction * invSize * x; + vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb; + vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb; + diffuseSum += (sample1 + sample2) * w; + weightSum += 2.0 * w; + } + gl_FragColor = vec4(diffuseSum/weightSum, 1.0); + }`, }) - }, + } - getCompositeMaterial: function (nMips) { + getCompositeMaterial(nMips) { return new ShaderMaterial({ defines: { NUM_MIPS: nMips, @@ -331,49 +336,43 @@ UnrealBloomPass.prototype = Object.assign(Object.create(Pass.prototype), { blurTexture3: { value: null }, blurTexture4: { value: null }, blurTexture5: { value: null }, - dirtTexture: { value: null }, bloomStrength: { value: 1.0 }, bloomFactors: { value: null }, bloomTintColors: { value: null }, bloomRadius: { value: 0.0 }, }, - vertexShader: - 'varying vec2 vUv;\n' + - 'void main() {\n' + - ' vUv = uv;\n' + - ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n' + - '}', - fragmentShader: - 'varying vec2 vUv;' + - 'uniform sampler2D blurTexture1;' + - 'uniform sampler2D blurTexture2;' + - 'uniform sampler2D blurTexture3;' + - 'uniform sampler2D blurTexture4;' + - 'uniform sampler2D blurTexture5;' + - 'uniform sampler2D dirtTexture;' + - 'uniform float bloomStrength;' + - 'uniform float bloomRadius;' + - 'uniform float bloomFactors[NUM_MIPS];' + - 'uniform vec3 bloomTintColors[NUM_MIPS];' + - '' + - 'float lerpBloomFactor(const in float factor) { ' + - ' float mirrorFactor = 1.2 - factor;' + - ' return mix(factor, mirrorFactor, bloomRadius);' + - '}' + - '' + - 'void main() {' + - ' gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + ' + - ' lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + ' + - ' lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + ' + - ' lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + ' + - ' lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) );' + - '}', + vertexShader: `varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }`, + + fragmentShader: `varying vec2 vUv; + uniform sampler2D blurTexture1; + uniform sampler2D blurTexture2; + uniform sampler2D blurTexture3; + uniform sampler2D blurTexture4; + uniform sampler2D blurTexture5; + uniform float bloomStrength; + uniform float bloomRadius; + uniform float bloomFactors[NUM_MIPS]; + uniform vec3 bloomTintColors[NUM_MIPS]; + + float lerpBloomFactor(const in float factor) { + float mirrorFactor = 1.2 - factor; + return mix(factor, mirrorFactor, bloomRadius); + } + + void main() { + gl_FragColor = bloomStrength * ( lerpBloomFactor(bloomFactors[0]) * vec4(bloomTintColors[0], 1.0) * texture2D(blurTexture1, vUv) + + lerpBloomFactor(bloomFactors[1]) * vec4(bloomTintColors[1], 1.0) * texture2D(blurTexture2, vUv) + + lerpBloomFactor(bloomFactors[2]) * vec4(bloomTintColors[2], 1.0) * texture2D(blurTexture3, vUv) + + lerpBloomFactor(bloomFactors[3]) * vec4(bloomTintColors[3], 1.0) * texture2D(blurTexture4, vUv) + + lerpBloomFactor(bloomFactors[4]) * vec4(bloomTintColors[4], 1.0) * texture2D(blurTexture5, vUv) ); + }`, }) - }, -}) - -UnrealBloomPass.BlurDirectionX = new Vector2(1.0, 0.0) -UnrealBloomPass.BlurDirectionY = new Vector2(0.0, 1.0) + } +} export { UnrealBloomPass } diff --git a/src/renderers/CSS2DRenderer.d.ts b/src/renderers/CSS2DRenderer.d.ts index 07633faf..31d76285 100644 --- a/src/renderers/CSS2DRenderer.d.ts +++ b/src/renderers/CSS2DRenderer.d.ts @@ -1,8 +1,9 @@ -import { Object3D, Scene, Camera } from 'three' +import { Object3D, Scene, Camera, Vector2 } from 'three' export class CSS2DObject extends Object3D { constructor(element: HTMLElement) element: HTMLElement + center: Vector2 onBeforeRender: (renderer: unknown, scene: Scene, camera: Camera) => void onAfterRender: (renderer: unknown, scene: Scene, camera: Camera) => void diff --git a/src/renderers/CSS2DRenderer.js b/src/renderers/CSS2DRenderer.js index c14d4c7a..f0aa059e 100644 --- a/src/renderers/CSS2DRenderer.js +++ b/src/renderers/CSS2DRenderer.js @@ -1,4 +1,4 @@ -import { Matrix4, Object3D, Vector3 } from 'three' +import { Matrix4, Object3D, Vector2, Vector3 } from 'three' class CSS2DObject extends Object3D { constructor(element = document.createElement('div')) { @@ -13,6 +13,8 @@ class CSS2DObject extends Object3D { this.element.setAttribute('draggable', false) + this.center = new Vector2(0.5, 0.5) // ( 0, 0 ) is the lower left; ( 1, 1 ) is the top right + this.addEventListener('removed', function () { this.traverse(function (object) { if (object.element instanceof Element && object.element.parentNode !== null) { @@ -27,15 +29,17 @@ class CSS2DObject extends Object3D { this.element = source.element.cloneNode(true) + this.center = source.center + return this } } -const _vector = /*#__PURE__*/ new Vector3() -const _viewMatrix = /*#__PURE__*/ new Matrix4() -const _viewProjectionMatrix = /*#__PURE__*/ new Matrix4() -const _a = /*#__PURE__*/ new Vector3() -const _b = /*#__PURE__*/ new Vector3() +const _vector = /* @__PURE__ */ new Vector3() +const _viewMatrix = /* @__PURE__ */ new Matrix4() +const _viewProjectionMatrix = /* @__PURE__ */ new Matrix4() +const _a = /* @__PURE__ */ new Vector3() +const _b = /* @__PURE__ */ new Vector3() class CSS2DRenderer { constructor(parameters = {}) { @@ -62,10 +66,8 @@ class CSS2DRenderer { } this.render = function (scene, camera) { - if (scene.matrixWorldAutoUpdate === true || scene.autoUpdate === true) scene.updateMatrixWorld() - if (camera.parent === null && (camera.matrixWorldAutoUpdate == null || camera.matrixWorldAutoUpdate === true)) { - camera.updateMatrixWorld() - } + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld() + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld() _viewMatrix.copy(camera.matrixWorldInverse) _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix) @@ -100,7 +102,12 @@ class CSS2DRenderer { const element = object.element element.style.transform = - 'translate(-50%,-50%) translate(' + + 'translate(' + + -100 * object.center.x + + '%,' + + -100 * object.center.y + + '%)' + + 'translate(' + (_vector.x * _widthHalf + _widthHalf) + 'px,' + (-_vector.y * _heightHalf + _heightHalf) + diff --git a/src/renderers/CSS3DRenderer.d.ts b/src/renderers/CSS3DRenderer.d.ts index c141843c..deac44f4 100644 --- a/src/renderers/CSS3DRenderer.d.ts +++ b/src/renderers/CSS3DRenderer.d.ts @@ -12,7 +12,7 @@ export class CSS3DSprite extends CSS3DObject { constructor(element: HTMLElement) } -export type CSS3DParameters = { +export interface CSS3DParameters { element?: HTMLElement } diff --git a/src/renderers/CSS3DRenderer.js b/src/renderers/CSS3DRenderer.js index 37ecf5c5..d4a7f9ae 100644 --- a/src/renderers/CSS3DRenderer.js +++ b/src/renderers/CSS3DRenderer.js @@ -1,260 +1,314 @@ -import { Matrix4, Object3D } from 'three' +import { Matrix4, Object3D, Quaternion, Vector3 } from 'three' /** * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs */ -var CSS3DObject = function (element) { - Object3D.call(this) +const _position = /* @__PURE__ */ new Vector3() +const _quaternion = /* @__PURE__ */ new Quaternion() +const _scale = /* @__PURE__ */ new Vector3() - this.element = element || document.createElement('div') - this.element.style.position = 'absolute' - this.element.style.pointerEvents = 'auto' +class CSS3DObject extends Object3D { + constructor(element = document.createElement('div')) { + super() - this.addEventListener('removed', function () { - this.traverse(function (object) { - if (object.element instanceof Element && object.element.parentNode !== null) { - object.element.parentNode.removeChild(object.element) - } - }) - }) -} + this.isCSS3DObject = true -CSS3DObject.prototype = Object.assign(Object.create(Object3D.prototype), { - constructor: CSS3DObject, + this.element = element + this.element.style.position = 'absolute' + this.element.style.pointerEvents = 'auto' + this.element.style.userSelect = 'none' - copy: function (source, recursive) { - Object3D.prototype.copy.call(this, source, recursive) + this.element.setAttribute('draggable', false) + + this.addEventListener('removed', function () { + this.traverse(function (object) { + if (object.element instanceof Element && object.element.parentNode !== null) { + object.element.parentNode.removeChild(object.element) + } + }) + }) + } + + copy(source, recursive) { + super.copy(source, recursive) this.element = source.element.cloneNode(true) return this - }, -}) - -var CSS3DSprite = function (element) { - CSS3DObject.call(this, element) + } } -CSS3DSprite.prototype = Object.create(CSS3DObject.prototype) -CSS3DSprite.prototype.constructor = CSS3DSprite +class CSS3DSprite extends CSS3DObject { + constructor(element) { + super(element) -// + this.isCSS3DSprite = true -var CSS3DRenderer = function () { - var _this = this + this.rotation2D = 0 + } - var _width, _height - var _widthHalf, _heightHalf + copy(source, recursive) { + super.copy(source, recursive) - var matrix = new Matrix4() + this.rotation2D = source.rotation2D - var cache = { - camera: { fov: 0, style: '' }, - objects: new WeakMap(), + return this } +} - var domElement = document.createElement('div') - domElement.style.overflow = 'hidden' - - this.domElement = domElement +// - var cameraElement = document.createElement('div') +const _matrix = /* @__PURE__ */ new Matrix4() +const _matrix2 = /* @__PURE__ */ new Matrix4() - cameraElement.style.transformStyle = 'preserve-3d' - cameraElement.style.pointerEvents = 'none' +class CSS3DRenderer { + constructor(parameters = {}) { + const _this = this - domElement.appendChild(cameraElement) + let _width, _height + let _widthHalf, _heightHalf - this.getSize = function () { - return { - width: _width, - height: _height, + const cache = { + camera: { fov: 0, style: '' }, + objects: new WeakMap(), } - } - this.setSize = function (width, height) { - _width = width - _height = height - _widthHalf = _width / 2 - _heightHalf = _height / 2 + const domElement = parameters.element !== undefined ? parameters.element : document.createElement('div') - domElement.style.width = width + 'px' - domElement.style.height = height + 'px' + domElement.style.overflow = 'hidden' - cameraElement.style.width = width + 'px' - cameraElement.style.height = height + 'px' - } + this.domElement = domElement - function epsilon(value) { - return Math.abs(value) < 1e-10 ? 0 : value - } + const viewElement = document.createElement('div') + viewElement.style.transformOrigin = '0 0' + viewElement.style.pointerEvents = 'none' + domElement.appendChild(viewElement) - function getCameraCSSMatrix(matrix) { - var elements = matrix.elements - - return ( - 'matrix3d(' + - epsilon(elements[0]) + - ',' + - epsilon(-elements[1]) + - ',' + - epsilon(elements[2]) + - ',' + - epsilon(elements[3]) + - ',' + - epsilon(elements[4]) + - ',' + - epsilon(-elements[5]) + - ',' + - epsilon(elements[6]) + - ',' + - epsilon(elements[7]) + - ',' + - epsilon(elements[8]) + - ',' + - epsilon(-elements[9]) + - ',' + - epsilon(elements[10]) + - ',' + - epsilon(elements[11]) + - ',' + - epsilon(elements[12]) + - ',' + - epsilon(-elements[13]) + - ',' + - epsilon(elements[14]) + - ',' + - epsilon(elements[15]) + - ')' - ) - } + const cameraElement = document.createElement('div') - function getObjectCSSMatrix(matrix) { - var elements = matrix.elements - var matrix3d = - 'matrix3d(' + - epsilon(elements[0]) + - ',' + - epsilon(elements[1]) + - ',' + - epsilon(elements[2]) + - ',' + - epsilon(elements[3]) + - ',' + - epsilon(-elements[4]) + - ',' + - epsilon(-elements[5]) + - ',' + - epsilon(-elements[6]) + - ',' + - epsilon(-elements[7]) + - ',' + - epsilon(elements[8]) + - ',' + - epsilon(elements[9]) + - ',' + - epsilon(elements[10]) + - ',' + - epsilon(elements[11]) + - ',' + - epsilon(elements[12]) + - ',' + - epsilon(elements[13]) + - ',' + - epsilon(elements[14]) + - ',' + - epsilon(elements[15]) + - ')' - - return 'translate(-50%,-50%)' + matrix3d - } + cameraElement.style.transformStyle = 'preserve-3d' - function renderObject(object, scene, camera, cameraCSSMatrix) { - if (object instanceof CSS3DObject) { - object.onBeforeRender(_this, scene, camera) + viewElement.appendChild(cameraElement) - var style + this.getSize = function () { + return { + width: _width, + height: _height, + } + } - if (object instanceof CSS3DSprite) { - // http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/ + this.render = function (scene, camera) { + const fov = camera.projectionMatrix.elements[5] * _heightHalf - matrix.copy(camera.matrixWorldInverse) - matrix.transpose() - matrix.copyPosition(object.matrixWorld) - matrix.scale(object.scale) + if (cache.camera.fov !== fov) { + viewElement.style.perspective = camera.isPerspectiveCamera ? fov + 'px' : '' + cache.camera.fov = fov + } - matrix.elements[3] = 0 - matrix.elements[7] = 0 - matrix.elements[11] = 0 - matrix.elements[15] = 1 + if (camera.view && camera.view.enabled) { + // view offset + viewElement.style.transform = `translate( ${-camera.view.offsetX * (_width / camera.view.width)}px, ${ + -camera.view.offsetY * (_height / camera.view.height) + }px )` - style = getObjectCSSMatrix(matrix) + // view fullWidth and fullHeight, view width and height + viewElement.style.transform += `scale( ${camera.view.fullWidth / camera.view.width}, ${ + camera.view.fullHeight / camera.view.height + } )` } else { - style = getObjectCSSMatrix(object.matrixWorld) + viewElement.style.transform = '' } - var element = object.element - var cachedObject = cache.objects.get(object) + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld() + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld() - if (cachedObject === undefined || cachedObject.style !== style) { - element.style.transform = style + let tx, ty - var objectData = { style: style } - cache.objects.set(object, objectData) + if (camera.isOrthographicCamera) { + tx = -(camera.right + camera.left) / 2 + ty = (camera.top + camera.bottom) / 2 } - element.style.display = object.visible ? '' : 'none' - - if (element.parentNode !== cameraElement) { - cameraElement.appendChild(element) + const scaleByViewOffset = camera.view && camera.view.enabled ? camera.view.height / camera.view.fullHeight : 1 + const cameraCSSMatrix = camera.isOrthographicCamera + ? `scale( ${scaleByViewOffset} )` + + 'scale(' + + fov + + ')' + + 'translate(' + + epsilon(tx) + + 'px,' + + epsilon(ty) + + 'px)' + + getCameraCSSMatrix(camera.matrixWorldInverse) + : `scale( ${scaleByViewOffset} )` + 'translateZ(' + fov + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse) + + const style = cameraCSSMatrix + 'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)' + + if (cache.camera.style !== style) { + cameraElement.style.transform = style + + cache.camera.style = style } - object.onAfterRender(_this, scene, camera) + renderObject(scene, scene, camera, cameraCSSMatrix) } - for (let i = 0, l = object.children.length; i < l; i++) { - renderObject(object.children[i], scene, camera, cameraCSSMatrix) - } - } + this.setSize = function (width, height) { + _width = width + _height = height + _widthHalf = _width / 2 + _heightHalf = _height / 2 + + domElement.style.width = width + 'px' + domElement.style.height = height + 'px' - this.render = function (scene, camera) { - var fov = camera.projectionMatrix.elements[5] * _heightHalf + viewElement.style.width = width + 'px' + viewElement.style.height = height + 'px' - if (cache.camera.fov !== fov) { - domElement.style.perspective = camera.isPerspectiveCamera ? fov + 'px' : '' - cache.camera.fov = fov + cameraElement.style.width = width + 'px' + cameraElement.style.height = height + 'px' } - if (scene.autoUpdate === true) scene.updateMatrixWorld() - if (camera.parent === null) camera.updateMatrixWorld() + function epsilon(value) { + return Math.abs(value) < 1e-10 ? 0 : value + } - if (camera.isOrthographicCamera) { - var tx = -(camera.right + camera.left) / 2 - var ty = (camera.top + camera.bottom) / 2 + function getCameraCSSMatrix(matrix) { + const elements = matrix.elements + + return ( + 'matrix3d(' + + epsilon(elements[0]) + + ',' + + epsilon(-elements[1]) + + ',' + + epsilon(elements[2]) + + ',' + + epsilon(elements[3]) + + ',' + + epsilon(elements[4]) + + ',' + + epsilon(-elements[5]) + + ',' + + epsilon(elements[6]) + + ',' + + epsilon(elements[7]) + + ',' + + epsilon(elements[8]) + + ',' + + epsilon(-elements[9]) + + ',' + + epsilon(elements[10]) + + ',' + + epsilon(elements[11]) + + ',' + + epsilon(elements[12]) + + ',' + + epsilon(-elements[13]) + + ',' + + epsilon(elements[14]) + + ',' + + epsilon(elements[15]) + + ')' + ) } - var cameraCSSMatrix = camera.isOrthographicCamera - ? 'scale(' + - fov + - ')' + - 'translate(' + - epsilon(tx) + - 'px,' + - epsilon(ty) + - 'px)' + - getCameraCSSMatrix(camera.matrixWorldInverse) - : 'translateZ(' + fov + 'px)' + getCameraCSSMatrix(camera.matrixWorldInverse) + function getObjectCSSMatrix(matrix) { + const elements = matrix.elements + const matrix3d = + 'matrix3d(' + + epsilon(elements[0]) + + ',' + + epsilon(elements[1]) + + ',' + + epsilon(elements[2]) + + ',' + + epsilon(elements[3]) + + ',' + + epsilon(-elements[4]) + + ',' + + epsilon(-elements[5]) + + ',' + + epsilon(-elements[6]) + + ',' + + epsilon(-elements[7]) + + ',' + + epsilon(elements[8]) + + ',' + + epsilon(elements[9]) + + ',' + + epsilon(elements[10]) + + ',' + + epsilon(elements[11]) + + ',' + + epsilon(elements[12]) + + ',' + + epsilon(elements[13]) + + ',' + + epsilon(elements[14]) + + ',' + + epsilon(elements[15]) + + ')' + + return 'translate(-50%,-50%)' + matrix3d + } - var style = cameraCSSMatrix + 'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)' + function renderObject(object, scene, camera, cameraCSSMatrix) { + if (object.isCSS3DObject) { + const visible = object.visible === true && object.layers.test(camera.layers) === true + object.element.style.display = visible === true ? '' : 'none' - if (cache.camera.style !== style) { - cameraElement.style.transform = style + if (visible === true) { + object.onBeforeRender(_this, scene, camera) - cache.camera.style = style - } + let style + + if (object.isCSS3DSprite) { + // http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/ + + _matrix.copy(camera.matrixWorldInverse) + _matrix.transpose() + + if (object.rotation2D !== 0) _matrix.multiply(_matrix2.makeRotationZ(object.rotation2D)) - renderObject(scene, scene, camera, cameraCSSMatrix) + object.matrixWorld.decompose(_position, _quaternion, _scale) + _matrix.setPosition(_position) + _matrix.scale(_scale) + + _matrix.elements[3] = 0 + _matrix.elements[7] = 0 + _matrix.elements[11] = 0 + _matrix.elements[15] = 1 + + style = getObjectCSSMatrix(_matrix) + } else { + style = getObjectCSSMatrix(object.matrixWorld) + } + + const element = object.element + const cachedObject = cache.objects.get(object) + + if (cachedObject === undefined || cachedObject.style !== style) { + element.style.transform = style + + const objectData = { style: style } + cache.objects.set(object, objectData) + } + + if (element.parentNode !== cameraElement) { + cameraElement.appendChild(element) + } + + object.onAfterRender(_this, scene, camera) + } + } + + for (let i = 0, l = object.children.length; i < l; i++) { + renderObject(object.children[i], scene, camera, cameraCSSMatrix) + } + } } } diff --git a/src/renderers/Projector.js b/src/renderers/Projector.js index bbf10564..21b8907d 100644 --- a/src/renderers/Projector.js +++ b/src/renderers/Projector.js @@ -1,416 +1,412 @@ import { Box3, Color, DoubleSide, Frustum, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three' -var RenderableObject = function () { - this.id = 0 +class RenderableObject { + constructor() { + this.id = 0 - this.object = null - this.z = 0 - this.renderOrder = 0 + this.object = null + this.z = 0 + this.renderOrder = 0 + } } // -var RenderableFace = function () { - this.id = 0 +class RenderableFace { + constructor() { + this.id = 0 - this.v1 = new RenderableVertex() - this.v2 = new RenderableVertex() - this.v3 = new RenderableVertex() + this.v1 = new RenderableVertex() + this.v2 = new RenderableVertex() + this.v3 = new RenderableVertex() - this.normalModel = new Vector3() + this.normalModel = new Vector3() - this.vertexNormalsModel = [new Vector3(), new Vector3(), new Vector3()] - this.vertexNormalsLength = 0 + this.vertexNormalsModel = [new Vector3(), new Vector3(), new Vector3()] + this.vertexNormalsLength = 0 - this.color = new Color() - this.material = null - this.uvs = [new Vector2(), new Vector2(), new Vector2()] + this.color = new Color() + this.material = null + this.uvs = [new Vector2(), new Vector2(), new Vector2()] - this.z = 0 - this.renderOrder = 0 + this.z = 0 + this.renderOrder = 0 + } } // -var RenderableVertex = function () { - this.position = new Vector3() - this.positionWorld = new Vector3() - this.positionScreen = new Vector4() +class RenderableVertex { + constructor() { + this.position = new Vector3() + this.positionWorld = new Vector3() + this.positionScreen = new Vector4() - this.visible = true -} + this.visible = true + } -RenderableVertex.prototype.copy = function (vertex) { - this.positionWorld.copy(vertex.positionWorld) - this.positionScreen.copy(vertex.positionScreen) + copy(vertex) { + this.positionWorld.copy(vertex.positionWorld) + this.positionScreen.copy(vertex.positionScreen) + } } // -var RenderableLine = function () { - this.id = 0 +class RenderableLine { + constructor() { + this.id = 0 - this.v1 = new RenderableVertex() - this.v2 = new RenderableVertex() + this.v1 = new RenderableVertex() + this.v2 = new RenderableVertex() - this.vertexColors = [new Color(), new Color()] - this.material = null + this.vertexColors = [new Color(), new Color()] + this.material = null - this.z = 0 - this.renderOrder = 0 + this.z = 0 + this.renderOrder = 0 + } } // -var RenderableSprite = function () { - this.id = 0 +class RenderableSprite { + constructor() { + this.id = 0 - this.object = null + this.object = null - this.x = 0 - this.y = 0 - this.z = 0 + this.x = 0 + this.y = 0 + this.z = 0 - this.rotation = 0 - this.scale = new Vector2() + this.rotation = 0 + this.scale = new Vector2() - this.material = null - this.renderOrder = 0 + this.material = null + this.renderOrder = 0 + } } // -var Projector = function () { - var _object, - _objectCount, - _objectPool = [], - _objectPoolLength = 0, - _vertex, - _vertexCount, - _vertexPool = [], - _vertexPoolLength = 0, - _face, - _faceCount, - _facePool = [], - _facePoolLength = 0, - _line, - _lineCount, - _linePool = [], - _linePoolLength = 0, - _sprite, - _spriteCount, - _spritePool = [], - _spritePoolLength = 0, - _renderData = { objects: [], lights: [], elements: [] }, - _vector3 = new Vector3(), - _vector4 = new Vector4(), - _clipBox = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)), - _boundingBox = new Box3(), - _points3 = new Array(3), - _viewMatrix = new Matrix4(), - _viewProjectionMatrix = new Matrix4(), - _modelMatrix, - _modelViewProjectionMatrix = new Matrix4(), - _frustum = new Frustum() - - // - - this.projectVector = function (vector, camera) { - console.warn('THREE.Projector: .projectVector() is now vector.project().') - vector.project(camera) - } - - this.unprojectVector = function (vector, camera) { - console.warn('THREE.Projector: .unprojectVector() is now vector.unproject().') - vector.unproject(camera) - } +class Projector { + constructor() { + let _object, + _objectCount, + _objectPoolLength = 0, + _vertex, + _vertexCount, + _vertexPoolLength = 0, + _face, + _faceCount, + _facePoolLength = 0, + _line, + _lineCount, + _linePoolLength = 0, + _sprite, + _spriteCount, + _spritePoolLength = 0, + _modelMatrix + + const _renderData = { objects: [], lights: [], elements: [] }, + _vector3 = new Vector3(), + _vector4 = new Vector4(), + _clipBox = new Box3(new Vector3(-1, -1, -1), new Vector3(1, 1, 1)), + _boundingBox = new Box3(), + _points3 = new Array(3), + _viewMatrix = new Matrix4(), + _viewProjectionMatrix = new Matrix4(), + _modelViewProjectionMatrix = new Matrix4(), + _frustum = new Frustum(), + _objectPool = [], + _vertexPool = [], + _facePool = [], + _linePool = [], + _spritePool = [] - this.pickingRay = function () { - console.error('THREE.Projector: .pickingRay() is now raycaster.setFromCamera().') - } + // - // + function RenderList() { + const normals = [] + const colors = [] + const uvs = [] - var RenderList = function () { - var normals = [] - var colors = [] - var uvs = [] + let object = null - var object = null + const normalMatrix = new Matrix3() - var normalMatrix = new Matrix3() + function setObject(value) { + object = value - function setObject(value) { - object = value + normalMatrix.getNormalMatrix(object.matrixWorld) - normalMatrix.getNormalMatrix(object.matrixWorld) + normals.length = 0 + colors.length = 0 + uvs.length = 0 + } - normals.length = 0 - colors.length = 0 - uvs.length = 0 - } + function projectVertex(vertex) { + const position = vertex.position + const positionWorld = vertex.positionWorld + const positionScreen = vertex.positionScreen - function projectVertex(vertex) { - var position = vertex.position - var positionWorld = vertex.positionWorld - var positionScreen = vertex.positionScreen + positionWorld.copy(position).applyMatrix4(_modelMatrix) + positionScreen.copy(positionWorld).applyMatrix4(_viewProjectionMatrix) - positionWorld.copy(position).applyMatrix4(_modelMatrix) - positionScreen.copy(positionWorld).applyMatrix4(_viewProjectionMatrix) + const invW = 1 / positionScreen.w - var invW = 1 / positionScreen.w + positionScreen.x *= invW + positionScreen.y *= invW + positionScreen.z *= invW - positionScreen.x *= invW - positionScreen.y *= invW - positionScreen.z *= invW + vertex.visible = + positionScreen.x >= -1 && + positionScreen.x <= 1 && + positionScreen.y >= -1 && + positionScreen.y <= 1 && + positionScreen.z >= -1 && + positionScreen.z <= 1 + } - vertex.visible = - positionScreen.x >= -1 && - positionScreen.x <= 1 && - positionScreen.y >= -1 && - positionScreen.y <= 1 && - positionScreen.z >= -1 && - positionScreen.z <= 1 - } + function pushVertex(x, y, z) { + _vertex = getNextVertexInPool() + _vertex.position.set(x, y, z) - function pushVertex(x, y, z) { - _vertex = getNextVertexInPool() - _vertex.position.set(x, y, z) + projectVertex(_vertex) + } - projectVertex(_vertex) - } + function pushNormal(x, y, z) { + normals.push(x, y, z) + } - function pushNormal(x, y, z) { - normals.push(x, y, z) - } + function pushColor(r, g, b) { + colors.push(r, g, b) + } - function pushColor(r, g, b) { - colors.push(r, g, b) - } + function pushUv(x, y) { + uvs.push(x, y) + } - function pushUv(x, y) { - uvs.push(x, y) - } + function checkTriangleVisibility(v1, v2, v3) { + if (v1.visible === true || v2.visible === true || v3.visible === true) return true - function checkTriangleVisibility(v1, v2, v3) { - if (v1.visible === true || v2.visible === true || v3.visible === true) return true + _points3[0] = v1.positionScreen + _points3[1] = v2.positionScreen + _points3[2] = v3.positionScreen - _points3[0] = v1.positionScreen - _points3[1] = v2.positionScreen - _points3[2] = v3.positionScreen + return _clipBox.intersectsBox(_boundingBox.setFromPoints(_points3)) + } - return _clipBox.intersectsBox(_boundingBox.setFromPoints(_points3)) - } + function checkBackfaceCulling(v1, v2, v3) { + return ( + (v3.positionScreen.x - v1.positionScreen.x) * (v2.positionScreen.y - v1.positionScreen.y) - + (v3.positionScreen.y - v1.positionScreen.y) * (v2.positionScreen.x - v1.positionScreen.x) < + 0 + ) + } - function checkBackfaceCulling(v1, v2, v3) { - return ( - (v3.positionScreen.x - v1.positionScreen.x) * (v2.positionScreen.y - v1.positionScreen.y) - - (v3.positionScreen.y - v1.positionScreen.y) * (v2.positionScreen.x - v1.positionScreen.x) < - 0 - ) - } + function pushLine(a, b) { + const v1 = _vertexPool[a] + const v2 = _vertexPool[b] - function pushLine(a, b) { - var v1 = _vertexPool[a] - var v2 = _vertexPool[b] + // Clip - // Clip + v1.positionScreen.copy(v1.position).applyMatrix4(_modelViewProjectionMatrix) + v2.positionScreen.copy(v2.position).applyMatrix4(_modelViewProjectionMatrix) - v1.positionScreen.copy(v1.position).applyMatrix4(_modelViewProjectionMatrix) - v2.positionScreen.copy(v2.position).applyMatrix4(_modelViewProjectionMatrix) + if (clipLine(v1.positionScreen, v2.positionScreen) === true) { + // Perform the perspective divide + v1.positionScreen.multiplyScalar(1 / v1.positionScreen.w) + v2.positionScreen.multiplyScalar(1 / v2.positionScreen.w) - if (clipLine(v1.positionScreen, v2.positionScreen) === true) { - // Perform the perspective divide - v1.positionScreen.multiplyScalar(1 / v1.positionScreen.w) - v2.positionScreen.multiplyScalar(1 / v2.positionScreen.w) + _line = getNextLineInPool() + _line.id = object.id + _line.v1.copy(v1) + _line.v2.copy(v2) + _line.z = Math.max(v1.positionScreen.z, v2.positionScreen.z) + _line.renderOrder = object.renderOrder - _line = getNextLineInPool() - _line.id = object.id - _line.v1.copy(v1) - _line.v2.copy(v2) - _line.z = Math.max(v1.positionScreen.z, v2.positionScreen.z) - _line.renderOrder = object.renderOrder + _line.material = object.material - _line.material = object.material + if (object.material.vertexColors) { + _line.vertexColors[0].fromArray(colors, a * 3) + _line.vertexColors[1].fromArray(colors, b * 3) + } - if (object.material.vertexColors) { - _line.vertexColors[0].fromArray(colors, a * 3) - _line.vertexColors[1].fromArray(colors, b * 3) + _renderData.elements.push(_line) } - - _renderData.elements.push(_line) } - } - function pushTriangle(a, b, c, material) { - var v1 = _vertexPool[a] - var v2 = _vertexPool[b] - var v3 = _vertexPool[c] - - if (checkTriangleVisibility(v1, v2, v3) === false) return - - if (material.side === DoubleSide || checkBackfaceCulling(v1, v2, v3) === true) { - _face = getNextFaceInPool() - - _face.id = object.id - _face.v1.copy(v1) - _face.v2.copy(v2) - _face.v3.copy(v3) - _face.z = (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3 - _face.renderOrder = object.renderOrder - - // face normal - _vector3.subVectors(v3.position, v2.position) - _vector4.subVectors(v1.position, v2.position) - _vector3.cross(_vector4) - _face.normalModel.copy(_vector3) - _face.normalModel.applyMatrix3(normalMatrix).normalize() - - for (let i = 0; i < 3; i++) { - var normal = _face.vertexNormalsModel[i] - normal.fromArray(normals, arguments[i] * 3) - normal.applyMatrix3(normalMatrix).normalize() - - var uv = _face.uvs[i] - uv.fromArray(uvs, arguments[i] * 2) - } + function pushTriangle(a, b, c, material) { + const v1 = _vertexPool[a] + const v2 = _vertexPool[b] + const v3 = _vertexPool[c] + + if (checkTriangleVisibility(v1, v2, v3) === false) return + + if (material.side === DoubleSide || checkBackfaceCulling(v1, v2, v3) === true) { + _face = getNextFaceInPool() + + _face.id = object.id + _face.v1.copy(v1) + _face.v2.copy(v2) + _face.v3.copy(v3) + _face.z = (v1.positionScreen.z + v2.positionScreen.z + v3.positionScreen.z) / 3 + _face.renderOrder = object.renderOrder + + // face normal + _vector3.subVectors(v3.position, v2.position) + _vector4.subVectors(v1.position, v2.position) + _vector3.cross(_vector4) + _face.normalModel.copy(_vector3) + _face.normalModel.applyMatrix3(normalMatrix).normalize() + + for (let i = 0; i < 3; i++) { + const normal = _face.vertexNormalsModel[i] + normal.fromArray(normals, arguments[i] * 3) + normal.applyMatrix3(normalMatrix).normalize() + + const uv = _face.uvs[i] + uv.fromArray(uvs, arguments[i] * 2) + } - _face.vertexNormalsLength = 3 + _face.vertexNormalsLength = 3 - _face.material = material + _face.material = material - if (material.vertexColors) { - _face.color.fromArray(colors, a * 3) - } + if (material.vertexColors) { + _face.color.fromArray(colors, a * 3) + } - _renderData.elements.push(_face) + _renderData.elements.push(_face) + } } - } - return { - setObject: setObject, - projectVertex: projectVertex, - checkTriangleVisibility: checkTriangleVisibility, - checkBackfaceCulling: checkBackfaceCulling, - pushVertex: pushVertex, - pushNormal: pushNormal, - pushColor: pushColor, - pushUv: pushUv, - pushLine: pushLine, - pushTriangle: pushTriangle, + return { + setObject: setObject, + projectVertex: projectVertex, + checkTriangleVisibility: checkTriangleVisibility, + checkBackfaceCulling: checkBackfaceCulling, + pushVertex: pushVertex, + pushNormal: pushNormal, + pushColor: pushColor, + pushUv: pushUv, + pushLine: pushLine, + pushTriangle: pushTriangle, + } } - } - var renderList = new RenderList() + const renderList = new RenderList() - function projectObject(object) { - if (object.visible === false) return + function projectObject(object) { + if (object.visible === false) return - if (object.isLight) { - _renderData.lights.push(object) - } else if (object.isMesh || object.isLine || object.isPoints) { - if (object.material.visible === false) return - if (object.frustumCulled === true && _frustum.intersectsObject(object) === false) return + if (object.isLight) { + _renderData.lights.push(object) + } else if (object.isMesh || object.isLine || object.isPoints) { + if (object.material.visible === false) return + if (object.frustumCulled === true && _frustum.intersectsObject(object) === false) return - addObject(object) - } else if (object.isSprite) { - if (object.material.visible === false) return - if (object.frustumCulled === true && _frustum.intersectsSprite(object) === false) return + addObject(object) + } else if (object.isSprite) { + if (object.material.visible === false) return + if (object.frustumCulled === true && _frustum.intersectsSprite(object) === false) return - addObject(object) - } + addObject(object) + } - var children = object.children + const children = object.children - for (let i = 0, l = children.length; i < l; i++) { - projectObject(children[i]) + for (let i = 0, l = children.length; i < l; i++) { + projectObject(children[i]) + } } - } - function addObject(object) { - _object = getNextObjectInPool() - _object.id = object.id - _object.object = object + function addObject(object) { + _object = getNextObjectInPool() + _object.id = object.id + _object.object = object - _vector3.setFromMatrixPosition(object.matrixWorld) - _vector3.applyMatrix4(_viewProjectionMatrix) - _object.z = _vector3.z - _object.renderOrder = object.renderOrder + _vector3.setFromMatrixPosition(object.matrixWorld) + _vector3.applyMatrix4(_viewProjectionMatrix) + _object.z = _vector3.z + _object.renderOrder = object.renderOrder - _renderData.objects.push(_object) - } + _renderData.objects.push(_object) + } - this.projectScene = function (scene, camera, sortObjects, sortElements) { - _faceCount = 0 - _lineCount = 0 - _spriteCount = 0 + this.projectScene = function (scene, camera, sortObjects, sortElements) { + _faceCount = 0 + _lineCount = 0 + _spriteCount = 0 - _renderData.elements.length = 0 + _renderData.elements.length = 0 - if (scene.autoUpdate === true) scene.updateMatrixWorld() - if (camera.parent === null) camera.updateMatrixWorld() + if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld() + if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld() - _viewMatrix.copy(camera.matrixWorldInverse) - _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix) + _viewMatrix.copy(camera.matrixWorldInverse) + _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix) - _frustum.setFromProjectionMatrix(_viewProjectionMatrix) + _frustum.setFromProjectionMatrix(_viewProjectionMatrix) - // + // - _objectCount = 0 + _objectCount = 0 - _renderData.objects.length = 0 - _renderData.lights.length = 0 + _renderData.objects.length = 0 + _renderData.lights.length = 0 - projectObject(scene) + projectObject(scene) - if (sortObjects === true) { - _renderData.objects.sort(painterSort) - } + if (sortObjects === true) { + _renderData.objects.sort(painterSort) + } - // + // - var objects = _renderData.objects + const objects = _renderData.objects - for (let o = 0, ol = objects.length; o < ol; o++) { - var object = objects[o].object - var geometry = object.geometry + for (let o = 0, ol = objects.length; o < ol; o++) { + const object = objects[o].object + const geometry = object.geometry - renderList.setObject(object) + renderList.setObject(object) - _modelMatrix = object.matrixWorld + _modelMatrix = object.matrixWorld - _vertexCount = 0 + _vertexCount = 0 - if (object.isMesh) { - if (geometry.isBufferGeometry) { - var material = object.material + if (object.isMesh) { + let material = object.material - var isMultiMaterial = Array.isArray(material) + const isMultiMaterial = Array.isArray(material) - var attributes = geometry.attributes - var groups = geometry.groups + const attributes = geometry.attributes + const groups = geometry.groups if (attributes.position === undefined) continue - var positions = attributes.position.array + const positions = attributes.position.array for (let i = 0, l = positions.length; i < l; i += 3) { - var x = positions[i] - var y = positions[i + 1] - var z = positions[i + 2] + let x = positions[i] + let y = positions[i + 1] + let z = positions[i + 2] + + const morphTargets = geometry.morphAttributes.position - if (material.morphTargets === true) { - var morphTargets = geometry.morphAttributes.position - var morphTargetsRelative = geometry.morphTargetsRelative - var morphInfluences = object.morphTargetInfluences + if (morphTargets !== undefined) { + const morphTargetsRelative = geometry.morphTargetsRelative + const morphInfluences = object.morphTargetInfluences for (let t = 0, tl = morphTargets.length; t < tl; t++) { - var influence = morphInfluences[t] + const influence = morphInfluences[t] if (influence === 0) continue - var target = morphTargets[t] + const target = morphTargets[t] if (morphTargetsRelative) { x += target.getX(i / 3) * influence @@ -428,7 +424,7 @@ var Projector = function () { } if (attributes.normal !== undefined) { - var normals = attributes.normal.array + const normals = attributes.normal.array for (let i = 0, l = normals.length; i < l; i += 3) { renderList.pushNormal(normals[i], normals[i + 1], normals[i + 2]) @@ -436,7 +432,7 @@ var Projector = function () { } if (attributes.color !== undefined) { - var colors = attributes.color.array + const colors = attributes.color.array for (let i = 0, l = colors.length; i < l; i += 3) { renderList.pushColor(colors[i], colors[i + 1], colors[i + 2]) @@ -444,7 +440,7 @@ var Projector = function () { } if (attributes.uv !== undefined) { - var uvs = attributes.uv.array + const uvs = attributes.uv.array for (let i = 0, l = uvs.length; i < l; i += 2) { renderList.pushUv(uvs[i], uvs[i + 1]) @@ -452,11 +448,11 @@ var Projector = function () { } if (geometry.index !== null) { - var indices = geometry.index.array + const indices = geometry.index.array if (groups.length > 0) { for (let g = 0; g < groups.length; g++) { - var group = groups[g] + const group = groups[g] material = isMultiMaterial === true ? object.material[group.materialIndex] : object.material @@ -474,7 +470,7 @@ var Projector = function () { } else { if (groups.length > 0) { for (let g = 0; g < groups.length; g++) { - var group = groups[g] + const group = groups[g] material = isMultiMaterial === true ? object.material[group.materialIndex] : object.material @@ -490,25 +486,20 @@ var Projector = function () { } } } - } else if (geometry.isGeometry) { - console.error('THREE.Projector no longer supports Geometry. Use THREE.BufferGeometry instead.') - return - } - } else if (object.isLine) { - _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix) + } else if (object.isLine) { + _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix) - if (geometry.isBufferGeometry) { - var attributes = geometry.attributes + const attributes = geometry.attributes if (attributes.position !== undefined) { - var positions = attributes.position.array + const positions = attributes.position.array for (let i = 0, l = positions.length; i < l; i += 3) { renderList.pushVertex(positions[i], positions[i + 1], positions[i + 2]) } if (attributes.color !== undefined) { - var colors = attributes.color.array + const colors = attributes.color.array for (let i = 0, l = colors.length; i < l; i += 3) { renderList.pushColor(colors[i], colors[i + 1], colors[i + 2]) @@ -516,34 +507,26 @@ var Projector = function () { } if (geometry.index !== null) { - var indices = geometry.index.array + const indices = geometry.index.array for (let i = 0, l = indices.length; i < l; i += 2) { renderList.pushLine(indices[i], indices[i + 1]) } } else { - var step = object.isLineSegments ? 2 : 1 + const step = object.isLineSegments ? 2 : 1 for (let i = 0, l = positions.length / 3 - 1; i < l; i += step) { renderList.pushLine(i, i + 1) } } } - } else if (geometry.isGeometry) { - console.error('THREE.Projector no longer supports Geometry. Use THREE.BufferGeometry instead.') - return - } - } else if (object.isPoints) { - _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix) + } else if (object.isPoints) { + _modelViewProjectionMatrix.multiplyMatrices(_viewProjectionMatrix, _modelMatrix) - if (geometry.isGeometry) { - console.error('THREE.Projector no longer supports Geometry. Use THREE.BufferGeometry instead.') - return - } else if (geometry.isBufferGeometry) { - var attributes = geometry.attributes + const attributes = geometry.attributes if (attributes.position !== undefined) { - var positions = attributes.position.array + const positions = attributes.position.array for (let i = 0, l = positions.length; i < l; i += 3) { _vector4.set(positions[i], positions[i + 1], positions[i + 2], 1) @@ -552,181 +535,182 @@ var Projector = function () { pushPoint(_vector4, object, camera) } } + } else if (object.isSprite) { + object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld) + _vector4.set(_modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1) + _vector4.applyMatrix4(_viewProjectionMatrix) + + pushPoint(_vector4, object, camera) } - } else if (object.isSprite) { - object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld) - _vector4.set(_modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1) - _vector4.applyMatrix4(_viewProjectionMatrix) + } - pushPoint(_vector4, object, camera) + if (sortElements === true) { + _renderData.elements.sort(painterSort) } - } - if (sortElements === true) { - _renderData.elements.sort(painterSort) + return _renderData } - return _renderData - } - - function pushPoint(_vector4, object, camera) { - var invW = 1 / _vector4.w - - _vector4.z *= invW - - if (_vector4.z >= -1 && _vector4.z <= 1) { - _sprite = getNextSpriteInPool() - _sprite.id = object.id - _sprite.x = _vector4.x * invW - _sprite.y = _vector4.y * invW - _sprite.z = _vector4.z - _sprite.renderOrder = object.renderOrder - _sprite.object = object + function pushPoint(_vector4, object, camera) { + const invW = 1 / _vector4.w + + _vector4.z *= invW + + if (_vector4.z >= -1 && _vector4.z <= 1) { + _sprite = getNextSpriteInPool() + _sprite.id = object.id + _sprite.x = _vector4.x * invW + _sprite.y = _vector4.y * invW + _sprite.z = _vector4.z + _sprite.renderOrder = object.renderOrder + _sprite.object = object + + _sprite.rotation = object.rotation + + _sprite.scale.x = + object.scale.x * + Math.abs( + _sprite.x - + (_vector4.x + camera.projectionMatrix.elements[0]) / (_vector4.w + camera.projectionMatrix.elements[12]), + ) + _sprite.scale.y = + object.scale.y * + Math.abs( + _sprite.y - + (_vector4.y + camera.projectionMatrix.elements[5]) / (_vector4.w + camera.projectionMatrix.elements[13]), + ) + + _sprite.material = object.material + + _renderData.elements.push(_sprite) + } + } - _sprite.rotation = object.rotation + // Pools - _sprite.scale.x = - object.scale.x * - Math.abs( - _sprite.x - - (_vector4.x + camera.projectionMatrix.elements[0]) / (_vector4.w + camera.projectionMatrix.elements[12]), - ) - _sprite.scale.y = - object.scale.y * - Math.abs( - _sprite.y - - (_vector4.y + camera.projectionMatrix.elements[5]) / (_vector4.w + camera.projectionMatrix.elements[13]), - ) - - _sprite.material = object.material + function getNextObjectInPool() { + if (_objectCount === _objectPoolLength) { + const object = new RenderableObject() + _objectPool.push(object) + _objectPoolLength++ + _objectCount++ + return object + } - _renderData.elements.push(_sprite) + return _objectPool[_objectCount++] } - } - // Pools + function getNextVertexInPool() { + if (_vertexCount === _vertexPoolLength) { + const vertex = new RenderableVertex() + _vertexPool.push(vertex) + _vertexPoolLength++ + _vertexCount++ + return vertex + } - function getNextObjectInPool() { - if (_objectCount === _objectPoolLength) { - var object = new RenderableObject() - _objectPool.push(object) - _objectPoolLength++ - _objectCount++ - return object + return _vertexPool[_vertexCount++] } - return _objectPool[_objectCount++] - } + function getNextFaceInPool() { + if (_faceCount === _facePoolLength) { + const face = new RenderableFace() + _facePool.push(face) + _facePoolLength++ + _faceCount++ + return face + } - function getNextVertexInPool() { - if (_vertexCount === _vertexPoolLength) { - var vertex = new RenderableVertex() - _vertexPool.push(vertex) - _vertexPoolLength++ - _vertexCount++ - return vertex + return _facePool[_faceCount++] } - return _vertexPool[_vertexCount++] - } + function getNextLineInPool() { + if (_lineCount === _linePoolLength) { + const line = new RenderableLine() + _linePool.push(line) + _linePoolLength++ + _lineCount++ + return line + } - function getNextFaceInPool() { - if (_faceCount === _facePoolLength) { - var face = new RenderableFace() - _facePool.push(face) - _facePoolLength++ - _faceCount++ - return face + return _linePool[_lineCount++] } - return _facePool[_faceCount++] - } + function getNextSpriteInPool() { + if (_spriteCount === _spritePoolLength) { + const sprite = new RenderableSprite() + _spritePool.push(sprite) + _spritePoolLength++ + _spriteCount++ + return sprite + } - function getNextLineInPool() { - if (_lineCount === _linePoolLength) { - var line = new RenderableLine() - _linePool.push(line) - _linePoolLength++ - _lineCount++ - return line + return _spritePool[_spriteCount++] } - return _linePool[_lineCount++] - } + // - function getNextSpriteInPool() { - if (_spriteCount === _spritePoolLength) { - var sprite = new RenderableSprite() - _spritePool.push(sprite) - _spritePoolLength++ - _spriteCount++ - return sprite + function painterSort(a, b) { + if (a.renderOrder !== b.renderOrder) { + return a.renderOrder - b.renderOrder + } else if (a.z !== b.z) { + return b.z - a.z + } else if (a.id !== b.id) { + return a.id - b.id + } else { + return 0 + } } - return _spritePool[_spriteCount++] - } + function clipLine(s1, s2) { + let alpha1 = 0, + alpha2 = 1 - // - - function painterSort(a, b) { - if (a.renderOrder !== b.renderOrder) { - return a.renderOrder - b.renderOrder - } else if (a.z !== b.z) { - return b.z - a.z - } else if (a.id !== b.id) { - return a.id - b.id - } else { - return 0 - } - } - - function clipLine(s1, s2) { - var alpha1 = 0, - alpha2 = 1, // Calculate the boundary coordinate of each vertex for the near and far clip planes, // Z = -1 and Z = +1, respectively. - bc1near = s1.z + s1.w, - bc2near = s2.z + s2.w, - bc1far = -s1.z + s1.w, - bc2far = -s2.z + s2.w - - if (bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0) { - // Both vertices lie entirely within all clip planes. - return true - } else if ((bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0)) { - // Both vertices lie entirely outside one of the clip planes. - return false - } else { - // The line segment spans at least one clip plane. - - if (bc1near < 0) { - // v1 lies outside the near plane, v2 inside - alpha1 = Math.max(alpha1, bc1near / (bc1near - bc2near)) - } else if (bc2near < 0) { - // v2 lies outside the near plane, v1 inside - alpha2 = Math.min(alpha2, bc1near / (bc1near - bc2near)) - } - - if (bc1far < 0) { - // v1 lies outside the far plane, v2 inside - alpha1 = Math.max(alpha1, bc1far / (bc1far - bc2far)) - } else if (bc2far < 0) { - // v2 lies outside the far plane, v2 inside - alpha2 = Math.min(alpha2, bc1far / (bc1far - bc2far)) - } + const bc1near = s1.z + s1.w, + bc2near = s2.z + s2.w, + bc1far = -s1.z + s1.w, + bc2far = -s2.z + s2.w - if (alpha2 < alpha1) { - // The line segment spans two boundaries, but is outside both of them. - // (This can't happen when we're only clipping against just near/far but good - // to leave the check here for future usage if other clip planes are added.) + if (bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0) { + // Both vertices lie entirely within all clip planes. + return true + } else if ((bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0)) { + // Both vertices lie entirely outside one of the clip planes. return false } else { - // Update the s1 and s2 vertices to match the clipped line segment. - s1.lerp(s2, alpha1) - s2.lerp(s1, 1 - alpha2) + // The line segment spans at least one clip plane. + + if (bc1near < 0) { + // v1 lies outside the near plane, v2 inside + alpha1 = Math.max(alpha1, bc1near / (bc1near - bc2near)) + } else if (bc2near < 0) { + // v2 lies outside the near plane, v1 inside + alpha2 = Math.min(alpha2, bc1near / (bc1near - bc2near)) + } - return true + if (bc1far < 0) { + // v1 lies outside the far plane, v2 inside + alpha1 = Math.max(alpha1, bc1far / (bc1far - bc2far)) + } else if (bc2far < 0) { + // v2 lies outside the far plane, v2 inside + alpha2 = Math.min(alpha2, bc1far / (bc1far - bc2far)) + } + + if (alpha2 < alpha1) { + // The line segment spans two boundaries, but is outside both of them. + // (This can't happen when we're only clipping against just near/far but good + // to leave the check here for future usage if other clip planes are added.) + return false + } else { + // Update the s1 and s2 vertices to match the clipped line segment. + s1.lerp(s2, alpha1) + s2.lerp(s1, 1 - alpha2) + + return true + } } } } diff --git a/src/renderers/SVGRenderer.js b/src/renderers/SVGRenderer.js index 4e2565a8..991a038d 100644 --- a/src/renderers/SVGRenderer.js +++ b/src/renderers/SVGRenderer.js @@ -1,480 +1,483 @@ import { Box2, Camera, Color, Matrix3, Matrix4, Object3D, Vector3 } from 'three' import { Projector, RenderableFace, RenderableLine, RenderableSprite } from '../renderers/Projector' -var SVGObject = function (node) { - Object3D.call(this) +class SVGObject extends Object3D { + constructor(node) { + super() - this.node = node -} + this.isSVGObject = true -SVGObject.prototype = Object.create(Object3D.prototype) -SVGObject.prototype.constructor = SVGObject - -var SVGRenderer = function () { - var _this = this, - _renderData, - _elements, - _lights, - _projector = new Projector(), - _svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'), - _svgWidth, - _svgHeight, - _svgWidthHalf, - _svgHeightHalf, - _v1, - _v2, - _v3, - _clipBox = new Box2(), - _elemBox = new Box2(), - _color = new Color(), - _diffuseColor = new Color(), - _ambientLight = new Color(), - _directionalLights = new Color(), - _pointLights = new Color(), - _clearColor = new Color(), - _vector3 = new Vector3(), // Needed for PointLight - _centroid = new Vector3(), - _normal = new Vector3(), - _normalViewMatrix = new Matrix3(), - _viewMatrix = new Matrix4(), - _viewProjectionMatrix = new Matrix4(), - _svgPathPool = [], - _svgNode, - _pathCount = 0, - _currentPath, - _currentStyle, - _quality = 1, - _precision = null - - this.domElement = _svg - - this.autoClear = true - this.sortObjects = true - this.sortElements = true - - this.overdraw = 0.5 - - this.info = { - render: { - vertices: 0, - faces: 0, - }, + this.node = node } +} - this.setQuality = function (quality) { - switch (quality) { - case 'high': - _quality = 1 - break - case 'low': - _quality = 0 - break +class SVGRenderer { + constructor() { + let _renderData, + _elements, + _lights, + _svgWidth, + _svgHeight, + _svgWidthHalf, + _svgHeightHalf, + _v1, + _v2, + _v3, + _svgNode, + _pathCount = 0, + _precision = null, + _quality = 1, + _currentPath, + _currentStyle + + const _this = this, + _clipBox = new Box2(), + _elemBox = new Box2(), + _color = new Color(), + _diffuseColor = new Color(), + _ambientLight = new Color(), + _directionalLights = new Color(), + _pointLights = new Color(), + _clearColor = new Color(), + _vector3 = new Vector3(), // Needed for PointLight + _centroid = new Vector3(), + _normal = new Vector3(), + _normalViewMatrix = new Matrix3(), + _viewMatrix = new Matrix4(), + _viewProjectionMatrix = new Matrix4(), + _svgPathPool = [], + _projector = new Projector(), + _svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg') + + this.domElement = _svg + + this.autoClear = true + this.sortObjects = true + this.sortElements = true + + this.overdraw = 0.5 + + this.info = { + render: { + vertices: 0, + faces: 0, + }, } - } - this.setClearColor = function (color) { - _clearColor.set(color) - } + this.setQuality = function (quality) { + switch (quality) { + case 'high': + _quality = 1 + break + case 'low': + _quality = 0 + break + } + } - this.setPixelRatio = function () {} + this.setClearColor = function (color) { + _clearColor.set(color) + } - this.setSize = function (width, height) { - _svgWidth = width - _svgHeight = height - _svgWidthHalf = _svgWidth / 2 - _svgHeightHalf = _svgHeight / 2 + this.setPixelRatio = function () {} - _svg.setAttribute('viewBox', -_svgWidthHalf + ' ' + -_svgHeightHalf + ' ' + _svgWidth + ' ' + _svgHeight) - _svg.setAttribute('width', _svgWidth) - _svg.setAttribute('height', _svgHeight) + this.setSize = function (width, height) { + _svgWidth = width + _svgHeight = height + _svgWidthHalf = _svgWidth / 2 + _svgHeightHalf = _svgHeight / 2 - _clipBox.min.set(-_svgWidthHalf, -_svgHeightHalf) - _clipBox.max.set(_svgWidthHalf, _svgHeightHalf) - } + _svg.setAttribute('viewBox', -_svgWidthHalf + ' ' + -_svgHeightHalf + ' ' + _svgWidth + ' ' + _svgHeight) + _svg.setAttribute('width', _svgWidth) + _svg.setAttribute('height', _svgHeight) - this.getSize = function () { - return { - width: _svgWidth, - height: _svgHeight, + _clipBox.min.set(-_svgWidthHalf, -_svgHeightHalf) + _clipBox.max.set(_svgWidthHalf, _svgHeightHalf) } - } - this.setPrecision = function (precision) { - _precision = precision - } - - function removeChildNodes() { - _pathCount = 0 - - while (_svg.childNodes.length > 0) { - _svg.removeChild(_svg.childNodes[0]) + this.getSize = function () { + return { + width: _svgWidth, + height: _svgHeight, + } } - } - function convert(c) { - return _precision !== null ? c.toFixed(_precision) : c - } + this.setPrecision = function (precision) { + _precision = precision + } - this.clear = function () { - removeChildNodes() - _svg.style.backgroundColor = _clearColor.getStyle() - } + function removeChildNodes() { + _pathCount = 0 - this.render = function (scene, camera) { - if (camera instanceof Camera === false) { - console.error('THREE.SVGRenderer.render: camera is not an instance of Camera.') - return + while (_svg.childNodes.length > 0) { + _svg.removeChild(_svg.childNodes[0]) + } } - var background = scene.background + function convert(c) { + return _precision !== null ? c.toFixed(_precision) : c + } - if (background && background.isColor) { + this.clear = function () { removeChildNodes() - _svg.style.backgroundColor = background.getStyle() - } else if (this.autoClear === true) { - this.clear() + _svg.style.backgroundColor = _clearColor.getStyle() } - _this.info.render.vertices = 0 - _this.info.render.faces = 0 + this.render = function (scene, camera) { + if (camera instanceof Camera === false) { + console.error('THREE.SVGRenderer.render: camera is not an instance of Camera.') + return + } - _viewMatrix.copy(camera.matrixWorldInverse) - _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix) + const background = scene.background - _renderData = _projector.projectScene(scene, camera, this.sortObjects, this.sortElements) - _elements = _renderData.elements - _lights = _renderData.lights + if (background && background.isColor) { + removeChildNodes() + _svg.style.backgroundColor = background.getStyle() + } else if (this.autoClear === true) { + this.clear() + } - _normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse) + _this.info.render.vertices = 0 + _this.info.render.faces = 0 - calculateLights(_lights) + _viewMatrix.copy(camera.matrixWorldInverse) + _viewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, _viewMatrix) - // reset accumulated path + _renderData = _projector.projectScene(scene, camera, this.sortObjects, this.sortElements) + _elements = _renderData.elements + _lights = _renderData.lights - _currentPath = '' - _currentStyle = '' + _normalViewMatrix.getNormalMatrix(camera.matrixWorldInverse) - for (let e = 0, el = _elements.length; e < el; e++) { - var element = _elements[e] - var material = element.material + calculateLights(_lights) - if (material === undefined || material.opacity === 0) continue + // reset accumulated path - _elemBox.makeEmpty() + _currentPath = '' + _currentStyle = '' - if (element instanceof RenderableSprite) { - _v1 = element - _v1.x *= _svgWidthHalf - _v1.y *= -_svgHeightHalf + for (let e = 0, el = _elements.length; e < el; e++) { + const element = _elements[e] + const material = element.material - renderSprite(_v1, element, material) - } else if (element instanceof RenderableLine) { - _v1 = element.v1 - _v2 = element.v2 + if (material === undefined || material.opacity === 0) continue - _v1.positionScreen.x *= _svgWidthHalf - _v1.positionScreen.y *= -_svgHeightHalf - _v2.positionScreen.x *= _svgWidthHalf - _v2.positionScreen.y *= -_svgHeightHalf + _elemBox.makeEmpty() - _elemBox.setFromPoints([_v1.positionScreen, _v2.positionScreen]) + if (element instanceof RenderableSprite) { + _v1 = element + _v1.x *= _svgWidthHalf + _v1.y *= -_svgHeightHalf - if (_clipBox.intersectsBox(_elemBox) === true) { - renderLine(_v1, _v2, element, material) - } - } else if (element instanceof RenderableFace) { - _v1 = element.v1 - _v2 = element.v2 - _v3 = element.v3 - - if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1) continue - if (_v2.positionScreen.z < -1 || _v2.positionScreen.z > 1) continue - if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1) continue - - _v1.positionScreen.x *= _svgWidthHalf - _v1.positionScreen.y *= -_svgHeightHalf - _v2.positionScreen.x *= _svgWidthHalf - _v2.positionScreen.y *= -_svgHeightHalf - _v3.positionScreen.x *= _svgWidthHalf - _v3.positionScreen.y *= -_svgHeightHalf - - if (this.overdraw > 0) { - expand(_v1.positionScreen, _v2.positionScreen, this.overdraw) - expand(_v2.positionScreen, _v3.positionScreen, this.overdraw) - expand(_v3.positionScreen, _v1.positionScreen, this.overdraw) - } + renderSprite(_v1, element, material) + } else if (element instanceof RenderableLine) { + _v1 = element.v1 + _v2 = element.v2 + + _v1.positionScreen.x *= _svgWidthHalf + _v1.positionScreen.y *= -_svgHeightHalf + _v2.positionScreen.x *= _svgWidthHalf + _v2.positionScreen.y *= -_svgHeightHalf + + _elemBox.setFromPoints([_v1.positionScreen, _v2.positionScreen]) + + if (_clipBox.intersectsBox(_elemBox) === true) { + renderLine(_v1, _v2, material) + } + } else if (element instanceof RenderableFace) { + _v1 = element.v1 + _v2 = element.v2 + _v3 = element.v3 - _elemBox.setFromPoints([_v1.positionScreen, _v2.positionScreen, _v3.positionScreen]) + if (_v1.positionScreen.z < -1 || _v1.positionScreen.z > 1) continue + if (_v2.positionScreen.z < -1 || _v2.positionScreen.z > 1) continue + if (_v3.positionScreen.z < -1 || _v3.positionScreen.z > 1) continue - if (_clipBox.intersectsBox(_elemBox) === true) { - renderFace3(_v1, _v2, _v3, element, material) + _v1.positionScreen.x *= _svgWidthHalf + _v1.positionScreen.y *= -_svgHeightHalf + _v2.positionScreen.x *= _svgWidthHalf + _v2.positionScreen.y *= -_svgHeightHalf + _v3.positionScreen.x *= _svgWidthHalf + _v3.positionScreen.y *= -_svgHeightHalf + + if (this.overdraw > 0) { + expand(_v1.positionScreen, _v2.positionScreen, this.overdraw) + expand(_v2.positionScreen, _v3.positionScreen, this.overdraw) + expand(_v3.positionScreen, _v1.positionScreen, this.overdraw) + } + + _elemBox.setFromPoints([_v1.positionScreen, _v2.positionScreen, _v3.positionScreen]) + + if (_clipBox.intersectsBox(_elemBox) === true) { + renderFace3(_v1, _v2, _v3, element, material) + } } } - } - flushPath() // just to flush last svg:path + flushPath() // just to flush last svg:path - scene.traverseVisible(function (object) { - if (object instanceof SVGObject) { - _vector3.setFromMatrixPosition(object.matrixWorld) - _vector3.applyMatrix4(_viewProjectionMatrix) + scene.traverseVisible(function (object) { + if (object.isSVGObject) { + _vector3.setFromMatrixPosition(object.matrixWorld) + _vector3.applyMatrix4(_viewProjectionMatrix) - if (_vector3.z < -1 || _vector3.z > 1) return + if (_vector3.z < -1 || _vector3.z > 1) return - var x = _vector3.x * _svgWidthHalf - var y = -_vector3.y * _svgHeightHalf + const x = _vector3.x * _svgWidthHalf + const y = -_vector3.y * _svgHeightHalf - var node = object.node - node.setAttribute('transform', 'translate(' + x + ',' + y + ')') + const node = object.node + node.setAttribute('transform', 'translate(' + x + ',' + y + ')') - _svg.appendChild(node) - } - }) - } + _svg.appendChild(node) + } + }) + } - function calculateLights(lights) { - _ambientLight.setRGB(0, 0, 0) - _directionalLights.setRGB(0, 0, 0) - _pointLights.setRGB(0, 0, 0) - - for (let l = 0, ll = lights.length; l < ll; l++) { - var light = lights[l] - var lightColor = light.color - - if (light.isAmbientLight) { - _ambientLight.r += lightColor.r - _ambientLight.g += lightColor.g - _ambientLight.b += lightColor.b - } else if (light.isDirectionalLight) { - _directionalLights.r += lightColor.r - _directionalLights.g += lightColor.g - _directionalLights.b += lightColor.b - } else if (light.isPointLight) { - _pointLights.r += lightColor.r - _pointLights.g += lightColor.g - _pointLights.b += lightColor.b + function calculateLights(lights) { + _ambientLight.setRGB(0, 0, 0) + _directionalLights.setRGB(0, 0, 0) + _pointLights.setRGB(0, 0, 0) + + for (let l = 0, ll = lights.length; l < ll; l++) { + const light = lights[l] + const lightColor = light.color + + if (light.isAmbientLight) { + _ambientLight.r += lightColor.r + _ambientLight.g += lightColor.g + _ambientLight.b += lightColor.b + } else if (light.isDirectionalLight) { + _directionalLights.r += lightColor.r + _directionalLights.g += lightColor.g + _directionalLights.b += lightColor.b + } else if (light.isPointLight) { + _pointLights.r += lightColor.r + _pointLights.g += lightColor.g + _pointLights.b += lightColor.b + } } } - } - function calculateLight(lights, position, normal, color) { - for (let l = 0, ll = lights.length; l < ll; l++) { - var light = lights[l] - var lightColor = light.color + function calculateLight(lights, position, normal, color) { + for (let l = 0, ll = lights.length; l < ll; l++) { + const light = lights[l] + const lightColor = light.color - if (light.isDirectionalLight) { - var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld).normalize() + if (light.isDirectionalLight) { + const lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld).normalize() - var amount = normal.dot(lightPosition) + let amount = normal.dot(lightPosition) - if (amount <= 0) continue + if (amount <= 0) continue - amount *= light.intensity + amount *= light.intensity - color.r += lightColor.r * amount - color.g += lightColor.g * amount - color.b += lightColor.b * amount - } else if (light.isPointLight) { - var lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld) + color.r += lightColor.r * amount + color.g += lightColor.g * amount + color.b += lightColor.b * amount + } else if (light.isPointLight) { + const lightPosition = _vector3.setFromMatrixPosition(light.matrixWorld) - var amount = normal.dot(_vector3.subVectors(lightPosition, position).normalize()) + let amount = normal.dot(_vector3.subVectors(lightPosition, position).normalize()) - if (amount <= 0) continue + if (amount <= 0) continue - amount *= light.distance == 0 ? 1 : 1 - Math.min(position.distanceTo(lightPosition) / light.distance, 1) + amount *= light.distance == 0 ? 1 : 1 - Math.min(position.distanceTo(lightPosition) / light.distance, 1) - if (amount == 0) continue + if (amount == 0) continue - amount *= light.intensity + amount *= light.intensity - color.r += lightColor.r * amount - color.g += lightColor.g * amount - color.b += lightColor.b * amount + color.r += lightColor.r * amount + color.g += lightColor.g * amount + color.b += lightColor.b * amount + } } } - } - function renderSprite(v1, element, material) { - var scaleX = element.scale.x * _svgWidthHalf - var scaleY = element.scale.y * _svgHeightHalf + function renderSprite(v1, element, material) { + let scaleX = element.scale.x * _svgWidthHalf + let scaleY = element.scale.y * _svgHeightHalf - if (material.isPointsMaterial) { - scaleX *= material.size - scaleY *= material.size - } - - var path = - 'M' + - convert(v1.x - scaleX * 0.5) + - ',' + - convert(v1.y - scaleY * 0.5) + - 'h' + - convert(scaleX) + - 'v' + - convert(scaleY) + - 'h' + - convert(-scaleX) + - 'z' - var style = '' - - if (material.isSpriteMaterial || material.isPointsMaterial) { - style = 'fill:' + material.color.getStyle() + ';fill-opacity:' + material.opacity - } - - addPath(style, path) - } + if (material.isPointsMaterial) { + scaleX *= material.size + scaleY *= material.size + } - function renderLine(v1, v2, element, material) { - var path = - 'M' + - convert(v1.positionScreen.x) + - ',' + - convert(v1.positionScreen.y) + - 'L' + - convert(v2.positionScreen.x) + - ',' + - convert(v2.positionScreen.y) - - if (material.isLineBasicMaterial) { - var style = - 'fill:none;stroke:' + - material.color.getStyle() + - ';stroke-opacity:' + - material.opacity + - ';stroke-width:' + - material.linewidth + - ';stroke-linecap:' + - material.linecap - - if (material.isLineDashedMaterial) { - style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize + const path = + 'M' + + convert(v1.x - scaleX * 0.5) + + ',' + + convert(v1.y - scaleY * 0.5) + + 'h' + + convert(scaleX) + + 'v' + + convert(scaleY) + + 'h' + + convert(-scaleX) + + 'z' + let style = '' + + if (material.isSpriteMaterial || material.isPointsMaterial) { + style = 'fill:' + material.color.getStyle() + ';fill-opacity:' + material.opacity } addPath(style, path) } - } - function renderFace3(v1, v2, v3, element, material) { - _this.info.render.vertices += 3 - _this.info.render.faces++ - - var path = - 'M' + - convert(v1.positionScreen.x) + - ',' + - convert(v1.positionScreen.y) + - 'L' + - convert(v2.positionScreen.x) + - ',' + - convert(v2.positionScreen.y) + - 'L' + - convert(v3.positionScreen.x) + - ',' + - convert(v3.positionScreen.y) + - 'z' - var style = '' - - if (material.isMeshBasicMaterial) { - _color.copy(material.color) - - if (material.vertexColors) { - _color.multiply(element.color) - } - } else if (material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) { - _diffuseColor.copy(material.color) + function renderLine(v1, v2, material) { + const path = + 'M' + + convert(v1.positionScreen.x) + + ',' + + convert(v1.positionScreen.y) + + 'L' + + convert(v2.positionScreen.x) + + ',' + + convert(v2.positionScreen.y) + + if (material.isLineBasicMaterial) { + let style = + 'fill:none;stroke:' + + material.color.getStyle() + + ';stroke-opacity:' + + material.opacity + + ';stroke-width:' + + material.linewidth + + ';stroke-linecap:' + + material.linecap + + if (material.isLineDashedMaterial) { + style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize + } - if (material.vertexColors) { - _diffuseColor.multiply(element.color) + addPath(style, path) } + } - _color.copy(_ambientLight) + function renderFace3(v1, v2, v3, element, material) { + _this.info.render.vertices += 3 + _this.info.render.faces++ + + const path = + 'M' + + convert(v1.positionScreen.x) + + ',' + + convert(v1.positionScreen.y) + + 'L' + + convert(v2.positionScreen.x) + + ',' + + convert(v2.positionScreen.y) + + 'L' + + convert(v3.positionScreen.x) + + ',' + + convert(v3.positionScreen.y) + + 'z' + let style = '' + + if (material.isMeshBasicMaterial) { + _color.copy(material.color) + + if (material.vertexColors) { + _color.multiply(element.color) + } + } else if (material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial) { + _diffuseColor.copy(material.color) - _centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3) + if (material.vertexColors) { + _diffuseColor.multiply(element.color) + } - calculateLight(_lights, _centroid, element.normalModel, _color) + _color.copy(_ambientLight) - _color.multiply(_diffuseColor).add(material.emissive) - } else if (material.isMeshNormalMaterial) { - _normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix).normalize() + _centroid.copy(v1.positionWorld).add(v2.positionWorld).add(v3.positionWorld).divideScalar(3) - _color.setRGB(_normal.x, _normal.y, _normal.z).multiplyScalar(0.5).addScalar(0.5) - } + calculateLight(_lights, _centroid, element.normalModel, _color) - if (material.wireframe) { - style = - 'fill:none;stroke:' + - _color.getStyle() + - ';stroke-opacity:' + - material.opacity + - ';stroke-width:' + - material.wireframeLinewidth + - ';stroke-linecap:' + - material.wireframeLinecap + - ';stroke-linejoin:' + - material.wireframeLinejoin - } else { - style = 'fill:' + _color.getStyle() + ';fill-opacity:' + material.opacity - } + _color.multiply(_diffuseColor).add(material.emissive) + } else if (material.isMeshNormalMaterial) { + _normal.copy(element.normalModel).applyMatrix3(_normalViewMatrix).normalize() - addPath(style, path) - } + _color.setRGB(_normal.x, _normal.y, _normal.z).multiplyScalar(0.5).addScalar(0.5) + } - // Hide anti-alias gaps + if (material.wireframe) { + style = + 'fill:none;stroke:' + + _color.getStyle() + + ';stroke-opacity:' + + material.opacity + + ';stroke-width:' + + material.wireframeLinewidth + + ';stroke-linecap:' + + material.wireframeLinecap + + ';stroke-linejoin:' + + material.wireframeLinejoin + } else { + style = 'fill:' + _color.getStyle() + ';fill-opacity:' + material.opacity + } - function expand(v1, v2, pixels) { - var x = v2.x - v1.x, - y = v2.y - v1.y, - det = x * x + y * y, - idet + addPath(style, path) + } - if (det === 0) return + // Hide anti-alias gaps - idet = pixels / Math.sqrt(det) + function expand(v1, v2, pixels) { + let x = v2.x - v1.x, + y = v2.y - v1.y + const det = x * x + y * y - x *= idet - y *= idet + if (det === 0) return - v2.x += x - v2.y += y - v1.x -= x - v1.y -= y - } + const idet = pixels / Math.sqrt(det) - function addPath(style, path) { - if (_currentStyle === style) { - _currentPath += path - } else { - flushPath() + x *= idet + y *= idet - _currentStyle = style - _currentPath = path + v2.x += x + v2.y += y + v1.x -= x + v1.y -= y } - } - function flushPath() { - if (_currentPath) { - _svgNode = getPathNode(_pathCount++) - _svgNode.setAttribute('d', _currentPath) - _svgNode.setAttribute('style', _currentStyle) - _svg.appendChild(_svgNode) + function addPath(style, path) { + if (_currentStyle === style) { + _currentPath += path + } else { + flushPath() + + _currentStyle = style + _currentPath = path + } } - _currentPath = '' - _currentStyle = '' - } + function flushPath() { + if (_currentPath) { + _svgNode = getPathNode(_pathCount++) + _svgNode.setAttribute('d', _currentPath) + _svgNode.setAttribute('style', _currentStyle) + _svg.appendChild(_svgNode) + } + + _currentPath = '' + _currentStyle = '' + } + + function getPathNode(id) { + if (_svgPathPool[id] == null) { + _svgPathPool[id] = document.createElementNS('http://www.w3.org/2000/svg', 'path') - function getPathNode(id) { - if (_svgPathPool[id] == null) { - _svgPathPool[id] = document.createElementNS('http://www.w3.org/2000/svg', 'path') + if (_quality == 0) { + _svgPathPool[id].setAttribute('shape-rendering', 'crispEdges') //optimizeSpeed + } - if (_quality == 0) { - _svgPathPool[id].setAttribute('shape-rendering', 'crispEdges') //optimizeSpeed + return _svgPathPool[id] } return _svgPathPool[id] } - - return _svgPathPool[id] } } diff --git a/src/renderers/nodes/accessors/CameraNode.js b/src/renderers/nodes/accessors/CameraNode.js deleted file mode 100644 index afe0420d..00000000 --- a/src/renderers/nodes/accessors/CameraNode.js +++ /dev/null @@ -1,72 +0,0 @@ -import Node from '../core/Node' -import Vector3Node from '../inputs/Vector3Node' -import Matrix4Node from '../inputs/Matrix4Node' -import { NodeUpdateType } from '../core/constants' - -class CameraNode extends Node { - static POSITION = 'position' - static PROJECTION = 'projection' - static VIEW = 'view' - - constructor(scope = CameraNode.POSITION) { - super() - - this.updateType = NodeUpdateType.Frame - - this.scope = scope - - this._inputNode = null - } - - getType() { - const scope = this.scope - - if (scope === CameraNode.PROJECTION || scope === CameraNode.VIEW) { - return 'mat4' - } - - return 'vec3' - } - - update(frame) { - const camera = frame.camera - const inputNode = this._inputNode - const scope = this.scope - - if (scope === CameraNode.PROJECTION) { - inputNode.value = camera.projectionMatrix - } else if (scope === CameraNode.VIEW) { - inputNode.value = camera.matrixWorldInverse - } else if (scope === CameraNode.POSITION) { - camera.getWorldPosition(inputNode.value) - } - } - - generate(builder, output) { - const nodeData = builder.getDataFromNode(this) - - let inputNode = this._inputNode - - if (nodeData.inputNode === undefined) { - const scope = this.scope - - if (scope === CameraNode.PROJECTION || scope === CameraNode.VIEW) { - if (inputNode === null || inputNode.isMatrix4Node !== true) { - inputNode = new Matrix4Node(null) - } - } else if (scope === CameraNode.POSITION) { - if (inputNode === null || inputNode.isVector3Node !== true) { - inputNode = new Vector3Node() - } - } - - this._inputNode = inputNode - - nodeData.inputNode = inputNode - } - - return inputNode.build(builder, output) - } -} - -export default CameraNode diff --git a/src/renderers/nodes/accessors/ModelNode.js b/src/renderers/nodes/accessors/ModelNode.js deleted file mode 100644 index 92e5a2fb..00000000 --- a/src/renderers/nodes/accessors/ModelNode.js +++ /dev/null @@ -1,69 +0,0 @@ -import Node from '../core/Node' -import Matrix4Node from '../inputs/Matrix4Node' -import Matrix3Node from '../inputs/Matrix3Node' -import { NodeUpdateType } from '../core/constants' - -class ModelNode extends Node { - static VIEW = 'view' - static NORMAL = 'normal' - - constructor(scope = ModelNode.VIEW) { - super() - - this.scope = scope - - this.updateType = NodeUpdateType.Object - - this._inputNode = null - } - - getType() { - const scope = this.scope - - if (scope === ModelNode.VIEW) { - return 'mat4' - } else if (scope === ModelNode.NORMAL) { - return 'mat3' - } - } - - update(frame) { - const object = frame.object - const inputNode = this._inputNode - const scope = this.scope - - if (scope === ModelNode.VIEW) { - inputNode.value = object.modelViewMatrix - } else if (scope === ModelNode.NORMAL) { - inputNode.value = object.normalMatrix - } - } - - generate(builder, output) { - const nodeData = builder.getDataFromNode(this) - - let inputNode = this._inputNode - - if (nodeData.inputNode === undefined) { - const scope = this.scope - - if (scope === ModelNode.VIEW) { - if (inputNode === null || inputNode.isMatrix4Node !== true) { - inputNode = new Matrix4Node(null) - } - } else if (scope === ModelNode.NORMAL) { - if (inputNode === null || inputNode.isMatrix3Node !== true) { - inputNode = new Matrix3Node(null) - } - } - - this._inputNode = inputNode - - nodeData.inputNode = inputNode - } - - return inputNode.build(builder, output) - } -} - -export default ModelNode diff --git a/src/renderers/nodes/accessors/ModelViewProjectionNode.js b/src/renderers/nodes/accessors/ModelViewProjectionNode.js deleted file mode 100644 index c9249062..00000000 --- a/src/renderers/nodes/accessors/ModelViewProjectionNode.js +++ /dev/null @@ -1,26 +0,0 @@ -import Node from '../core/Node' -import CameraNode from '../accessors/CameraNode' -import ModelNode from '../accessors/ModelNode' -import OperatorNode from '../math/OperatorNode' -import PositionNode from '../accessors/PositionNode' - -class ModelViewProjectionNode extends Node { - constructor(position = new PositionNode()) { - super('vec4') - - this.position = position - - this._mvpMatrix = new OperatorNode('*', new CameraNode(CameraNode.PROJECTION), new ModelNode(ModelNode.VIEW)) - } - - generate(builder, output) { - const type = this.getType(builder) - - const mvpSnipped = this._mvpMatrix.build(builder) - const positionSnipped = this.position.build(builder, 'vec3') - - return builder.format(`( ${mvpSnipped} * vec4( ${positionSnipped}, 1.0 ) )`, type, output) - } -} - -export default ModelViewProjectionNode diff --git a/src/renderers/nodes/accessors/NormalNode.js b/src/renderers/nodes/accessors/NormalNode.js deleted file mode 100644 index a73ef93c..00000000 --- a/src/renderers/nodes/accessors/NormalNode.js +++ /dev/null @@ -1,72 +0,0 @@ -import Node from '../core/Node' -import AttributeNode from '../core/AttributeNode' -import VaryNode from '../core/VaryNode' -import ModelNode from '../accessors/ModelNode' -import CameraNode from '../accessors/CameraNode' -import OperatorNode from '../math/OperatorNode' -import MathNode from '../math/MathNode' - -class NormalNode extends Node { - static LOCAL = 'local' - static WORLD = 'world' - static VIEW = 'view' - - constructor(scope = NormalNode.LOCAL) { - super('vec3') - - this.scope = scope - } - - generate(builder, output) { - const type = this.getType(builder) - const nodeData = builder.getDataFromNode(this, builder.shaderStage) - const scope = this.scope - - let localNormalNode = nodeData.localNormalNode - - if (localNormalNode === undefined) { - localNormalNode = new AttributeNode('normal', 'vec3') - - nodeData.localNormalNode = localNormalNode - } - - let outputNode = localNormalNode - - if (scope === NormalNode.VIEW) { - let viewNormalNode = nodeData.viewNormalNode - - if (viewNormalNode === undefined) { - const unnormalizedWNNode = new OperatorNode('*', new ModelNode(ModelNode.NORMAL), localNormalNode) - const vertexNormalNode = new MathNode(MathNode.NORMALIZE, unnormalizedWNNode) - - viewNormalNode = new MathNode(MathNode.NORMALIZE, new VaryNode(vertexNormalNode)) - - nodeData.viewNormalNode = viewNormalNode - } - - outputNode = viewNormalNode - } else if (scope === NormalNode.WORLD) { - let worldNormalNode = nodeData.worldNormalNode - - if (worldNormalNode === undefined) { - const vertexNormalNode = new MathNode( - MathNode.INVERSE_TRANSFORM_DIRETION, - new NormalNode(NormalNode.VIEW), - new CameraNode(CameraNode.VIEW), - ) - - worldNormalNode = new VaryNode(vertexNormalNode) - - nodeData.worldNormalNode = worldNormalNode - } - - outputNode = worldNormalNode - } - - const normalSnipped = outputNode.build(builder, type) - - return builder.format(normalSnipped, type, output) - } -} - -export default NormalNode diff --git a/src/renderers/nodes/accessors/PositionNode.js b/src/renderers/nodes/accessors/PositionNode.js deleted file mode 100644 index 058b097d..00000000 --- a/src/renderers/nodes/accessors/PositionNode.js +++ /dev/null @@ -1,31 +0,0 @@ -import Node from '../core/Node' -import AttributeNode from '../core/AttributeNode' - -class PositionNode extends Node { - static LOCAL = 'local' - - constructor(scope = PositionNode.POSITION) { - super('vec3') - - this.scope = scope - } - - generate(builder, output) { - const type = this.getType(builder) - const nodeData = builder.getDataFromNode(this, builder.shaderStage) - - let positionNode = nodeData.positionNode - - if (positionNode === undefined) { - positionNode = new AttributeNode('position', 'vec3') - - nodeData.positionNode = positionNode - } - - const positionSnipped = positionNode.build(builder, type) - - return builder.format(positionSnipped, type, output) - } -} - -export default PositionNode diff --git a/src/renderers/nodes/accessors/UVNode.js b/src/renderers/nodes/accessors/UVNode.js deleted file mode 100644 index 9dadb039..00000000 --- a/src/renderers/nodes/accessors/UVNode.js +++ /dev/null @@ -1,15 +0,0 @@ -import AttributeNode from '../core/AttributeNode' - -class UVNode extends AttributeNode { - constructor(index = 0) { - super(null, 'vec2') - - this.index = index - } - - getAttributeName(/*builder*/) { - return 'uv' + (this.index > 0 ? this.index + 1 : '') - } -} - -export default UVNode diff --git a/src/renderers/nodes/core/AttributeNode.js b/src/renderers/nodes/core/AttributeNode.js deleted file mode 100644 index 530ddbb5..00000000 --- a/src/renderers/nodes/core/AttributeNode.js +++ /dev/null @@ -1,47 +0,0 @@ -import Node from './Node' - -class AttributeNode extends Node { - constructor(name, type) { - super(type) - - this.name = name - } - - setAttributeName(name) { - this.name = name - - return this - } - - getAttributeName(/*builder*/) { - return this.name - } - - generate(builder, output) { - const attributeName = this.getAttributeName(builder) - const attributeType = this.getType(builder) - - const attribute = builder.getAttribute(attributeName, attributeType) - - if (builder.isShaderStage('vertex')) { - return builder.format(attribute.name, attribute.type, output) - } else { - const nodeData = builder.getDataFromNode(this, builder.shaderStage) - - let nodeVary = nodeData.varyNode - - if (nodeVary === undefined) { - nodeVary = builder.getVaryFromNode(this, attribute.type) - nodeVary.snippet = attributeName - - nodeData.nodeVary = nodeVary - } - - const varyName = builder.getPropertyName(nodeVary) - - return builder.format(varyName, attribute.type, output) - } - } -} - -export default AttributeNode diff --git a/src/renderers/nodes/core/InputNode.js b/src/renderers/nodes/core/InputNode.js deleted file mode 100644 index 6078935e..00000000 --- a/src/renderers/nodes/core/InputNode.js +++ /dev/null @@ -1,40 +0,0 @@ -import Node from './Node' - -class InputNode extends Node { - constructor(type) { - super(type) - - this.constant = false - - Object.defineProperty(this, 'isInputNode', { value: true }) - } - - setConst(value) { - this.constant = value - - return this - } - - getConst() { - return this.constant - } - - generateConst(builder) { - return builder.getConst(this.getType(builder), this.value) - } - - generate(builder, output) { - const type = this.getType(builder) - - if (this.constant === true) { - return builder.format(this.generateConst(builder), type, output) - } else { - const nodeUniform = builder.getUniformFromNode(this, builder.shaderStage, this.getType(builder)) - const propertyName = builder.getPropertyName(nodeUniform) - - return builder.format(propertyName, type, output) - } - } -} - -export default InputNode diff --git a/src/renderers/nodes/core/Node.js b/src/renderers/nodes/core/Node.js deleted file mode 100644 index 699a3916..00000000 --- a/src/renderers/nodes/core/Node.js +++ /dev/null @@ -1,47 +0,0 @@ -import { NodeUpdateType } from './constants' - -class Node { - constructor(type = null) { - this.type = type - - this.updateType = NodeUpdateType.None - - Object.defineProperty(this, 'isNode', { value: true }) - } - - getUpdateType(/*builder*/) { - return this.updateType - } - - getType(/*builder*/) { - return this.type - } - - update(/*frame*/) { - console.warn('Abstract function.') - } - - generate(/*builder, output*/) { - console.warn('Abstract function.') - } - - buildStage(builder, shaderStage, output = null) { - const oldShaderStage = builder.shaderStage - - builder.shaderStage = shaderStage - - const snippet = this.build(builder, output) - - builder.shaderStage = oldShaderStage - - return snippet - } - - build(builder, output = null) { - builder.addNode(this) - - return this.generate(builder, output) - } -} - -export default Node diff --git a/src/renderers/nodes/core/NodeAttribute.js b/src/renderers/nodes/core/NodeAttribute.js deleted file mode 100644 index 7f45158d..00000000 --- a/src/renderers/nodes/core/NodeAttribute.js +++ /dev/null @@ -1,10 +0,0 @@ -class NodeAttribute { - constructor(name, type) { - this.name = name - this.type = type - - Object.defineProperty(this, 'isNodeAttribute', { value: true }) - } -} - -export default NodeAttribute diff --git a/src/renderers/nodes/core/NodeBuilder.js b/src/renderers/nodes/core/NodeBuilder.js deleted file mode 100644 index ad162111..00000000 --- a/src/renderers/nodes/core/NodeBuilder.js +++ /dev/null @@ -1,317 +0,0 @@ -import NodeUniform from './NodeUniform' -import NodeAttribute from './NodeAttribute' -import NodeVary from './NodeVary' -import { NodeUpdateType } from './constants' - -class NodeBuilder { - constructor(material, renderer) { - this.material = material - this.renderer = renderer - - this.nodes = [] - this.updateNodes = [] - - this.vertexShader = null - this.fragmentShader = null - - this.slots = { vertex: [], fragment: [] } - this.defines = { vertex: {}, fragment: {} } - this.uniforms = { vertex: [], fragment: [] } - this.attributes = [] - this.varys = [] - - this.nodesData = new WeakMap() - - this.shaderStage = null - } - - addNode(node) { - if (this.nodes.indexOf(node) === -1) { - const updateType = node.getUpdateType(this) - - if (updateType !== NodeUpdateType.None) { - this.updateNodes.push(node) - } - - this.nodes.push(node) - } - } - - addSlot(shaderStage, slot) { - this.slots[shaderStage].push(slot) - } - - define(shaderStage, name, value = '') { - this.defines[shaderStage][name] = value - } - - getTexture(/* textureProperty, uvSnippet */) { - console.warn('Abstract function.') - } - - getConst(type, value) { - if (type === 'float') return value + (value % 1 ? '' : '.0') - if (type === 'vec2') return `vec2( ${value.x}, ${value.y} )` - if (type === 'vec3') return `vec3( ${value.x}, ${value.y}, ${value.z} )` - if (type === 'vec4') return `vec4( ${value.x}, ${value.y}, ${value.z}, ${value.w} )` - if (type === 'color') return `vec3( ${value.r}, ${value.g}, ${value.b} )` - - throw new Error(`Type '${type}' not found in generate constant attempt.`) - } - - getAttribute(name, type) { - const attributes = this.attributes - - // find attribute - - for (let attribute of attributes) { - if (attribute.name === name) { - return attribute - } - } - - // create a new if no exist - - const attribute = new NodeAttribute(name, type) - - attributes.push(attribute) - - return attribute - } - - getPropertyName(node) { - return node.name - } - - isVector(type) { - return /vec\d/.test(type) - } - - isMatrix(type) { - return /mat\d/.test(type) - } - - isShaderStage(shaderStage) { - return this.shaderStage === shaderStage - } - - getVectorType(type) { - if (type === 'color') return 'vec3' - else if (type === 'texture') return 'vec4' - - return type - } - - getTypeFromLength(type) { - if (type === 1) return 'float' - if (type === 2) return 'vec2' - if (type === 3) return 'vec3' - if (type === 4) return 'vec4' - - return 0 - } - - getTypeLength(type) { - type = this.getVectorType(type) - - if (type === 'float') return 1 - if (type === 'vec2') return 2 - if (type === 'vec3') return 3 - if (type === 'vec4') return 4 - - return 0 - } - - getDataFromNode(node, shaderStage = null) { - let nodeData = this.nodesData.get(node) - - if (nodeData === undefined) { - nodeData = { vertex: {}, fragment: {} } - - this.nodesData.set(node, nodeData) - } - - return shaderStage ? nodeData[shaderStage] : nodeData - } - - getUniformFromNode(node, shaderStage, type) { - const nodeData = this.getDataFromNode(node, shaderStage) - - let nodeUniform = nodeData.uniform - - if (nodeUniform === undefined) { - const uniforms = this.uniforms[shaderStage] - const index = uniforms.length - - nodeUniform = new NodeUniform('nodeU' + index, type, node) - - uniforms.push(nodeUniform) - - nodeData.uniform = nodeUniform - } - - return nodeUniform - } - - getVaryFromNode(node, type) { - const nodeData = this.getDataFromNode(node) - - let nodeVary = nodeData.vary - - if (nodeVary === undefined) { - const varys = this.varys - const index = varys.length - - nodeVary = new NodeVary('nodeV' + index, type) - - varys.push(nodeVary) - - nodeData.vary = nodeVary - } - - return nodeVary - } - - /* - analyzeNode( node ) { - - - } - */ - - flowNode(node, output) { - const flowData = {} - flowData.result = node.build(this, output) - - return flowData - } - - _buildDefines(shader) { - const defines = this.defines[shader] - - let code = '' - - for (let name in defines) { - code += `#define ${name} ${defines[name]}\n` - } - - return code - } - - getAttributesBodySnippet(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getAttributesHeaderSnippet(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getVarysHeaderSnippet(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getVarysBodySnippet(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getUniformsHeaderSnippet(/*shaderStage*/) { - console.warn('Abstract function.') - } - - getHash() { - return this.vertexShader + this.fragmentShader - } - - build() { - const shaderStages = ['vertex', 'fragment'] - const shaderData = {} - - for (let shaderStage of shaderStages) { - this.shaderStage = shaderStage - - const slots = this.slots[shaderStage] - - for (let slot of slots) { - const flowData = this.flowNode(slot.node, slot.output) - - this.define(shaderStage, `NODE_${slot.name}`, flowData.result) - } - } - - this.shaderStage = null - - for (let shaderStage of shaderStages) { - this.define(shaderStage, 'NODE_HEADER_UNIFORMS', this.getUniformsHeaderSnippet(shaderStage)) - this.define(shaderStage, 'NODE_HEADER_ATTRIBUTES', this.getAttributesHeaderSnippet(shaderStage)) - this.define(shaderStage, 'NODE_HEADER_VARYS', this.getVarysHeaderSnippet(shaderStage)) - - this.define(shaderStage, 'NODE_BODY_VARYS', this.getVarysBodySnippet(shaderStage)) - - shaderData[shaderStage] = this._buildDefines(shaderStage) - } - - this.vertexShader = shaderData.vertex - this.fragmentShader = shaderData.fragment - - return this - } - - format(snippet, fromType, toType) { - fromType = this.getVectorType(fromType) - toType = this.getVectorType(toType) - - const typeToType = `${fromType} to ${toType}` - - switch (typeToType) { - case 'float to vec2': - return `vec2( ${snippet} )` - case 'float to vec3': - return `vec3( ${snippet} )` - case 'float to vec4': - return `vec4( vec3( ${snippet} ), 1.0 )` - - case 'vec2 to float': - return `${snippet}.x` - case 'vec2 to vec3': - return `vec3( ${snippet}, 0.0 )` - case 'vec2 to vec4': - return `vec4( ${snippet}.xy, 0.0, 1.0 )` - - case 'vec3 to float': - return `${snippet}.x` - case 'vec3 to vec2': - return `${snippet}.xy` - case 'vec3 to vec4': - return `vec4( ${snippet}, 1.0 )` - - case 'vec4 to float': - return `${snippet}.x` - case 'vec4 to vec2': - return `${snippet}.xy` - case 'vec4 to vec3': - return `${snippet}.xyz` - - case 'mat3 to float': - return `( ${snippet} * vec3( 1.0 ) ).x` - case 'mat3 to vec2': - return `( ${snippet} * vec3( 1.0 ) ).xy` - case 'mat3 to vec3': - return `( ${snippet} * vec3( 1.0 ) ).xyz` - case 'mat3 to vec4': - return `vec4( ${snippet} * vec3( 1.0 ), 1.0 )` - - case 'mat4 to float': - return `( ${snippet} * vec4( 1.0 ) ).x` - case 'mat4 to vec2': - return `( ${snippet} * vec4( 1.0 ) ).xy` - case 'mat4 to vec3': - return `( ${snippet} * vec4( 1.0 ) ).xyz` - case 'mat4 to vec4': - return `( ${snippet} * vec4( 1.0 ) )` - } - - return snippet - } -} - -export default NodeBuilder diff --git a/src/renderers/nodes/core/NodeFrame.js b/src/renderers/nodes/core/NodeFrame.js deleted file mode 100644 index 3ca40928..00000000 --- a/src/renderers/nodes/core/NodeFrame.js +++ /dev/null @@ -1,45 +0,0 @@ -import { NodeUpdateType } from './constants' - -class NodeFrame { - constructor() { - this.time = 0 - this.deltaTime = 0 - - this.frameId = 0 - - this.startTime = null - - this.updateMap = new WeakMap() - - this.renderer = null - this.material = null - this.camera = null - this.object = null - } - - updateNode(node) { - if (node.updateType === NodeUpdateType.Frame) { - if (this.updateMap.get(node) !== this.frameId) { - this.updateMap.set(node, this.frameId) - - node.update(this) - } - } else if (node.updateType === NodeUpdateType.Object) { - node.update(this) - } - } - - update() { - this.frameId++ - - if (this.lastTime === undefined) this.lastTime = performance.now() - - this.deltaTime = (performance.now() - this.lastTime) / 1000 - - this.lastTime = performance.now() - - this.time += this.deltaTime - } -} - -export default NodeFrame diff --git a/src/renderers/nodes/core/NodeSlot.js b/src/renderers/nodes/core/NodeSlot.js deleted file mode 100644 index 4d4a9cee..00000000 --- a/src/renderers/nodes/core/NodeSlot.js +++ /dev/null @@ -1,9 +0,0 @@ -class NodeSlot { - constructor(node, name, output) { - this.node = node - this.name = name - this.output = output - } -} - -export default NodeSlot diff --git a/src/renderers/nodes/core/NodeUniform.js b/src/renderers/nodes/core/NodeUniform.js deleted file mode 100644 index a3b55fa4..00000000 --- a/src/renderers/nodes/core/NodeUniform.js +++ /dev/null @@ -1,20 +0,0 @@ -class NodeUniform { - constructor(name, type, node, needsUpdate = undefined) { - this.name = name - this.type = type - this.node = node - this.needsUpdate = needsUpdate - - Object.defineProperty(this, 'isNodeUniform', { value: true }) - } - - get value() { - return this.node.value - } - - set value(val) { - this.node.value = val - } -} - -export default NodeUniform diff --git a/src/renderers/nodes/core/NodeVary.js b/src/renderers/nodes/core/NodeVary.js deleted file mode 100644 index def8b8c9..00000000 --- a/src/renderers/nodes/core/NodeVary.js +++ /dev/null @@ -1,11 +0,0 @@ -class NodeVary { - constructor(name, type, snippet = '') { - this.name = name - this.type = type - this.snippet = snippet - - Object.defineProperty(this, 'isNodeVary', { value: true }) - } -} - -export default NodeVary diff --git a/src/renderers/nodes/core/VaryNode.js b/src/renderers/nodes/core/VaryNode.js deleted file mode 100644 index 76f5599c..00000000 --- a/src/renderers/nodes/core/VaryNode.js +++ /dev/null @@ -1,32 +0,0 @@ -import Node from './Node' -import { NodeShaderStage } from './constants' - -class VaryNode extends Node { - constructor(value) { - super() - - this.value = value - } - - getType(builder) { - // VaryNode is auto type - - return this.value.getType(builder) - } - - generate(builder, output) { - const type = this.getType(builder) - - // force nodeVary.snippet work in vertex stage - const snippet = this.value.buildStage(builder, NodeShaderStage.Vertex, type) - - const nodeVary = builder.getVaryFromNode(this, type) - nodeVary.snippet = snippet - - const propertyName = builder.getPropertyName(nodeVary) - - return builder.format(propertyName, type, output) - } -} - -export default VaryNode diff --git a/src/renderers/nodes/core/constants.js b/src/renderers/nodes/core/constants.js deleted file mode 100644 index c2778b37..00000000 --- a/src/renderers/nodes/core/constants.js +++ /dev/null @@ -1,19 +0,0 @@ -export const NodeShaderStage = { - Vertex: 'vertex', - Fragment: 'fragment', -} - -export const NodeUpdateType = { - None: 'none', - Frame: 'frame', - Object: 'object', -} - -export const NodeType = { - Float: 'float', - Vector2: 'vec2', - Vector3: 'vec3', - Vector4: 'vec4', - Matrix3: 'mat3', - Matrix4: 'mat4', -} diff --git a/src/renderers/nodes/inputs/ColorNode.js b/src/renderers/nodes/inputs/ColorNode.js deleted file mode 100644 index c733b515..00000000 --- a/src/renderers/nodes/inputs/ColorNode.js +++ /dev/null @@ -1,13 +0,0 @@ -import InputNode from '../core/InputNode' - -class ColorNode extends InputNode { - constructor(value) { - super('color') - - this.value = value - - Object.defineProperty(this, 'isColorNode', { value: true }) - } -} - -export default ColorNode diff --git a/src/renderers/nodes/inputs/FloatNode.js b/src/renderers/nodes/inputs/FloatNode.js deleted file mode 100644 index a3d8dcc1..00000000 --- a/src/renderers/nodes/inputs/FloatNode.js +++ /dev/null @@ -1,13 +0,0 @@ -import InputNode from '../core/InputNode' - -class FloatNode extends InputNode { - constructor(value = 0) { - super('float') - - this.value = value - - Object.defineProperty(this, 'isFloatNode', { value: true }) - } -} - -export default FloatNode diff --git a/src/renderers/nodes/inputs/Matrix3Node.js b/src/renderers/nodes/inputs/Matrix3Node.js deleted file mode 100644 index 1f37cca6..00000000 --- a/src/renderers/nodes/inputs/Matrix3Node.js +++ /dev/null @@ -1,14 +0,0 @@ -import InputNode from '../core/InputNode' -import { Matrix3 } from 'three' - -class Matrix3Node extends InputNode { - constructor(value = new Matrix3()) { - super('mat3') - - this.value = value - - Object.defineProperty(this, 'isMatrix3Node', { value: true }) - } -} - -export default Matrix3Node diff --git a/src/renderers/nodes/inputs/Matrix4Node.js b/src/renderers/nodes/inputs/Matrix4Node.js deleted file mode 100644 index f0ad3a5b..00000000 --- a/src/renderers/nodes/inputs/Matrix4Node.js +++ /dev/null @@ -1,14 +0,0 @@ -import InputNode from '../core/InputNode' -import { Matrix4 } from 'three' - -class Matrix4Node extends InputNode { - constructor(value = new Matrix4()) { - super('mat4') - - this.value = value - - Object.defineProperty(this, 'isMatrix4Node', { value: true }) - } -} - -export default Matrix4Node diff --git a/src/renderers/nodes/inputs/TextureNode.js b/src/renderers/nodes/inputs/TextureNode.js deleted file mode 100644 index 697f546e..00000000 --- a/src/renderers/nodes/inputs/TextureNode.js +++ /dev/null @@ -1,26 +0,0 @@ -import InputNode from '../core/InputNode' -import UVNode from '../accessors/UVNode' - -class TextureNode extends InputNode { - constructor(value, uv = new UVNode()) { - super('texture') - - this.value = value - this.uv = uv - - Object.defineProperty(this, 'isTextureNode', { value: true }) - } - - generate(builder, output) { - const type = this.getType(builder) - - const textureProperty = super.generate(builder, type) - const uvSnippet = this.uv.build(builder, 'vec2') - - const textureCallSnippet = builder.getTexture(textureProperty, uvSnippet) - - return builder.format(textureCallSnippet, type, output) - } -} - -export default TextureNode diff --git a/src/renderers/nodes/inputs/Vector2Node.js b/src/renderers/nodes/inputs/Vector2Node.js deleted file mode 100644 index 82f7047e..00000000 --- a/src/renderers/nodes/inputs/Vector2Node.js +++ /dev/null @@ -1,13 +0,0 @@ -import InputNode from '../core/InputNode' - -class Vector2Node extends InputNode { - constructor(value) { - super('vec2') - - this.value = value - - Object.defineProperty(this, 'isVector2Node', { value: true }) - } -} - -export default Vector2Node diff --git a/src/renderers/nodes/inputs/Vector3Node.js b/src/renderers/nodes/inputs/Vector3Node.js deleted file mode 100644 index 6966bfff..00000000 --- a/src/renderers/nodes/inputs/Vector3Node.js +++ /dev/null @@ -1,14 +0,0 @@ -import InputNode from '../core/InputNode' -import { Vector3 } from 'three' - -class Vector3Node extends InputNode { - constructor(value = new Vector3()) { - super('vec3') - - this.value = value - - Object.defineProperty(this, 'isVector3Node', { value: true }) - } -} - -export default Vector3Node diff --git a/src/renderers/nodes/inputs/Vector4Node.js b/src/renderers/nodes/inputs/Vector4Node.js deleted file mode 100644 index 333b5c9a..00000000 --- a/src/renderers/nodes/inputs/Vector4Node.js +++ /dev/null @@ -1,14 +0,0 @@ -import InputNode from '../core/InputNode' -import { Vector4 } from 'three' - -class Vector4Node extends InputNode { - constructor(value = new Vector4()) { - super('vec4') - - this.value = value - - Object.defineProperty(this, 'isVector4Node', { value: true }) - } -} - -export default Vector4Node diff --git a/src/renderers/nodes/math/MathNode.js b/src/renderers/nodes/math/MathNode.js deleted file mode 100644 index 14b6aec9..00000000 --- a/src/renderers/nodes/math/MathNode.js +++ /dev/null @@ -1,65 +0,0 @@ -import Node from '../core/Node' - -class MathNode extends Node { - static NORMALIZE = 'normalize' - static INVERSE_TRANSFORM_DIRETION = 'inverseTransformDirection' - - constructor(method, a, b = null) { - super() - - this.method = method - - this.a = a - this.b = b - } - - getType(builder) { - const method = this.method - - if (method === MathNode.INVERSE_TRANSFORM_DIRETION) { - return 'vec3' - } else { - const typeA = this.a.getType(builder) - - if (this.b !== null) { - if (builder.getTypeLength(typeB) > builder.getTypeLength(typeA)) { - // anytype x anytype: use the greater length vector - - return typeB - } - } - - return typeA - } - } - - generate(builder, output) { - const method = this.method - const type = this.getType(builder) - - let a = null, - b = null - - if (method === MathNode.INVERSE_TRANSFORM_DIRETION) { - a = this.a.build(builder, 'vec3') - b = this.b.build(builder, 'mat4') - - // add in FunctionNode later - return `normalize( ( vec4( ${a}, 0.0 ) * ${b} ).xyz )` - } else { - a = this.a.build(builder, type) - - if (this.b !== null) { - b = this.b.build(builder, type) - } - } - - if (b !== null) { - return builder.format(`${method}( ${a}, ${b} )`, type, output) - } else { - return builder.format(`${method}( ${a} )`, type, output) - } - } -} - -export default MathNode diff --git a/src/renderers/nodes/math/OperatorNode.js b/src/renderers/nodes/math/OperatorNode.js deleted file mode 100644 index ee33ef6a..00000000 --- a/src/renderers/nodes/math/OperatorNode.js +++ /dev/null @@ -1,65 +0,0 @@ -import Node from '../core/Node' - -class OperatorNode extends Node { - constructor(op, a, b) { - super() - - this.op = op - - this.a = a - this.b = b - } - - getType(builder) { - const typeA = this.a.getType(builder) - const typeB = this.b.getType(builder) - - if (builder.isMatrix(typeA) && builder.isVector(typeB)) { - // matrix x vector - - return typeB - } else if (builder.isVector(typeA) && builder.isMatrix(typeB)) { - // vector x matrix - - return typeA - } else if (builder.getTypeLength(typeB) > builder.getTypeLength(typeA)) { - // anytype x anytype: use the greater length vector - - return typeB - } - - return typeA - } - - getVectorFromMatrix(type) { - return 'vec' + type.substr(3) - } - - generate(builder, output) { - let typeA = this.a.getType(builder) - let typeB = this.b.getType(builder) - - let type = this.getType(builder) - - if (builder.isMatrix(typeA) && builder.isVector(typeB)) { - // matrix x vector - - type = typeB = this.getVectorFromMatrix(typeA) - } else if (builder.isVector(typeA) && builder.isMatrix(typeB)) { - // vector x matrix - - type = typeB = this.getVectorFromMatrix(typeB) - } else { - // anytype x anytype - - typeA = typeB = type - } - - const a = this.a.build(builder, typeA) - const b = this.b.build(builder, typeB) - - return builder.format(`( ${a} ${this.op} ${b} )`, type, output) - } -} - -export default OperatorNode diff --git a/src/renderers/nodes/utils/SwitchNode.js b/src/renderers/nodes/utils/SwitchNode.js deleted file mode 100644 index 0ab4c60a..00000000 --- a/src/renderers/nodes/utils/SwitchNode.js +++ /dev/null @@ -1,25 +0,0 @@ -import Node from '../core/Node' - -class SwitchNode extends Node { - constructor(node, components = 'x') { - super() - - this.node = node - this.components = components - } - - getType(builder) { - return builder.getTypeFromLength(this.components.length) - } - - generate(builder, output) { - const nodeType = this.node.getType(builder) - const nodeSnippet = this.node.build(builder, nodeType) - - const snippet = `${nodeSnippet}.${this.components}` - - return builder.format(snippet, this.getType(builder), output) - } -} - -export default SwitchNode diff --git a/src/renderers/nodes/utils/TimerNode.js b/src/renderers/nodes/utils/TimerNode.js deleted file mode 100644 index f77586b2..00000000 --- a/src/renderers/nodes/utils/TimerNode.js +++ /dev/null @@ -1,16 +0,0 @@ -import FloatNode from '../inputs/FloatNode' -import { NodeUpdateType } from '../core/constants' - -class TimerNode extends FloatNode { - constructor() { - super() - - this.updateType = NodeUpdateType.Frame - } - - update(frame) { - this.value = frame.time - } -} - -export default TimerNode diff --git a/src/renderers/webgpu/WebGPU.js b/src/renderers/webgpu/WebGPU.js deleted file mode 100644 index f4fc1545..00000000 --- a/src/renderers/webgpu/WebGPU.js +++ /dev/null @@ -1,28 +0,0 @@ -class WebGPU { - static isAvailable() { - return navigator.gpu !== undefined - } - - static getErrorMessage() { - const message = - 'Your browser does not support WebGPU.' - - const element = document.createElement('div') - element.id = 'webgpumessage' - element.style.fontFamily = 'monospace' - element.style.fontSize = '13px' - element.style.fontWeight = 'normal' - element.style.textAlign = 'center' - element.style.background = '#fff' - element.style.color = '#000' - element.style.padding = '1.5em' - element.style.width = '400px' - element.style.margin = '5em auto 0' - - element.innerHTML = message - - return element - } -} - -export default WebGPU diff --git a/src/renderers/webgpu/WebGPUAttributes.js b/src/renderers/webgpu/WebGPUAttributes.js deleted file mode 100644 index 3967808b..00000000 --- a/src/renderers/webgpu/WebGPUAttributes.js +++ /dev/null @@ -1,90 +0,0 @@ -class WebGPUAttributes { - constructor(device) { - this.buffers = new WeakMap() - this.device = device - } - - get(attribute) { - return this.buffers.get(attribute) - } - - remove(attribute) { - const data = this.buffers.get(attribute) - - if (data) { - data.buffer.destroy() - - this.buffers.delete(attribute) - } - } - - update(attribute, isIndex = false, usage = null) { - let data = this.buffers.get(attribute) - - if (data === undefined) { - if (usage === null) { - usage = isIndex === true ? GPUBufferUsage.INDEX : GPUBufferUsage.VERTEX - } - - data = this._createBuffer(attribute, usage) - - this.buffers.set(attribute, data) - } else if (usage && usage !== data.usage) { - data.buffer.destroy() - - data = this._createBuffer(attribute, usage) - - this.buffers.set(attribute, data) - } else if (data.version < attribute.version) { - this._writeBuffer(data.buffer, attribute) - - data.version = attribute.version - } - } - - _createBuffer(attribute, usage) { - const array = attribute.array - const size = array.byteLength + ((4 - (array.byteLength % 4)) % 4) // ensure 4 byte alignment, see #20441 - - const buffer = this.device.createBuffer({ - size: size, - usage: usage | GPUBufferUsage.COPY_DST, - mappedAtCreation: true, - }) - - new array.constructor(buffer.getMappedRange()).set(array) - - buffer.unmap() - - attribute.onUploadCallback() - - return { - version: attribute.version, - buffer: buffer, - usage: usage, - } - } - - _writeBuffer(buffer, attribute) { - const array = attribute.array - const updateRange = attribute.updateRange - - if (updateRange.count === -1) { - // Not using update ranges - - this.device.queue.writeBuffer(buffer, 0, array, 0) - } else { - this.device.queue.writeBuffer( - buffer, - 0, - array, - updateRange.offset * array.BYTES_PER_ELEMENT, - updateRange.count * array.BYTES_PER_ELEMENT, - ) - - updateRange.count = -1 // reset range - } - } -} - -export default WebGPUAttributes diff --git a/src/renderers/webgpu/WebGPUBackground.js b/src/renderers/webgpu/WebGPUBackground.js deleted file mode 100644 index 96ceff62..00000000 --- a/src/renderers/webgpu/WebGPUBackground.js +++ /dev/null @@ -1,77 +0,0 @@ -import { GPULoadOp } from './constants' -import { Color } from 'three' - -let _clearAlpha -const _clearColor = new Color() - -class WebGPUBackground { - constructor(renderer) { - this.renderer = renderer - - this.forceClear = false - } - - clear() { - this.forceClear = true - } - - update(scene) { - const renderer = this.renderer - const background = scene.isScene === true ? scene.background : null - let forceClear = this.forceClear - - if (background === null) { - // no background settings, use clear color configuration from the renderer - - _clearColor.copy(renderer._clearColor) - _clearAlpha = renderer._clearAlpha - } else if (background.isColor === true) { - // background is an opaque color - - _clearColor.copy(background) - _clearAlpha = 1 - forceClear = true - } else { - console.error('THREE.WebGPURenderer: Unsupported background configuration.', background) - } - - // configure render pass descriptor - - const renderPassDescriptor = renderer._renderPassDescriptor - const colorAttachment = renderPassDescriptor.colorAttachments[0] - const depthStencilAttachment = renderPassDescriptor.depthStencilAttachment - - if (renderer.autoClear === true || forceClear === true) { - if (renderer.autoClearColor === true) { - colorAttachment.loadValue = { - r: _clearColor.r, - g: _clearColor.g, - b: _clearColor.b, - a: _clearAlpha, - } - } else { - colorAttachment.loadValue = GPULoadOp.Load - } - - if (renderer.autoClearDepth === true) { - depthStencilAttachment.depthLoadValue = renderer._clearDepth - } else { - depthStencilAttachment.depthLoadValue = GPULoadOp.Load - } - - if (renderer.autoClearStencil === true) { - depthStencilAttachment.stencilLoadValue = renderer._clearDepth - } else { - depthStencilAttachment.stencilLoadValue = GPULoadOp.Load - } - } else { - colorAttachment.loadValue = GPULoadOp.Load - depthStencilAttachment.depthLoadValue = GPULoadOp.Load - depthStencilAttachment.stencilLoadValue = GPULoadOp.Load - } - - this.forceClear = false - } -} - -export default WebGPUBackground diff --git a/src/renderers/webgpu/WebGPUBinding.js b/src/renderers/webgpu/WebGPUBinding.js deleted file mode 100644 index 674a1bf4..00000000 --- a/src/renderers/webgpu/WebGPUBinding.js +++ /dev/null @@ -1,16 +0,0 @@ -class WebGPUBinding { - constructor(name = '') { - this.name = name - this.visibility = null - - this.type = null // read-only - - this.isShared = false - } - - setVisibility(visibility) { - this.visibility = visibility - } -} - -export default WebGPUBinding diff --git a/src/renderers/webgpu/WebGPUBindings.js b/src/renderers/webgpu/WebGPUBindings.js deleted file mode 100644 index cf7bae3c..00000000 --- a/src/renderers/webgpu/WebGPUBindings.js +++ /dev/null @@ -1,203 +0,0 @@ -class WebGPUBindings { - constructor(device, info, properties, textures, pipelines, computePipelines, attributes, nodes) { - this.device = device - this.info = info - this.properties = properties - this.textures = textures - this.pipelines = pipelines - this.computePipelines = computePipelines - this.attributes = attributes - this.nodes = nodes - - this.uniformsData = new WeakMap() - - this.updateMap = new WeakMap() - } - - get(object) { - let data = this.uniformsData.get(object) - - if (data === undefined) { - const pipeline = this.pipelines.get(object) - const material = object.material - - const nodeBuilder = this.nodes.get(material) - - // each material defines an array of bindings (ubos, textures, samplers etc.) - - const bindings = nodeBuilder.getBindings() - - // setup (static) binding layout and (dynamic) binding group - - const bindLayout = pipeline.getBindGroupLayout(0) - const bindGroup = this._createBindGroup(bindings, bindLayout) - - data = { - layout: bindLayout, - group: bindGroup, - bindings: bindings, - } - - this.uniformsData.set(object, data) - } - - return data - } - - getForCompute(param) { - let data = this.uniformsData.get(param) - - if (data === undefined) { - const pipeline = this.computePipelines.get(param) - const bindings = param.bindings !== undefined ? param.bindings.slice() : [] - const bindLayout = pipeline.getBindGroupLayout(0) - const bindGroup = this._createBindGroup(bindings, bindLayout) - - data = { - layout: bindLayout, - group: bindGroup, - bindings: bindings, - } - - this.uniformsData.set(param, data) - } - - return data - } - - update(object, camera) { - const textures = this.textures - - const data = this.get(object) - const bindings = data.bindings - - const updateMap = this.updateMap - const frame = this.info.render.frame - - let needsBindGroupRefresh = false - - // iterate over all bindings and check if buffer updates or a new binding group is required - - for (let binding of bindings) { - const isShared = binding.isShared - const isUpdated = updateMap.get(binding) === frame - - if (isShared && isUpdated) continue - - if (binding.isUniformsGroup) { - const array = binding.array - const bufferGPU = binding.bufferGPU - - binding.onBeforeUpdate(object, camera) - - const needsBufferWrite = binding.update() - - if (needsBufferWrite === true) { - this.device.queue.writeBuffer(bufferGPU, 0, array, 0) - } - } else if (binding.isStorageBuffer) { - const attribute = binding.attribute - this.attributes.update(attribute, false, binding.usage) - } else if (binding.isSampler) { - const texture = binding.texture - - textures.updateSampler(texture) - - const samplerGPU = textures.getSampler(texture) - - if (binding.samplerGPU !== samplerGPU) { - binding.samplerGPU = samplerGPU - needsBindGroupRefresh = true - } - } else if (binding.isSampledTexture) { - const texture = binding.texture - - const forceUpdate = textures.updateTexture(texture) - const textureGPU = textures.getTextureGPU(texture) - - if (binding.textureGPU !== textureGPU || forceUpdate === true) { - binding.textureGPU = textureGPU - needsBindGroupRefresh = true - } - } - - updateMap.set(binding, frame) - } - - if (needsBindGroupRefresh === true) { - data.group = this._createBindGroup(bindings, data.layout) - } - } - - dispose() { - this.uniformsData = new WeakMap() - this.updateMap = new WeakMap() - } - - _createBindGroup(bindings, layout) { - let bindingPoint = 0 - const entries = [] - - for (let binding of bindings) { - if (binding.isUniformsGroup) { - if (binding.bufferGPU === null) { - const byteLength = binding.getByteLength() - - binding.array = new Float32Array(new ArrayBuffer(byteLength)) - - binding.bufferGPU = this.device.createBuffer({ - size: byteLength, - usage: binding.usage, - }) - } - - entries.push({ - binding: bindingPoint, - resource: { buffer: binding.bufferGPU }, - }) - } else if (binding.isStorageBuffer) { - if (binding.bufferGPU === null) { - const attribute = binding.attribute - - this.attributes.update(attribute, false, binding.usage) - binding.bufferGPU = this.attributes.get(attribute).buffer - } - - entries.push({ - binding: bindingPoint, - resource: { buffer: binding.bufferGPU }, - }) - } else if (binding.isSampler) { - if (binding.samplerGPU === null) { - binding.samplerGPU = this.textures.getDefaultSampler() - } - - entries.push({ binding: bindingPoint, resource: binding.samplerGPU }) - } else if (binding.isSampledTexture) { - if (binding.textureGPU === null) { - if (binding.isSampledCubeTexture) { - binding.textureGPU = this.textures.getDefaultCubeTexture() - } else { - binding.textureGPU = this.textures.getDefaultTexture() - } - } - - entries.push({ - binding: bindingPoint, - resource: binding.textureGPU.createView({ - dimension: binding.dimension, - }), - }) - } - - bindingPoint++ - } - - return this.device.createBindGroup({ - layout: layout, - entries: entries, - }) - } -} - -export default WebGPUBindings diff --git a/src/renderers/webgpu/WebGPUComputePipelines.js b/src/renderers/webgpu/WebGPUComputePipelines.js deleted file mode 100644 index 90ca7638..00000000 --- a/src/renderers/webgpu/WebGPUComputePipelines.js +++ /dev/null @@ -1,60 +0,0 @@ -class WebGPUComputePipelines { - constructor(device, glslang) { - this.device = device - this.glslang = glslang - - this.pipelines = new WeakMap() - this.shaderModules = { - compute: new WeakMap(), - } - } - - get(param) { - let pipeline = this.pipelines.get(param) - - if (pipeline === undefined) { - const device = this.device - const shader = { - computeShader: param.shader, - } - - // shader modules - - const glslang = this.glslang - - let moduleCompute = this.shaderModules.compute.get(shader) - - if (moduleCompute === undefined) { - const byteCodeCompute = glslang.compileGLSL(shader.computeShader, 'compute') - - moduleCompute = device.createShaderModule({ code: byteCodeCompute }) - - this.shaderModules.compute.set(shader, moduleCompute) - } - - // - - const computeStage = { - module: moduleCompute, - entryPoint: 'main', - } - - pipeline = device.createComputePipeline({ - computeStage: computeStage, - }) - - this.pipelines.set(param, pipeline) - } - - return pipeline - } - - dispose() { - this.pipelines = new WeakMap() - this.shaderModules = { - compute: new WeakMap(), - } - } -} - -export default WebGPUComputePipelines diff --git a/src/renderers/webgpu/WebGPUGeometries.js b/src/renderers/webgpu/WebGPUGeometries.js deleted file mode 100644 index 5e6b4fd9..00000000 --- a/src/renderers/webgpu/WebGPUGeometries.js +++ /dev/null @@ -1,58 +0,0 @@ -class WebGPUGeometries { - constructor(attributes, info) { - this.attributes = attributes - this.info = info - - this.geometries = new WeakMap() - } - - update(geometry) { - if (this.geometries.has(geometry) === false) { - const disposeCallback = onGeometryDispose.bind(this) - - this.geometries.set(geometry, disposeCallback) - - this.info.memory.geometries++ - - geometry.addEventListener('dispose', disposeCallback) - } - - const geometryAttributes = geometry.attributes - - for (let name in geometryAttributes) { - this.attributes.update(geometryAttributes[name]) - } - - const index = geometry.index - - if (index !== null) { - this.attributes.update(index, true) - } - } -} - -function onGeometryDispose(event) { - const geometry = event.target - const disposeCallback = this.geometries.get(geometry) - - this.geometries.delete(geometry) - - this.info.memory.geometries-- - - geometry.removeEventListener('dispose', disposeCallback) - - // - - const index = geometry.index - const geometryAttributes = geometry.attributes - - if (index !== null) { - this.attributes.remove(index) - } - - for (let name in geometryAttributes) { - this.attributes.remove(geometryAttributes[name]) - } -} - -export default WebGPUGeometries diff --git a/src/renderers/webgpu/WebGPUInfo.js b/src/renderers/webgpu/WebGPUInfo.js deleted file mode 100644 index 311c42cf..00000000 --- a/src/renderers/webgpu/WebGPUInfo.js +++ /dev/null @@ -1,53 +0,0 @@ -class WebGPUInfo { - constructor() { - this.autoReset = true - - this.render = { - frame: 0, - drawCalls: 0, - triangles: 0, - points: 0, - lines: 0, - } - - this.memory = { - geometries: 0, - textures: 0, - } - } - - update(object, count, instanceCount) { - this.render.drawCalls++ - - if (object.isMesh) { - this.render.triangles += instanceCount * (count / 3) - } else if (object.isPoints) { - this.render.points += instanceCount * count - } else if (object.isLineSegments) { - this.render.lines += instanceCount * (count / 2) - } else if (object.isLine) { - this.render.lines += instanceCount * (count - 1) - } else { - console.error('THREE.WebGPUInfo: Unknown object type.') - } - } - - reset() { - this.render.frame++ - this.render.drawCalls = 0 - this.render.triangles = 0 - this.render.points = 0 - this.render.lines = 0 - } - - dispose() { - this.reset() - - this.render.frame = 0 - - this.memory.geometries = 0 - this.memory.textures = 0 - } -} - -export default WebGPUInfo diff --git a/src/renderers/webgpu/WebGPUObjects.js b/src/renderers/webgpu/WebGPUObjects.js deleted file mode 100644 index d88aaec2..00000000 --- a/src/renderers/webgpu/WebGPUObjects.js +++ /dev/null @@ -1,30 +0,0 @@ -class WebGPUObjects { - constructor(geometries, info) { - this.geometries = geometries - this.info = info - - this.updateMap = new WeakMap() - } - - update(object) { - const geometry = object.geometry - const updateMap = this.updateMap - const frame = this.info.render.frame - - if (geometry.isBufferGeometry !== true) { - throw 'THREE.WebGPURenderer: This renderer only supports THREE.BufferGeometry for geometries.' - } - - if (updateMap.get(geometry) !== frame) { - this.geometries.update(geometry) - - updateMap.set(geometry, frame) - } - } - - dispose() { - this.updateMap = new WeakMap() - } -} - -export default WebGPUObjects diff --git a/src/renderers/webgpu/WebGPUProperties.js b/src/renderers/webgpu/WebGPUProperties.js deleted file mode 100644 index 16db43d6..00000000 --- a/src/renderers/webgpu/WebGPUProperties.js +++ /dev/null @@ -1,26 +0,0 @@ -class WebGPUProperties { - constructor() { - this.properties = new WeakMap() - } - - get(object) { - let map = this.properties.get(object) - - if (map === undefined) { - map = {} - this.properties.set(object, map) - } - - return map - } - - remove(object) { - this.properties.delete(object) - } - - dispose() { - this.properties = new WeakMap() - } -} - -export default WebGPUProperties diff --git a/src/renderers/webgpu/WebGPURenderLists.js b/src/renderers/webgpu/WebGPURenderLists.js deleted file mode 100644 index f2a69ff9..00000000 --- a/src/renderers/webgpu/WebGPURenderLists.js +++ /dev/null @@ -1,141 +0,0 @@ -function painterSortStable(a, b) { - if (a.groupOrder !== b.groupOrder) { - return a.groupOrder - b.groupOrder - } else if (a.renderOrder !== b.renderOrder) { - return a.renderOrder - b.renderOrder - } else if (a.material.id !== b.material.id) { - return a.material.id - b.material.id - } else if (a.z !== b.z) { - return a.z - b.z - } else { - return a.id - b.id - } -} - -function reversePainterSortStable(a, b) { - if (a.groupOrder !== b.groupOrder) { - return a.groupOrder - b.groupOrder - } else if (a.renderOrder !== b.renderOrder) { - return a.renderOrder - b.renderOrder - } else if (a.z !== b.z) { - return b.z - a.z - } else { - return a.id - b.id - } -} - -class WebGPURenderList { - constructor() { - this.renderItems = [] - this.renderItemsIndex = 0 - - this.opaque = [] - this.transparent = [] - } - - init() { - this.renderItemsIndex = 0 - - this.opaque.length = 0 - this.transparent.length = 0 - } - - getNextRenderItem(object, geometry, material, groupOrder, z, group) { - let renderItem = this.renderItems[this.renderItemsIndex] - - if (renderItem === undefined) { - renderItem = { - id: object.id, - object: object, - geometry: geometry, - material: material, - groupOrder: groupOrder, - renderOrder: object.renderOrder, - z: z, - group: group, - } - - this.renderItems[this.renderItemsIndex] = renderItem - } else { - renderItem.id = object.id - renderItem.object = object - renderItem.geometry = geometry - renderItem.material = material - renderItem.groupOrder = groupOrder - renderItem.renderOrder = object.renderOrder - renderItem.z = z - renderItem.group = group - } - - this.renderItemsIndex++ - - return renderItem - } - - push(object, geometry, material, groupOrder, z, group) { - const renderItem = this.getNextRenderItem(object, geometry, material, groupOrder, z, group) - - ;(material.transparent === true ? this.transparent : this.opaque).push(renderItem) - } - - unshift(object, geometry, material, groupOrder, z, group) { - const renderItem = this.getNextRenderItem(object, geometry, material, groupOrder, z, group) - - ;(material.transparent === true ? this.transparent : this.opaque).unshift(renderItem) - } - - sort(customOpaqueSort, customTransparentSort) { - if (this.opaque.length > 1) this.opaque.sort(customOpaqueSort || painterSortStable) - if (this.transparent.length > 1) this.transparent.sort(customTransparentSort || reversePainterSortStable) - } - - finish() { - // Clear references from inactive renderItems in the list - - for (let i = this.renderItemsIndex, il = this.renderItems.length; i < il; i++) { - const renderItem = this.renderItems[i] - - if (renderItem.id === null) break - - renderItem.id = null - renderItem.object = null - renderItem.geometry = null - renderItem.material = null - renderItem.program = null - renderItem.group = null - } - } -} - -class WebGPURenderLists { - constructor() { - this.lists = new WeakMap() - } - - get(scene, camera) { - const lists = this.lists - - const cameras = lists.get(scene) - let list - - if (cameras === undefined) { - list = new WebGPURenderList() - lists.set(scene, new WeakMap()) - lists.get(scene).set(camera, list) - } else { - list = cameras.get(camera) - if (list === undefined) { - list = new WebGPURenderList() - cameras.set(camera, list) - } - } - - return list - } - - dispose() { - this.lists = new WeakMap() - } -} - -export default WebGPURenderLists diff --git a/src/renderers/webgpu/WebGPURenderPipelines.js b/src/renderers/webgpu/WebGPURenderPipelines.js deleted file mode 100644 index 761680d7..00000000 --- a/src/renderers/webgpu/WebGPURenderPipelines.js +++ /dev/null @@ -1,779 +0,0 @@ -import { - GPUPrimitiveTopology, - GPUIndexFormat, - GPUTextureFormat, - GPUCompareFunction, - GPUFrontFace, - GPUCullMode, - GPUVertexFormat, - GPUBlendFactor, - GPUBlendOperation, - BlendColorFactor, - OneMinusBlendColorFactor, - GPUColorWriteFlags, - GPUStencilOperation, - GPUInputStepMode, -} from './constants' -import { - FrontSide, - BackSide, - DoubleSide, - NeverDepth, - AlwaysDepth, - LessDepth, - LessEqualDepth, - EqualDepth, - GreaterEqualDepth, - GreaterDepth, - NotEqualDepth, - NeverStencilFunc, - AlwaysStencilFunc, - LessStencilFunc, - LessEqualStencilFunc, - EqualStencilFunc, - GreaterEqualStencilFunc, - GreaterStencilFunc, - NotEqualStencilFunc, - KeepStencilOp, - ZeroStencilOp, - ReplaceStencilOp, - InvertStencilOp, - IncrementStencilOp, - DecrementStencilOp, - IncrementWrapStencilOp, - DecrementWrapStencilOp, - NoBlending, - NormalBlending, - AdditiveBlending, - SubtractiveBlending, - MultiplyBlending, - CustomBlending, - AddEquation, - SubtractEquation, - ReverseSubtractEquation, - MinEquation, - MaxEquation, - ZeroFactor, - OneFactor, - SrcColorFactor, - OneMinusSrcColorFactor, - SrcAlphaFactor, - OneMinusSrcAlphaFactor, - DstAlphaFactor, - OneMinusDstAlphaFactor, - DstColorFactor, - OneMinusDstColorFactor, - SrcAlphaSaturateFactor, -} from 'three' - -class WebGPURenderPipelines { - constructor(renderer, properties, device, glslang, sampleCount, nodes) { - this.renderer = renderer - this.properties = properties - this.device = device - this.glslang = glslang - this.sampleCount = sampleCount - this.nodes = nodes - - this.pipelines = new WeakMap() - this.shaderAttributes = new WeakMap() - - this.shaderModules = { - vertex: new Map(), - fragment: new Map(), - } - } - - get(object) { - // @TODO: Avoid a 1:1 relationship between pipelines and objects. It's necessary - // to check various conditions in order to request an appropriate pipeline. - // - // - material's version and node configuration - // - environment map (material) - // - fog and environment (scene) - // - output encoding (renderer) - // - light state - // - clipping planes - // - // The renderer needs to manage multiple pipelines per object so - // GPUDevice.createRenderPipeline() is only called when no pipeline exists for the - // current configuration. - - let pipeline = this.pipelines.get(object) - - if (pipeline === undefined) { - const device = this.device - const properties = this.properties - - const material = object.material - - // get shader - - const nodeBuilder = this.nodes.get(material) - - // shader modules - - const glslang = this.glslang - - let moduleVertex = this.shaderModules.vertex.get(nodeBuilder.vertexShader) - - if (moduleVertex === undefined) { - const byteCodeVertex = glslang.compileGLSL(nodeBuilder.vertexShader, 'vertex') - - moduleVertex = { - module: device.createShaderModule({ code: byteCodeVertex }), - entryPoint: 'main', - } - - this.shaderModules.vertex.set(nodeBuilder.vertexShader, moduleVertex) - } - - let moduleFragment = this.shaderModules.fragment.get(nodeBuilder.fragmentShader) - - if (moduleFragment === undefined) { - const byteCodeFragment = glslang.compileGLSL(nodeBuilder.fragmentShader, 'fragment') - - moduleFragment = { - module: device.createShaderModule({ code: byteCodeFragment }), - entryPoint: 'main', - } - - this.shaderModules.fragment.set(nodeBuilder.fragmentShader, moduleFragment) - } - - // dispose material - - const materialProperties = properties.get(material) - - if (materialProperties.disposeCallback === undefined) { - const disposeCallback = onMaterialDispose.bind(this) - materialProperties.disposeCallback = disposeCallback - - material.addEventListener('dispose', disposeCallback) - } - - // determine shader attributes - - const shaderAttributes = this._parseShaderAttributes(nodeBuilder.vertexShader) - - // vertex buffers - - const vertexBuffers = [] - const geometry = object.geometry - - for (let attribute of shaderAttributes) { - const name = attribute.name - const geometryAttribute = geometry.getAttribute(name) - const stepMode = - geometryAttribute !== undefined && geometryAttribute.isInstancedBufferAttribute - ? GPUInputStepMode.Instance - : GPUInputStepMode.Vertex - - vertexBuffers.push({ - arrayStride: attribute.arrayStride, - attributes: [ - { - shaderLocation: attribute.slot, - offset: 0, - format: attribute.format, - }, - ], - stepMode: stepMode, - }) - } - - // - - let indexFormat - - if (object.isLine) { - const count = geometry.index ? geometry.index.count : geometry.attributes.position.count - - indexFormat = count > 65535 ? GPUIndexFormat.Uint32 : GPUIndexFormat.Uint16 // define data type for primitive restart value - } - - // - - let alphaBlend = {} - let colorBlend = {} - - if (material.transparent === true && material.blending !== NoBlending) { - alphaBlend = this._getAlphaBlend(material) - colorBlend = this._getColorBlend(material) - } - - // - - let stencilFront = {} - - if (material.stencilWrite === true) { - stencilFront = { - compare: this._getStencilCompare(material), - failOp: this._getStencilOperation(material.stencilFail), - depthFailOp: this._getStencilOperation(material.stencilZFail), - passOp: this._getStencilOperation(material.stencilZPass), - } - } - - // pipeline - - const primitiveTopology = this._getPrimitiveTopology(object) - const rasterizationState = this._getRasterizationStateDescriptor(material) - const colorWriteMask = this._getColorWriteMask(material) - const depthCompare = this._getDepthCompare(material) - const colorFormat = this._getColorFormat(this.renderer) - const depthStencilFormat = this._getDepthStencilFormat(this.renderer) - - pipeline = device.createRenderPipeline({ - vertexStage: moduleVertex, - fragmentStage: moduleFragment, - primitiveTopology: primitiveTopology, - rasterizationState: rasterizationState, - colorStates: [ - { - format: colorFormat, - alphaBlend: alphaBlend, - colorBlend: colorBlend, - writeMask: colorWriteMask, - }, - ], - depthStencilState: { - format: depthStencilFormat, - depthWriteEnabled: material.depthWrite, - depthCompare: depthCompare, - stencilFront: stencilFront, - stencilBack: {}, // three.js does not provide an API to configure the back function (gl.stencilFuncSeparate() was never used) - stencilReadMask: material.stencilFuncMask, - stencilWriteMask: material.stencilWriteMask, - }, - vertexState: { - indexFormat: indexFormat, - vertexBuffers: vertexBuffers, - }, - sampleCount: this.sampleCount, - }) - - this.pipelines.set(object, pipeline) - this.shaderAttributes.set(pipeline, shaderAttributes) - } - - return pipeline - } - - getShaderAttributes(pipeline) { - return this.shaderAttributes.get(pipeline) - } - - dispose() { - this.pipelines = new WeakMap() - this.shaderAttributes = new WeakMap() - this.shaderModules = { - vertex: new Map(), - fragment: new Map(), - } - } - - _getArrayStride(type) { - // @TODO: This code is GLSL specific. We need to update when we switch to WGSL. - - if (type === 'float') return 4 - if (type === 'vec2') return 8 - if (type === 'vec3') return 12 - if (type === 'vec4') return 16 - - if (type === 'int') return 4 - if (type === 'ivec2') return 8 - if (type === 'ivec3') return 12 - if (type === 'ivec4') return 16 - - if (type === 'uint') return 4 - if (type === 'uvec2') return 8 - if (type === 'uvec3') return 12 - if (type === 'uvec4') return 16 - - console.error('THREE.WebGPURenderer: Shader variable type not supported yet.', type) - } - - _getAlphaBlend(material) { - const blending = material.blending - const premultipliedAlpha = material.premultipliedAlpha - - let alphaBlend = undefined - - switch (blending) { - case NormalBlending: - if (premultipliedAlpha === false) { - alphaBlend = { - srcFactor: GPUBlendFactor.One, - dstFactor: GPUBlendFactor.OneMinusSrcAlpha, - operation: GPUBlendOperation.Add, - } - } - - break - - case AdditiveBlending: - // no alphaBlend settings - break - - case SubtractiveBlending: - if (premultipliedAlpha === true) { - alphaBlend = { - srcFactor: GPUBlendFactor.OneMinusSrcColor, - dstFactor: GPUBlendFactor.OneMinusSrcAlpha, - operation: GPUBlendOperation.Add, - } - } - - break - - case MultiplyBlending: - if (premultipliedAlpha === true) { - alphaBlend = { - srcFactor: GPUBlendFactor.Zero, - dstFactor: GPUBlendFactor.SrcAlpha, - operation: GPUBlendOperation.Add, - } - } - - break - - case CustomBlending: - const blendSrcAlpha = material.blendSrcAlpha - const blendDstAlpha = material.blendDstAlpha - const blendEquationAlpha = material.blendEquationAlpha - - if (blendSrcAlpha !== null && blendDstAlpha !== null && blendEquationAlpha !== null) { - alphaBlend = { - srcFactor: this._getBlendFactor(blendSrcAlpha), - dstFactor: this._getBlendFactor(blendDstAlpha), - operation: this._getBlendOperation(blendEquationAlpha), - } - } - - break - - default: - console.error('THREE.WebGPURenderer: Blending not supported.', blending) - } - - return alphaBlend - } - - _getBlendFactor(blend) { - let blendFactor - - switch (blend) { - case ZeroFactor: - blendFactor = GPUBlendFactor.Zero - break - - case OneFactor: - blendFactor = GPUBlendFactor.One - break - - case SrcColorFactor: - blendFactor = GPUBlendFactor.SrcColor - break - - case OneMinusSrcColorFactor: - blendFactor = GPUBlendFactor.OneMinusSrcColor - break - - case SrcAlphaFactor: - blendFactor = GPUBlendFactor.SrcAlpha - break - - case OneMinusSrcAlphaFactor: - blendFactor = GPUBlendFactor.OneMinusSrcAlpha - break - - case DstColorFactor: - blendFactor = GPUBlendFactor.DstColor - break - - case OneMinusDstColorFactor: - blendFactor = GPUBlendFactor.OneMinusDstColor - break - - case DstAlphaFactor: - blendFactor = GPUBlendFactor.DstAlpha - break - - case OneMinusDstAlphaFactor: - blendFactor = GPUBlendFactor.OneMinusDstAlpha - break - - case SrcAlphaSaturateFactor: - blendFactor = GPUBlendFactor.SrcAlphaSaturated - break - - case BlendColorFactor: - blendFactor = GPUBlendFactor.BlendColor - break - - case OneMinusBlendColorFactor: - blendFactor = GPUBlendFactor.OneMinusBlendColor - break - - default: - console.error('THREE.WebGPURenderer: Blend factor not supported.', blend) - } - - return blendFactor - } - - _getBlendOperation(blendEquation) { - let blendOperation - - switch (blendEquation) { - case AddEquation: - blendOperation = GPUBlendOperation.Add - break - - case SubtractEquation: - blendOperation = GPUBlendOperation.Subtract - break - - case ReverseSubtractEquation: - blendOperation = GPUBlendOperation.ReverseSubtract - break - - case MinEquation: - blendOperation = GPUBlendOperation.Min - break - - case MaxEquation: - blendOperation = GPUBlendOperation.Max - break - - default: - console.error('THREE.WebGPURenderer: Blend equation not supported.', blendEquation) - } - - return blendOperation - } - - _getColorBlend(material) { - const blending = material.blending - const premultipliedAlpha = material.premultipliedAlpha - - const colorBlend = { - srcFactor: null, - dstFactor: null, - operation: null, - } - - switch (blending) { - case NormalBlending: - colorBlend.srcFactor = premultipliedAlpha === true ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha - colorBlend.dstFactor = GPUBlendFactor.OneMinusSrcAlpha - colorBlend.operation = GPUBlendOperation.Add - break - - case AdditiveBlending: - colorBlend.srcFactor = premultipliedAlpha === true ? GPUBlendFactor.One : GPUBlendFactor.SrcAlpha - colorBlend.operation = GPUBlendOperation.Add - break - - case SubtractiveBlending: - colorBlend.srcFactor = GPUBlendFactor.Zero - colorBlend.dstFactor = premultipliedAlpha === true ? GPUBlendFactor.Zero : GPUBlendFactor.OneMinusSrcColor - colorBlend.operation = GPUBlendOperation.Add - break - - case MultiplyBlending: - colorBlend.srcFactor = GPUBlendFactor.Zero - colorBlend.dstFactor = GPUBlendFactor.SrcColor - colorBlend.operation = GPUBlendOperation.Add - break - - case CustomBlending: - colorBlend.srcFactor = this._getBlendFactor(material.blendSrc) - colorBlend.dstFactor = this._getBlendFactor(material.blendDst) - colorBlend.operation = this._getBlendOperation(material.blendEquation) - break - - default: - console.error('THREE.WebGPURenderer: Blending not supported.', blending) - } - - return colorBlend - } - - _getColorFormat(renderer) { - let format - - const renderTarget = renderer.getRenderTarget() - - if (renderTarget !== null) { - const renderTargetProperties = this.properties.get(renderTarget) - format = renderTargetProperties.colorTextureFormat - } else { - format = GPUTextureFormat.BRGA8Unorm // default swap chain format - } - - return format - } - - _getColorWriteMask(material) { - return material.colorWrite === true ? GPUColorWriteFlags.All : GPUColorWriteFlags.None - } - - _getDepthCompare(material) { - let depthCompare - - if (material.depthTest === false) { - depthCompare = GPUCompareFunction.Always - } else { - const depthFunc = material.depthFunc - - switch (depthFunc) { - case NeverDepth: - depthCompare = GPUCompareFunction.Never - break - - case AlwaysDepth: - depthCompare = GPUCompareFunction.Always - break - - case LessDepth: - depthCompare = GPUCompareFunction.Less - break - - case LessEqualDepth: - depthCompare = GPUCompareFunction.LessEqual - break - - case EqualDepth: - depthCompare = GPUCompareFunction.Equal - break - - case GreaterEqualDepth: - depthCompare = GPUCompareFunction.GreaterEqual - break - - case GreaterDepth: - depthCompare = GPUCompareFunction.Greater - break - - case NotEqualDepth: - depthCompare = GPUCompareFunction.NotEqual - break - - default: - console.error('THREE.WebGPURenderer: Invalid depth function.', depthFunc) - } - } - - return depthCompare - } - - _getDepthStencilFormat(renderer) { - let format - - const renderTarget = renderer.getRenderTarget() - - if (renderTarget !== null) { - const renderTargetProperties = this.properties.get(renderTarget) - format = renderTargetProperties.depthTextureFormat - } else { - format = GPUTextureFormat.Depth24PlusStencil8 - } - - return format - } - - _getPrimitiveTopology(object) { - if (object.isMesh) return GPUPrimitiveTopology.TriangleList - else if (object.isPoints) return GPUPrimitiveTopology.PointList - else if (object.isLine) return GPUPrimitiveTopology.LineStrip - else if (object.isLineSegments) return GPUPrimitiveTopology.LineList - } - - _getRasterizationStateDescriptor(material) { - const descriptor = {} - - switch (material.side) { - case FrontSide: - descriptor.frontFace = GPUFrontFace.CCW - descriptor.cullMode = GPUCullMode.Back - break - - case BackSide: - descriptor.frontFace = GPUFrontFace.CW - descriptor.cullMode = GPUCullMode.Back - break - - case DoubleSide: - descriptor.frontFace = GPUFrontFace.CCW - descriptor.cullMode = GPUCullMode.None - break - - default: - console.error('THREE.WebGPURenderer: Unknown Material.side value.', material.side) - break - } - - return descriptor - } - - _getStencilCompare(material) { - let stencilCompare - - const stencilFunc = material.stencilFunc - - switch (stencilFunc) { - case NeverStencilFunc: - stencilCompare = GPUCompareFunction.Never - break - - case AlwaysStencilFunc: - stencilCompare = GPUCompareFunction.Always - break - - case LessStencilFunc: - stencilCompare = GPUCompareFunction.Less - break - - case LessEqualStencilFunc: - stencilCompare = GPUCompareFunction.LessEqual - break - - case EqualStencilFunc: - stencilCompare = GPUCompareFunction.Equal - break - - case GreaterEqualStencilFunc: - stencilCompare = GPUCompareFunction.GreaterEqual - break - - case GreaterStencilFunc: - stencilCompare = GPUCompareFunction.Greater - break - - case NotEqualStencilFunc: - stencilCompare = GPUCompareFunction.NotEqual - break - - default: - console.error('THREE.WebGPURenderer: Invalid stencil function.', stencilFunc) - } - - return stencilCompare - } - - _getStencilOperation(op) { - let stencilOperation - - switch (op) { - case KeepStencilOp: - stencilOperation = GPUStencilOperation.Keep - break - - case ZeroStencilOp: - stencilOperation = GPUStencilOperation.Zero - break - - case ReplaceStencilOp: - stencilOperation = GPUStencilOperation.Replace - break - - case InvertStencilOp: - stencilOperation = GPUStencilOperation.Invert - break - - case IncrementStencilOp: - stencilOperation = GPUStencilOperation.IncrementClamp - break - - case DecrementStencilOp: - stencilOperation = GPUStencilOperation.DecrementClamp - break - - case IncrementWrapStencilOp: - stencilOperation = GPUStencilOperation.IncrementWrap - break - - case DecrementWrapStencilOp: - stencilOperation = GPUStencilOperation.DecrementWrap - break - - default: - console.error('THREE.WebGPURenderer: Invalid stencil operation.', stencilOperation) - } - - return stencilOperation - } - - _getVertexFormat(type) { - // @TODO: This code is GLSL specific. We need to update when we switch to WGSL. - - if (type === 'float') return GPUVertexFormat.Float - if (type === 'vec2') return GPUVertexFormat.Float2 - if (type === 'vec3') return GPUVertexFormat.Float3 - if (type === 'vec4') return GPUVertexFormat.Float4 - - if (type === 'int') return GPUVertexFormat.Int - if (type === 'ivec2') return GPUVertexFormat.Int2 - if (type === 'ivec3') return GPUVertexFormat.Int3 - if (type === 'ivec4') return GPUVertexFormat.Int4 - - if (type === 'uint') return GPUVertexFormat.UInt - if (type === 'uvec2') return GPUVertexFormat.UInt2 - if (type === 'uvec3') return GPUVertexFormat.UInt3 - if (type === 'uvec4') return GPUVertexFormat.UInt4 - - console.error('THREE.WebGPURenderer: Shader variable type not supported yet.', type) - } - - _parseShaderAttributes(shader) { - // find "layout (location = num) in type name" in vertex shader - - const regex = /\s*layout\s*\(\s*location\s*=\s*(?[0-9]+)\s*\)\s*in\s+(?\w+)\s+(?\w+)\s*;/gim - let shaderAttribute = null - - const attributes = [] - - while ((shaderAttribute = regex.exec(shader))) { - const shaderLocation = parseInt(shaderAttribute.groups.location) - const arrayStride = this._getArrayStride(shaderAttribute.groups.type) - const vertexFormat = this._getVertexFormat(shaderAttribute.groups.type) - - attributes.push({ - name: shaderAttribute.groups.name, - arrayStride: arrayStride, - slot: shaderLocation, - format: vertexFormat, - }) - } - - // the sort ensures to setup vertex buffers in the correct order - - return attributes.sort(function (a, b) { - return a.slot - b.slot - }) - } -} - -function onMaterialDispose(event) { - const properties = this.properties - const nodes = this.nodes - const shaderModules = this.shaderModules - - const material = event.target - const materialProperties = properties.get(material) - const nodeBuilder = nodes.get(material) - - material.removeEventListener('dispose', materialProperties.disposeCallback) - - properties.remove(material) - nodes.remove(material) - - shaderModules.vertex.delete(nodeBuilder.vertexShader) - shaderModules.fragment.delete(nodeBuilder.fragmentShader) - - // @TODO: still needed remove bindings and pipeline -} - -export default WebGPURenderPipelines diff --git a/src/renderers/webgpu/WebGPURenderer.js b/src/renderers/webgpu/WebGPURenderer.js deleted file mode 100644 index 160af833..00000000 --- a/src/renderers/webgpu/WebGPURenderer.js +++ /dev/null @@ -1,767 +0,0 @@ -import { GPUIndexFormat, GPUTextureFormat, GPUStoreOp } from './constants' -import WebGPUObjects from './WebGPUObjects' -import WebGPUAttributes from './WebGPUAttributes' -import WebGPUGeometries from './WebGPUGeometries' -import WebGPUInfo from './WebGPUInfo' -import WebGPUProperties from './WebGPUProperties' -import WebGPURenderPipelines from './WebGPURenderPipelines' -import WebGPUComputePipelines from './WebGPUComputePipelines' -import WebGPUBindings from './WebGPUBindings' -import WebGPURenderLists from './WebGPURenderLists' -import WebGPUTextures from './WebGPUTextures' -import WebGPUBackground from './WebGPUBackground' -import WebGPUNodes from './nodes/WebGPUNodes' - -import { Frustum, Matrix4, Vector3, Color } from 'three' - -console.info( - 'THREE.WebGPURenderer: Modified Matrix4.makePerspective() and Matrix4.makeOrtographic() to work with WebGPU, see https://github.com/mrdoob/three.js/issues/20276.', -) - -Matrix4.prototype.makePerspective = function (left, right, top, bottom, near, far) { - const te = this.elements - const x = (2 * near) / (right - left) - const y = (2 * near) / (top - bottom) - - const a = (right + left) / (right - left) - const b = (top + bottom) / (top - bottom) - const c = -far / (far - near) - const d = (-far * near) / (far - near) - - te[0] = x - te[4] = 0 - te[8] = a - te[12] = 0 - te[1] = 0 - te[5] = y - te[9] = b - te[13] = 0 - te[2] = 0 - te[6] = 0 - te[10] = c - te[14] = d - te[3] = 0 - te[7] = 0 - te[11] = -1 - te[15] = 0 - - return this -} - -Matrix4.prototype.makeOrthographic = function (left, right, top, bottom, near, far) { - const te = this.elements - const w = 1.0 / (right - left) - const h = 1.0 / (top - bottom) - const p = 1.0 / (far - near) - - const x = (right + left) * w - const y = (top + bottom) * h - const z = near * p - - te[0] = 2 * w - te[4] = 0 - te[8] = 0 - te[12] = -x - te[1] = 0 - te[5] = 2 * h - te[9] = 0 - te[13] = -y - te[2] = 0 - te[6] = 0 - te[10] = -1 * p - te[14] = -z - te[3] = 0 - te[7] = 0 - te[11] = 0 - te[15] = 1 - - return this -} - -const _frustum = new Frustum() -const _projScreenMatrix = new Matrix4() -const _vector3 = new Vector3() - -class WebGPURenderer { - constructor(parameters = {}) { - // public - - this.domElement = parameters.canvas !== undefined ? parameters.canvas : this._createCanvasElement() - - this.autoClear = true - this.autoClearColor = true - this.autoClearDepth = true - this.autoClearStencil = true - - this.sortObjects = true - - // internals - - this._parameters = Object.assign({}, parameters) - - this._pixelRatio = 1 - this._width = this.domElement.width - this._height = this.domElement.height - - this._viewport = null - this._scissor = null - - this._adapter = null - this._device = null - this._context = null - this._swapChain = null - this._colorBuffer = null - this._depthBuffer = null - - this._info = null - this._properties = null - this._attributes = null - this._geometries = null - this._nodes = null - this._bindings = null - this._objects = null - this._renderPipelines = null - this._computePipelines = null - this._renderLists = null - this._textures = null - this._background = null - - this._renderPassDescriptor = null - - this._currentRenderList = null - this._opaqueSort = null - this._transparentSort = null - - this._clearAlpha = 1 - this._clearColor = new Color(0x000000) - this._clearDepth = 1 - this._clearStencil = 0 - - this._renderTarget = null - - // some parameters require default values other than "undefined" - - this._parameters.antialias = parameters.antialias === true - - if (this._parameters.antialias === true) { - this._parameters.sampleCount = parameters.sampleCount === undefined ? 4 : parameters.sampleCount - } else { - this._parameters.sampleCount = 1 - } - - this._parameters.extensions = parameters.extensions === undefined ? [] : parameters.extensions - this._parameters.limits = parameters.limits === undefined ? {} : parameters.limits - } - - async init() { - const parameters = this._parameters - - const adapterOptions = { - powerPreference: parameters.powerPreference, - } - - const adapter = await navigator.gpu.requestAdapter(adapterOptions) - - const deviceDescriptor = { - extensions: parameters.extensions, - limits: parameters.limits, - } - - const device = await adapter.requestDevice(deviceDescriptor) - - const glslang = await import('@webgpu/glslang/dist/web-devel/glslang.js') - const compiler = await glslang() - - const context = parameters.context !== undefined ? parameters.context : this.domElement.getContext('gpupresent') - - const swapChain = context.configureSwapChain({ - device: device, - format: GPUTextureFormat.BRGA8Unorm, // this is the only valid swap chain format right now (r121) - }) - - this._adapter = adapter - this._device = device - this._context = context - this._swapChain = swapChain - - this._info = new WebGPUInfo() - this._properties = new WebGPUProperties() - this._attributes = new WebGPUAttributes(device) - this._geometries = new WebGPUGeometries(this._attributes, this._info) - this._textures = new WebGPUTextures(device, this._properties, this._info, compiler) - this._objects = new WebGPUObjects(this._geometries, this._info) - this._nodes = new WebGPUNodes(this) - this._renderPipelines = new WebGPURenderPipelines( - this, - this._properties, - device, - compiler, - parameters.sampleCount, - this._nodes, - ) - this._computePipelines = new WebGPUComputePipelines(device, compiler) - this._bindings = new WebGPUBindings( - device, - this._info, - this._properties, - this._textures, - this._renderPipelines, - this._computePipelines, - this._attributes, - this._nodes, - ) - this._renderLists = new WebGPURenderLists() - this._background = new WebGPUBackground(this) - - // - - this._renderPassDescriptor = { - colorAttachments: [ - { - attachment: null, - }, - ], - depthStencilAttachment: { - attachment: null, - depthStoreOp: GPUStoreOp.Store, - stencilStoreOp: GPUStoreOp.Store, - }, - } - - this._setupColorBuffer() - this._setupDepthBuffer() - } - - render(scene, camera) { - // @TODO: move this to animation loop - - this._nodes.updateFrame() - - // - - if (scene.autoUpdate === true) scene.updateMatrixWorld() - - if (camera.parent === null) camera.updateMatrixWorld() - - if (this._info.autoReset === true) this._info.reset() - - _projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse) - _frustum.setFromProjectionMatrix(_projScreenMatrix) - - this._currentRenderList = this._renderLists.get(scene, camera) - this._currentRenderList.init() - - this._projectObject(scene, camera, 0) - - this._currentRenderList.finish() - - if (this.sortObjects === true) { - this._currentRenderList.sort(this._opaqueSort, this._transparentSort) - } - - // prepare render pass descriptor - - const colorAttachment = this._renderPassDescriptor.colorAttachments[0] - const depthStencilAttachment = this._renderPassDescriptor.depthStencilAttachment - - const renderTarget = this._renderTarget - - if (renderTarget !== null) { - // @TODO: Support RenderTarget with antialiasing. - - const renderTargetProperties = this._properties.get(renderTarget) - - colorAttachment.attachment = renderTargetProperties.colorTextureGPU.createView() - depthStencilAttachment.attachment = renderTargetProperties.depthTextureGPU.createView() - } else { - if (this._parameters.antialias === true) { - colorAttachment.attachment = this._colorBuffer.createView() - colorAttachment.resolveTarget = this._swapChain.getCurrentTexture().createView() - } else { - colorAttachment.attachment = this._swapChain.getCurrentTexture().createView() - colorAttachment.resolveTarget = undefined - } - - depthStencilAttachment.attachment = this._depthBuffer.createView() - } - - // - - this._background.update(scene) - - // start render pass - - const device = this._device - const cmdEncoder = device.createCommandEncoder({}) - const passEncoder = cmdEncoder.beginRenderPass(this._renderPassDescriptor) - - // global rasterization settings for all renderable objects - - const vp = this._viewport - - if (vp !== null) { - const width = Math.floor(vp.width * this._pixelRatio) - const height = Math.floor(vp.height * this._pixelRatio) - - passEncoder.setViewport(vp.x, vp.y, width, height, vp.minDepth, vp.maxDepth) - } - - const sc = this._scissor - - if (sc !== null) { - const width = Math.floor(sc.width * this._pixelRatio) - const height = Math.floor(sc.height * this._pixelRatio) - - passEncoder.setScissorRect(sc.x, sc.y, width, height) - } - - // process render lists - - const opaqueObjects = this._currentRenderList.opaque - const transparentObjects = this._currentRenderList.transparent - - if (opaqueObjects.length > 0) this._renderObjects(opaqueObjects, camera, passEncoder) - if (transparentObjects.length > 0) this._renderObjects(transparentObjects, camera, passEncoder) - - // finish render pass - - passEncoder.endPass() - device.queue.submit([cmdEncoder.finish()]) - } - - getContext() { - return this._context - } - - getPixelRatio() { - return this._pixelRatio - } - - getDrawingBufferSize(target) { - return target.set(this._width * this._pixelRatio, this._height * this._pixelRatio).floor() - } - - getSize(target) { - return target.set(this._width, this._height) - } - - setPixelRatio(value = 1) { - this._pixelRatio = value - - this.setSize(this._width, this._height, false) - } - - setDrawingBufferSize(width, height, pixelRatio) { - this._width = width - this._height = height - - this._pixelRatio = pixelRatio - - this.domElement.width = Math.floor(width * pixelRatio) - this.domElement.height = Math.floor(height * pixelRatio) - - this._setupColorBuffer() - this._setupDepthBuffer() - } - - setSize(width, height, updateStyle = true) { - this._width = width - this._height = height - - this.domElement.width = Math.floor(width * this._pixelRatio) - this.domElement.height = Math.floor(height * this._pixelRatio) - - if (updateStyle === true) { - this.domElement.style.width = width + 'px' - this.domElement.style.height = height + 'px' - } - - this._setupColorBuffer() - this._setupDepthBuffer() - } - - setOpaqueSort(method) { - this._opaqueSort = method - } - - setTransparentSort(method) { - this._transparentSort = method - } - - getScissor(target) { - const scissor = this._scissor - - target.x = scissor.x - target.y = scissor.y - target.width = scissor.width - target.height = scissor.height - - return target - } - - setScissor(x, y, width, height) { - if (x === null) { - this._scissor = null - } else { - this._scissor = { - x: x, - y: y, - width: width, - height: height, - } - } - } - - getViewport(target) { - const viewport = this._viewport - - target.x = viewport.x - target.y = viewport.y - target.width = viewport.width - target.height = viewport.height - target.minDepth = viewport.minDepth - target.maxDepth = viewport.maxDepth - - return target - } - - setViewport(x, y, width, height, minDepth = 0, maxDepth = 1) { - if (x === null) { - this._viewport = null - } else { - this._viewport = { - x: x, - y: y, - width: width, - height: height, - minDepth: minDepth, - maxDepth: maxDepth, - } - } - } - - getClearColor(target) { - return target.copy(this._clearColor) - } - - setClearColor(color, alpha = 1) { - this._clearColor.set(color) - this._clearAlpha = alpha - } - - getClearAlpha() { - return this._clearAlpha - } - - setClearAlpha(alpha) { - this._clearAlpha = alpha - } - - getClearDepth() { - return this._clearDepth - } - - setClearDepth(depth) { - this._clearDepth = depth - } - - getClearStencil() { - return this._clearStencil - } - - setClearStencil(stencil) { - this._clearStencil = stencil - } - - clear() { - this._background.clear() - } - - dispose() { - this._objects.dispose() - this._properties.dispose() - this._renderPipelines.dispose() - this._computePipelines.dispose() - this._nodes.dispose() - this._bindings.dispose() - this._info.dispose() - this._renderLists.dispose() - this._textures.dispose() - } - - setRenderTarget(renderTarget) { - this._renderTarget = renderTarget - - if (renderTarget !== null) { - this._textures.initRenderTarget(renderTarget) - } - } - - compute(computeParams) { - const device = this._device - const cmdEncoder = device.createCommandEncoder({}) - const passEncoder = cmdEncoder.beginComputePass() - - for (let param of computeParams) { - // pipeline - - const pipeline = this._computePipelines.get(param) - passEncoder.setPipeline(pipeline) - - // bind group - - const bindGroup = this._bindings.getForCompute(param).group - this._bindings.update(param) - passEncoder.setBindGroup(0, bindGroup) - - passEncoder.dispatch(param.num) - } - - passEncoder.endPass() - device.queue.submit([cmdEncoder.finish()]) - } - - getRenderTarget() { - return this._renderTarget - } - - _projectObject(object, camera, groupOrder) { - const info = this._info - const currentRenderList = this._currentRenderList - - if (object.visible === false) return - - const visible = object.layers.test(camera.layers) - - if (visible) { - if (object.isGroup) { - groupOrder = object.renderOrder - } else if (object.isLOD) { - if (object.autoUpdate === true) object.update(camera) - } else if (object.isLight) { - //currentRenderState.pushLight( object ); - - if (object.castShadow) { - //currentRenderState.pushShadow( object ); - } - } else if (object.isSprite) { - if (!object.frustumCulled || _frustum.intersectsSprite(object)) { - if (this.sortObjects === true) { - _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix) - } - - const geometry = object.geometry - const material = object.material - - if (material.visible) { - currentRenderList.push(object, geometry, material, groupOrder, _vector3.z, null) - } - } - } else if (object.isLineLoop) { - console.error( - 'THREE.WebGPURenderer: Objects of type THREE.LineLoop are not supported. Please use THREE.Line or THREE.LineSegments.', - ) - } else if (object.isMesh || object.isLine || object.isPoints) { - if (object.isSkinnedMesh) { - // update skeleton only once in a frame - - if (object.skeleton.frame !== info.render.frame) { - object.skeleton.update() - object.skeleton.frame = info.render.frame - } - } - - if (!object.frustumCulled || _frustum.intersectsObject(object)) { - if (this.sortObjects === true) { - _vector3.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix) - } - - const geometry = object.geometry - const material = object.material - - if (Array.isArray(material)) { - const groups = geometry.groups - - for (let i = 0, l = groups.length; i < l; i++) { - const group = groups[i] - const groupMaterial = material[group.materialIndex] - - if (groupMaterial && groupMaterial.visible) { - currentRenderList.push(object, geometry, groupMaterial, groupOrder, _vector3.z, group) - } - } - } else if (material.visible) { - currentRenderList.push(object, geometry, material, groupOrder, _vector3.z, null) - } - } - } - } - - const children = object.children - - for (let i = 0, l = children.length; i < l; i++) { - this._projectObject(children[i], camera, groupOrder) - } - } - - _renderObjects(renderList, camera, passEncoder) { - // process renderable objects - - for (let i = 0, il = renderList.length; i < il; i++) { - const renderItem = renderList[i] - - // @TODO: Add support for multiple materials per object. This will require to extract - // the material from the renderItem object and pass it with its group data to _renderObject(). - - const object = renderItem.object - - object.modelViewMatrix.multiplyMatrices(camera.matrixWorldInverse, object.matrixWorld) - object.normalMatrix.getNormalMatrix(object.modelViewMatrix) - - this._objects.update(object) - - if (camera.isArrayCamera) { - const cameras = camera.cameras - - for (let j = 0, jl = cameras.length; j < jl; j++) { - const camera2 = cameras[j] - - if (object.layers.test(camera2.layers)) { - const vp = camera2.viewport - const minDepth = vp.minDepth === undefined ? 0 : vp.minDepth - const maxDepth = vp.maxDepth === undefined ? 1 : vp.maxDepth - - passEncoder.setViewport(vp.x, vp.y, vp.width, vp.height, minDepth, maxDepth) - - this._nodes.update(object, camera2) - this._bindings.update(object, camera2) - this._renderObject(object, passEncoder) - } - } - } else { - this._nodes.update(object, camera) - this._bindings.update(object, camera) - this._renderObject(object, passEncoder) - } - } - } - - _renderObject(object, passEncoder) { - const info = this._info - - // pipeline - - const pipeline = this._renderPipelines.get(object) - passEncoder.setPipeline(pipeline) - - // bind group - - const bindGroup = this._bindings.get(object).group - passEncoder.setBindGroup(0, bindGroup) - - // index - - const geometry = object.geometry - const index = geometry.index - - const hasIndex = index !== null - - if (hasIndex === true) { - this._setupIndexBuffer(index, passEncoder) - } - - // vertex buffers - - this._setupVertexBuffers(geometry.attributes, passEncoder, pipeline) - - // draw - - const drawRange = geometry.drawRange - const firstVertex = drawRange.start - const instanceCount = geometry.isInstancedBufferGeometry ? geometry.instanceCount : 1 - - if (hasIndex === true) { - const indexCount = drawRange.count !== Infinity ? drawRange.count : index.count - - passEncoder.drawIndexed(indexCount, instanceCount, firstVertex, 0, 0) - - info.update(object, indexCount, instanceCount) - } else { - const positionAttribute = geometry.attributes.position - const vertexCount = drawRange.count !== Infinity ? drawRange.count : positionAttribute.count - - passEncoder.draw(vertexCount, instanceCount, firstVertex, 0) - - info.update(object, vertexCount, instanceCount) - } - } - - _setupIndexBuffer(index, encoder) { - const buffer = this._attributes.get(index).buffer - const indexFormat = index.array instanceof Uint16Array ? GPUIndexFormat.Uint16 : GPUIndexFormat.Uint32 - - encoder.setIndexBuffer(buffer, indexFormat) - } - - _setupVertexBuffers(geometryAttributes, encoder, pipeline) { - const shaderAttributes = this._renderPipelines.getShaderAttributes(pipeline) - - for (let shaderAttribute of shaderAttributes) { - const name = shaderAttribute.name - const slot = shaderAttribute.slot - - const attribute = geometryAttributes[name] - - if (attribute !== undefined) { - const buffer = this._attributes.get(attribute).buffer - encoder.setVertexBuffer(slot, buffer) - } - } - } - - _setupColorBuffer() { - const device = this._device - - if (device) { - if (this._colorBuffer) this._colorBuffer.destroy() - - this._colorBuffer = this._device.createTexture({ - size: { - width: this._width * this._pixelRatio, - height: this._height * this._pixelRatio, - depthOrArrayLayers: 1, - }, - sampleCount: this._parameters.sampleCount, - format: GPUTextureFormat.BRGA8Unorm, - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }) - } - } - - _setupDepthBuffer() { - const device = this._device - - if (device) { - if (this._depthBuffer) this._depthBuffer.destroy() - - this._depthBuffer = this._device.createTexture({ - size: { - width: this._width * this._pixelRatio, - height: this._height * this._pixelRatio, - depthOrArrayLayers: 1, - }, - sampleCount: this._parameters.sampleCount, - format: GPUTextureFormat.Depth24PlusStencil8, - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }) - } - } - - _createCanvasElement() { - const canvas = document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas') - canvas.style.display = 'block' - return canvas - } -} - -export default WebGPURenderer diff --git a/src/renderers/webgpu/WebGPUSampledTexture.js b/src/renderers/webgpu/WebGPUSampledTexture.js deleted file mode 100644 index fd8b1cf3..00000000 --- a/src/renderers/webgpu/WebGPUSampledTexture.js +++ /dev/null @@ -1,51 +0,0 @@ -import WebGPUBinding from './WebGPUBinding' -import { GPUBindingType, GPUTextureViewDimension } from './constants' - -class WebGPUSampledTexture extends WebGPUBinding { - constructor(name, texture) { - super(name) - - this.texture = texture - - this.dimension = GPUTextureViewDimension.TwoD - - this.type = GPUBindingType.SampledTexture - this.visibility = GPUShaderStage.FRAGMENT - - this.textureGPU = null // set by the renderer - - Object.defineProperty(this, 'isSampledTexture', { value: true }) - } -} - -class WebGPUSampledArrayTexture extends WebGPUSampledTexture { - constructor(name) { - super(name) - - this.dimension = GPUTextureViewDimension.TwoDArray - - Object.defineProperty(this, 'isSampledArrayTexture', { value: true }) - } -} - -class WebGPUSampled3DTexture extends WebGPUSampledTexture { - constructor(name) { - super(name) - - this.dimension = GPUTextureViewDimension.ThreeD - - Object.defineProperty(this, 'isSampled3DTexture', { value: true }) - } -} - -class WebGPUSampledCubeTexture extends WebGPUSampledTexture { - constructor(name) { - super(name) - - this.dimension = GPUTextureViewDimension.Cube - - Object.defineProperty(this, 'isSampledCubeTexture', { value: true }) - } -} - -export { WebGPUSampledTexture, WebGPUSampledArrayTexture, WebGPUSampled3DTexture, WebGPUSampledCubeTexture } diff --git a/src/renderers/webgpu/WebGPUSampler.js b/src/renderers/webgpu/WebGPUSampler.js deleted file mode 100644 index 91afc4fa..00000000 --- a/src/renderers/webgpu/WebGPUSampler.js +++ /dev/null @@ -1,19 +0,0 @@ -import WebGPUBinding from './WebGPUBinding' -import { GPUBindingType } from './constants' - -class WebGPUSampler extends WebGPUBinding { - constructor(name, texture) { - super(name) - - this.texture = texture - - this.type = GPUBindingType.Sampler - this.visibility = GPUShaderStage.FRAGMENT - - this.samplerGPU = null // set by the renderer - - Object.defineProperty(this, 'isSampler', { value: true }) - } -} - -export default WebGPUSampler diff --git a/src/renderers/webgpu/WebGPUStorageBuffer.js b/src/renderers/webgpu/WebGPUStorageBuffer.js deleted file mode 100644 index dc21e0ed..00000000 --- a/src/renderers/webgpu/WebGPUStorageBuffer.js +++ /dev/null @@ -1,19 +0,0 @@ -import WebGPUBinding from './WebGPUBinding' -import { GPUBindingType } from './constants' - -class WebGPUStorageBuffer extends WebGPUBinding { - constructor(name, attribute) { - super(name) - - this.type = GPUBindingType.StorageBuffer - - this.usage = GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST - - this.attribute = attribute - this.bufferGPU = null // set by the renderer - - Object.defineProperty(this, 'isStorageBuffer', { value: true }) - } -} - -export default WebGPUStorageBuffer diff --git a/src/renderers/webgpu/WebGPUTextureRenderer.js b/src/renderers/webgpu/WebGPUTextureRenderer.js deleted file mode 100644 index e05a25f6..00000000 --- a/src/renderers/webgpu/WebGPUTextureRenderer.js +++ /dev/null @@ -1,30 +0,0 @@ -import { WebGLRenderTarget } from 'three' - -class WebGPUTextureRenderer { - constructor(renderer, options = {}) { - this.renderer = renderer - - // @TODO: Consider to introduce WebGPURenderTarget or rename WebGLRenderTarget to just RenderTarget - - this.renderTarget = new WebGLRenderTarget(options) - } - - getTexture() { - return this.renderTarget.texture - } - - setSize(width, height) { - this.renderTarget.setSize(width, height) - } - - render(scene, camera) { - const renderer = this.renderer - const renderTarget = this.renderTarget - - renderer.setRenderTarget(renderTarget) - renderer.render(scene, camera) - renderer.setRenderTarget(null) - } -} - -export default WebGPUTextureRenderer diff --git a/src/renderers/webgpu/WebGPUTextureUtils.js b/src/renderers/webgpu/WebGPUTextureUtils.js deleted file mode 100644 index 1f379538..00000000 --- a/src/renderers/webgpu/WebGPUTextureUtils.js +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2020 Brandon Jones -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: - -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. - -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { GPUIndexFormat, GPUFilterMode, GPUPrimitiveTopology } from './constants' - -// ported from https://github.com/toji/web-texture-tool/blob/master/src/webgpu-mipmap-generator.js - -class WebGPUTextureUtils { - constructor(device, glslang) { - this.device = device - - const mipmapVertexSource = `#version 450 - const vec2 pos[4] = vec2[4](vec2(-1.0f, 1.0f), vec2(1.0f, 1.0f), vec2(-1.0f, -1.0f), vec2(1.0f, -1.0f)); - const vec2 tex[4] = vec2[4](vec2(0.0f, 0.0f), vec2(1.0f, 0.0f), vec2(0.0f, 1.0f), vec2(1.0f, 1.0f)); - layout(location = 0) out vec2 vTex; - void main() { - vTex = tex[gl_VertexIndex]; - gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0); - } - ` - - const mipmapFragmentSource = `#version 450 - layout(set = 0, binding = 0) uniform sampler imgSampler; - layout(set = 0, binding = 1) uniform texture2D img; - layout(location = 0) in vec2 vTex; - layout(location = 0) out vec4 outColor; - void main() { - outColor = texture(sampler2D(img, imgSampler), vTex); - }` - - this.sampler = device.createSampler({ minFilter: GPUFilterMode.Linear }) - - // We'll need a new pipeline for every texture format used. - this.pipelines = {} - - this.mipmapVertexShaderModule = device.createShaderModule({ - code: glslang.compileGLSL(mipmapVertexSource, 'vertex'), - }) - this.mipmapFragmentShaderModule = device.createShaderModule({ - code: glslang.compileGLSL(mipmapFragmentSource, 'fragment'), - }) - } - - getMipmapPipeline(format) { - let pipeline = this.pipelines[format] - - if (pipeline === undefined) { - pipeline = this.device.createRenderPipeline({ - vertexStage: { - module: this.mipmapVertexShaderModule, - entryPoint: 'main', - }, - fragmentStage: { - module: this.mipmapFragmentShaderModule, - entryPoint: 'main', - }, - primitiveTopology: GPUPrimitiveTopology.TriangleStrip, - vertexState: { - indexFormat: GPUIndexFormat.Uint32, - }, - colorStates: [{ format }], - }) - this.pipelines[format] = pipeline - } - - return pipeline - } - - generateMipmaps(textureGPU, textureGPUDescriptor) { - const pipeline = this.getMipmapPipeline(textureGPUDescriptor.format) - - const commandEncoder = this.device.createCommandEncoder({}) - const bindGroupLayout = pipeline.getBindGroupLayout(0) // @TODO: Consider making this static. - - let srcView = textureGPU.createView({ - baseMipLevel: 0, - mipLevelCount: 1, - }) - - for (let i = 1; i < textureGPUDescriptor.mipLevelCount; i++) { - const dstView = textureGPU.createView({ - baseMipLevel: i, - mipLevelCount: 1, - }) - - const passEncoder = commandEncoder.beginRenderPass({ - colorAttachments: [ - { - attachment: dstView, - loadValue: [0, 0, 0, 0], - }, - ], - }) - - const bindGroup = this.device.createBindGroup({ - layout: bindGroupLayout, - entries: [ - { - binding: 0, - resource: this.sampler, - }, - { - binding: 1, - resource: srcView, - }, - ], - }) - - passEncoder.setPipeline(pipeline) - passEncoder.setBindGroup(0, bindGroup) - passEncoder.draw(4, 1, 0, 0) - passEncoder.endPass() - - srcView = dstView - } - - this.device.queue.submit([commandEncoder.finish()]) - } -} - -export default WebGPUTextureUtils diff --git a/src/renderers/webgpu/WebGPUTextures.js b/src/renderers/webgpu/WebGPUTextures.js deleted file mode 100644 index 2f9b4b3b..00000000 --- a/src/renderers/webgpu/WebGPUTextures.js +++ /dev/null @@ -1,664 +0,0 @@ -import { GPUTextureFormat, GPUAddressMode, GPUFilterMode, GPUTextureDimension } from './constants' -import { - CubeTexture, - Texture, - NearestFilter, - NearestMipmapNearestFilter, - NearestMipmapLinearFilter, - LinearFilter, - RepeatWrapping, - MirroredRepeatWrapping, - RGBAFormat, - RedFormat, - RGFormat, - RGBA_S3TC_DXT1_Format, - RGBA_S3TC_DXT3_Format, - RGBA_S3TC_DXT5_Format, - UnsignedByteType, - FloatType, - HalfFloatType, -} from 'three' -import WebGPUTextureUtils from './WebGPUTextureUtils' - -class WebGPUTextures { - constructor(device, properties, info, glslang) { - this.device = device - this.properties = properties - this.info = info - this.glslang = glslang - - this.defaultTexture = null - this.defaultCubeTexture = null - this.defaultSampler = null - - this.samplerCache = new Map() - this.utils = null - } - - getDefaultSampler() { - if (this.defaultSampler === null) { - this.defaultSampler = this.device.createSampler({}) - } - - return this.defaultSampler - } - - getDefaultTexture() { - if (this.defaultTexture === null) { - const texture = new Texture() - texture.minFilter = NearestFilter - texture.magFilter = NearestFilter - - this.defaultTexture = this._createTexture(texture) - } - - return this.defaultTexture - } - - getDefaultCubeTexture() { - if (this.defaultCubeTexture === null) { - const texture = new CubeTexture() - texture.minFilter = NearestFilter - texture.magFilter = NearestFilter - - this.defaultCubeTexture = this._createTexture(texture) - } - - return this.defaultCubeTexture - } - - getTextureGPU(texture) { - const textureProperties = this.properties.get(texture) - - return textureProperties.textureGPU - } - - getSampler(texture) { - const textureProperties = this.properties.get(texture) - - return textureProperties.samplerGPU - } - - updateTexture(texture) { - let forceUpdate = false - - const textureProperties = this.properties.get(texture) - - if (texture.version > 0 && textureProperties.version !== texture.version) { - const image = texture.image - - if (image === undefined) { - console.warn('THREE.WebGPURenderer: Texture marked for update but image is undefined.') - } else if (image.complete === false) { - console.warn('THREE.WebGPURenderer: Texture marked for update but image is incomplete.') - } else { - // texture init - - if (textureProperties.initialized === undefined) { - textureProperties.initialized = true - - const disposeCallback = onTextureDispose.bind(this) - textureProperties.disposeCallback = disposeCallback - - texture.addEventListener('dispose', disposeCallback) - - this.info.memory.textures++ - } - - // texture creation - - if (textureProperties.textureGPU !== undefined) { - // @TODO: Avoid calling of destroy() in certain scenarios. When only the contents of a texture - // are updated, a buffer upload should be sufficient. However, if the user changes - // the dimensions of the texture, format or usage, a new instance of GPUTexture is required. - - textureProperties.textureGPU.destroy() - } - - textureProperties.textureGPU = this._createTexture(texture) - textureProperties.version = texture.version - forceUpdate = true - } - } - - // if the texture is used for RTT, it's necessary to init it once so the binding - // group's resource definition points to the respective GPUTexture - - if (textureProperties.initializedRTT === false) { - textureProperties.initializedRTT = true - forceUpdate = true - } - - return forceUpdate - } - - updateSampler(texture) { - const array = [] - - array.push(texture.wrapS) - array.push(texture.wrapT) - array.push(texture.wrapR) - array.push(texture.magFilter) - array.push(texture.minFilter) - array.push(texture.anisotropy) - - const key = array.join() - let samplerGPU = this.samplerCache.get(key) - - if (samplerGPU === undefined) { - samplerGPU = this.device.createSampler({ - addressModeU: this._convertAddressMode(texture.wrapS), - addressModeV: this._convertAddressMode(texture.wrapT), - addressModeW: this._convertAddressMode(texture.wrapR), - magFilter: this._convertFilterMode(texture.magFilter), - minFilter: this._convertFilterMode(texture.minFilter), - mipmapFilter: this._convertFilterMode(texture.minFilter), - maxAnisotropy: texture.anisotropy, - }) - - this.samplerCache.set(key, samplerGPU) - } - - const textureProperties = this.properties.get(texture) - textureProperties.samplerGPU = samplerGPU - } - - initRenderTarget(renderTarget) { - const properties = this.properties - const renderTargetProperties = properties.get(renderTarget) - - if (renderTargetProperties.initialized === undefined) { - const device = this.device - - const width = renderTarget.width - const height = renderTarget.height - const colorTextureFormat = this._getFormat(renderTarget.texture) - - const colorTextureGPU = device.createTexture({ - size: { - width: width, - height: height, - depthOrArrayLayers: 1, - }, - format: colorTextureFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.SAMPLED, - }) - - this.info.memory.textures++ - - renderTargetProperties.colorTextureGPU = colorTextureGPU - renderTargetProperties.colorTextureFormat = colorTextureFormat - - // When the ".texture" or ".depthTexture" property of a render target is used as a map, - // the renderer has to find the respective GPUTexture objects to setup the bind groups. - // Since it's not possible to see just from a texture object whether it belongs to a render - // target or not, we need the initializedRTT flag. - - const textureProperties = properties.get(renderTarget.texture) - textureProperties.textureGPU = colorTextureGPU - textureProperties.initializedRTT = false - - if (renderTarget.depthBuffer === true) { - const depthTextureFormat = GPUTextureFormat.Depth24PlusStencil8 // @TODO: Make configurable - - const depthTextureGPU = device.createTexture({ - size: { - width: width, - height: height, - depthOrArrayLayers: 1, - }, - format: depthTextureFormat, - usage: GPUTextureUsage.RENDER_ATTACHMENT, - }) - - this.info.memory.textures++ - - renderTargetProperties.depthTextureGPU = depthTextureGPU - renderTargetProperties.depthTextureFormat = depthTextureFormat - - if (renderTarget.depthTexture !== null) { - const depthTextureProperties = properties.get(renderTarget.depthTexture) - depthTextureProperties.textureGPU = depthTextureGPU - depthTextureProperties.initializedRTT = false - } - } - - // - - const disposeCallback = onRenderTargetDispose.bind(this) - renderTargetProperties.disposeCallback = disposeCallback - - renderTarget.addEventListener('dispose', disposeCallback) - - // - - renderTargetProperties.initialized = true - } - } - - dispose() { - this.samplerCache.clear() - } - - _convertAddressMode(value) { - let addressMode = GPUAddressMode.ClampToEdge - - if (value === RepeatWrapping) { - addressMode = GPUAddressMode.Repeat - } else if (value === MirroredRepeatWrapping) { - addressMode = GPUAddressMode.MirrorRepeat - } - - return addressMode - } - - _convertFilterMode(value) { - let filterMode = GPUFilterMode.Linear - - if (value === NearestFilter || value === NearestMipmapNearestFilter || value === NearestMipmapLinearFilter) { - filterMode = GPUFilterMode.Nearest - } - - return filterMode - } - - _createTexture(texture) { - const device = this.device - const image = texture.image - - const { width, height, depth } = this._getSize(texture) - const needsMipmaps = this._needsMipmaps(texture) - const dimension = this._getDimension(texture) - const mipLevelCount = this._getMipLevelCount(texture, width, height, needsMipmaps) - const format = this._getFormat(texture) - - let usage = GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST - - if (needsMipmaps === true) { - // current mipmap generation requires RENDER_ATTACHMENT - - usage |= GPUTextureUsage.RENDER_ATTACHMENT - } - - // texture creation - - const textureGPUDescriptor = { - size: { - width: width, - height: height, - depthOrArrayLayers: depth, - }, - mipLevelCount: mipLevelCount, - sampleCount: 1, - dimension: dimension, - format: format, - usage: usage, - } - const textureGPU = device.createTexture(textureGPUDescriptor) - - // transfer texture data - - if (texture.isDataTexture || texture.isDataTexture2DArray || texture.isDataTexture3D) { - this._copyBufferToTexture(image, format, textureGPU) - - if (needsMipmaps === true) this._generateMipmaps(textureGPU, textureGPUDescriptor) - } else if (texture.isCompressedTexture) { - this._copyCompressedBufferToTexture(texture.mipmaps, format, textureGPU) - } else if (texture.isCubeTexture) { - this._copyCubeMapToTexture(image, texture, textureGPU) - } else { - if (image !== undefined) { - // assume HTMLImageElement, HTMLCanvasElement or ImageBitmap - - this._getImageBitmap(image, texture).then((imageBitmap) => { - this._copyImageBitmapToTexture(imageBitmap, textureGPU) - - if (needsMipmaps === true) this._generateMipmaps(textureGPU, textureGPUDescriptor) - }) - } - } - - return textureGPU - } - - _copyBufferToTexture(image, format, textureGPU) { - // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() - // @TODO: Consider to support valid buffer layouts with other formats like RGB - - const data = image.data - - const bytesPerTexel = this._getBytesPerTexel(format) - const bytesPerRow = Math.ceil((image.width * bytesPerTexel) / 256) * 256 - - this.device.queue.writeTexture( - { - texture: textureGPU, - mipLevel: 0, - }, - data, - { - offset: 0, - bytesPerRow, - }, - { - width: image.width, - height: image.height, - depthOrArrayLayers: image.depth !== undefined ? image.depth : 1, - }, - ) - } - - _copyCubeMapToTexture(images, texture, textureGPU) { - for (let i = 0; i < images.length; i++) { - const image = images[i] - - this._getImageBitmap(image, texture).then((imageBitmap) => { - this._copyImageBitmapToTexture(imageBitmap, textureGPU, { - x: 0, - y: 0, - z: i, - }) - }) - } - } - - _copyImageBitmapToTexture(image, textureGPU, origin = { x: 0, y: 0, z: 0 }) { - this.device.queue.copyImageBitmapToTexture( - { - imageBitmap: image, - }, - { - texture: textureGPU, - mipLevel: 0, - origin: origin, - }, - { - width: image.width, - height: image.height, - depthOrArrayLayers: 1, - }, - ) - } - - _copyCompressedBufferToTexture(mipmaps, format, textureGPU) { - // @TODO: Consider to use GPUCommandEncoder.copyBufferToTexture() - - const blockData = this._getBlockData(format) - - for (let i = 0; i < mipmaps.length; i++) { - const mipmap = mipmaps[i] - - const width = mipmap.width - const height = mipmap.height - - const bytesPerRow = Math.ceil(width / blockData.width) * blockData.byteLength - - this.device.queue.writeTexture( - { - texture: textureGPU, - mipLevel: i, - }, - mipmap.data, - { - offset: 0, - bytesPerRow, - }, - { - width: Math.ceil(width / blockData.width) * blockData.width, - height: Math.ceil(height / blockData.width) * blockData.width, - depthOrArrayLayers: 1, - }, - ) - } - } - - _generateMipmaps(textureGPU, textureGPUDescriptor) { - if (this.utils === null) { - this.utils = new WebGPUTextureUtils(this.device, this.glslang) // only create this helper if necessary - } - - this.utils.generateMipmaps(textureGPU, textureGPUDescriptor) - } - - _getBlockData(format) { - // this method is only relevant for compressed texture formats - - if (format === GPUTextureFormat.BC1RGBAUnorm || format === GPUTextureFormat.BC1RGBAUnormSRGB) { - return { byteLength: 8, width: 4, height: 4 } - } // DXT1 - if (format === GPUTextureFormat.BC2RGBAUnorm || format === GPUTextureFormat.BC2RGBAUnormSRGB) { - return { byteLength: 16, width: 4, height: 4 } - } // DXT3 - if (format === GPUTextureFormat.BC3RGBAUnorm || format === GPUTextureFormat.BC3RGBAUnormSRGB) { - return { byteLength: 16, width: 4, height: 4 } - } // DXT5 - if (format === GPUTextureFormat.BC4RUnorm || format === GPUTextureFormat.BC4RSNorm) { - return { byteLength: 8, width: 4, height: 4 } - } // RGTC1 - if (format === GPUTextureFormat.BC5RGUnorm || format === GPUTextureFormat.BC5RGSnorm) { - return { byteLength: 16, width: 4, height: 4 } - } // RGTC2 - if (format === GPUTextureFormat.BC6HRGBUFloat || format === GPUTextureFormat.BC6HRGBFloat) { - return { byteLength: 16, width: 4, height: 4 } - } // BPTC (float) - if (format === GPUTextureFormat.BC7RGBAUnorm || format === GPUTextureFormat.BC7RGBAUnormSRGB) { - return { byteLength: 16, width: 4, height: 4 } - } // BPTC (unorm) - } - - _getBytesPerTexel(format) { - if (format === GPUTextureFormat.R8Unorm) return 1 - if (format === GPUTextureFormat.R16Float) return 2 - if (format === GPUTextureFormat.RG8Unorm) return 2 - if (format === GPUTextureFormat.RG16Float) return 4 - if (format === GPUTextureFormat.R32Float) return 4 - if (format === GPUTextureFormat.RGBA8Unorm || format === GPUTextureFormat.RGBA8UnormSRGB) return 4 - if (format === GPUTextureFormat.RG32Float) return 8 - if (format === GPUTextureFormat.RGBA16Float) return 8 - if (format === GPUTextureFormat.RGBA32Float) return 16 - } - - _getDimension(texture) { - let dimension - - if (texture.isDataTexture3D) { - dimension = GPUTextureDimension.ThreeD - } else { - dimension = GPUTextureDimension.TwoD - } - - return dimension - } - - _getFormat(texture) { - const format = texture.format - const type = texture.type - const isSRGB = 'colorSpace' in texture ? texture.colorSpace === 'srgb' : texture.encoding === 3001 - - let formatGPU - - switch (format) { - case RGBA_S3TC_DXT1_Format: - formatGPU = isSRGB ? GPUTextureFormat.BC1RGBAUnormSRGB : GPUTextureFormat.BC1RGBAUnorm - break - - case RGBA_S3TC_DXT3_Format: - formatGPU = isSRGB ? GPUTextureFormat.BC2RGBAUnormSRGB : GPUTextureFormat.BC2RGBAUnorm - break - - case RGBA_S3TC_DXT5_Format: - formatGPU = isSRGB ? GPUTextureFormat.BC3RGBAUnormSRGB : GPUTextureFormat.BC3RGBAUnorm - break - - case RGBAFormat: - switch (type) { - case UnsignedByteType: - formatGPU = isSRGB ? GPUTextureFormat.RGBA8UnormSRGB : GPUTextureFormat.RGBA8Unorm - break - - case HalfFloatType: - formatGPU = GPUTextureFormat.RGBA16Float - break - - case FloatType: - formatGPU = GPUTextureFormat.RGBA32Float - break - - default: - console.error('WebGPURenderer: Unsupported texture type with RGBAFormat.', type) - } - - break - - case RedFormat: - switch (type) { - case UnsignedByteType: - formatGPU = GPUTextureFormat.R8Unorm - break - - case HalfFloatType: - formatGPU = GPUTextureFormat.R16Float - break - - case FloatType: - formatGPU = GPUTextureFormat.R32Float - break - - default: - console.error('WebGPURenderer: Unsupported texture type with RedFormat.', type) - } - - break - - case RGFormat: - switch (type) { - case UnsignedByteType: - formatGPU = GPUTextureFormat.RG8Unorm - break - - case HalfFloatType: - formatGPU = GPUTextureFormat.RG16Float - break - - case FloatType: - formatGPU = GPUTextureFormat.RG32Float - break - - default: - console.error('WebGPURenderer: Unsupported texture type with RGFormat.', type) - } - - break - - default: - console.error('WebGPURenderer: Unsupported texture format.', format) - } - - return formatGPU - } - - _getImageBitmap(image, texture) { - const width = image.width - const height = image.height - - if ( - (typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement) || - (typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement) - ) { - const options = {} - - options.imageOrientation = texture.flipY === true ? 'flipY' : 'none' - options.premultiplyAlpha = texture.premultiplyAlpha === true ? 'premultiply' : 'default' - - return createImageBitmap(image, 0, 0, width, height, options) - } else { - // assume ImageBitmap - - return Promise.resolve(image) - } - } - - _getMipLevelCount(texture, width, height, needsMipmaps) { - let mipLevelCount - - if (texture.isCompressedTexture) { - mipLevelCount = texture.mipmaps.length - } else if (needsMipmaps === true) { - mipLevelCount = Math.floor(Math.log2(Math.max(width, height))) + 1 - } else { - mipLevelCount = 1 // a texture without mipmaps has a base mip (mipLevel 0) - } - - return mipLevelCount - } - - _getSize(texture) { - const image = texture.image - - let width, height, depth - - if (texture.isCubeTexture) { - width = image.length > 0 ? image[0].width : 1 - height = image.length > 0 ? image[0].height : 1 - depth = 6 // one image for each side of the cube map - } else if (image !== undefined) { - width = image.width - height = image.height - depth = image.depth !== undefined ? image.depth : 1 - } else { - width = height = depth = 1 - } - - return { width, height, depth } - } - - _needsMipmaps(texture) { - return ( - texture.isCompressedTexture !== true && - texture.generateMipmaps === true && - texture.minFilter !== NearestFilter && - texture.minFilter !== LinearFilter - ) - } -} - -function onRenderTargetDispose(event) { - const renderTarget = event.target - const properties = this.properties - - const renderTargetProperties = properties.get(renderTarget) - - renderTarget.removeEventListener('dispose', renderTargetProperties.disposeCallback) - - renderTargetProperties.colorTextureGPU.destroy() - properties.remove(renderTarget.texture) - - this.info.memory.textures-- - - if (renderTarget.depthBuffer === true) { - renderTargetProperties.depthTextureGPU.destroy() - - this.info.memory.textures-- - - if (renderTarget.depthTexture !== null) { - properties.remove(renderTarget.depthTexture) - } - } - - properties.remove(renderTarget) -} - -function onTextureDispose(event) { - const texture = event.target - - const textureProperties = this.properties.get(texture) - textureProperties.textureGPU.destroy() - - texture.removeEventListener('dispose', textureProperties.disposeCallback) - - this.properties.remove(texture) - - this.info.memory.textures-- -} - -export default WebGPUTextures diff --git a/src/renderers/webgpu/WebGPUUniform.js b/src/renderers/webgpu/WebGPUUniform.js deleted file mode 100644 index 7854880d..00000000 --- a/src/renderers/webgpu/WebGPUUniform.js +++ /dev/null @@ -1,100 +0,0 @@ -import { Color, Matrix3, Matrix4, Vector2, Vector3, Vector4 } from 'three' - -class WebGPUUniform { - constructor(name, value = null) { - this.name = name - this.value = value - - this.boundary = 0 // used to build the uniform buffer according to the STD140 layout - this.itemSize = 0 - - this.offset = 0 // this property is set by WebGPUUniformsGroup and marks the start position in the uniform buffer - } - - setValue(value) { - this.value = value - } - - getValue() { - return this.value - } -} - -class FloatUniform extends WebGPUUniform { - constructor(name, value = 0) { - super(name, value) - - this.boundary = 4 - this.itemSize = 1 - - Object.defineProperty(this, 'isFloatUniform', { value: true }) - } -} - -class Vector2Uniform extends WebGPUUniform { - constructor(name, value = new Vector2()) { - super(name, value) - - this.boundary = 8 - this.itemSize = 2 - - Object.defineProperty(this, 'isVector2Uniform', { value: true }) - } -} - -class Vector3Uniform extends WebGPUUniform { - constructor(name, value = new Vector3()) { - super(name, value) - - this.boundary = 16 - this.itemSize = 3 - - Object.defineProperty(this, 'isVector3Uniform', { value: true }) - } -} - -class Vector4Uniform extends WebGPUUniform { - constructor(name, value = new Vector4()) { - super(name, value) - - this.boundary = 16 - this.itemSize = 4 - - Object.defineProperty(this, 'isVector4Uniform', { value: true }) - } -} - -class ColorUniform extends WebGPUUniform { - constructor(name, value = new Color()) { - super(name, value) - - this.boundary = 16 - this.itemSize = 3 - - Object.defineProperty(this, 'isColorUniform', { value: true }) - } -} - -class Matrix3Uniform extends WebGPUUniform { - constructor(name, value = new Matrix3()) { - super(name, value) - - this.boundary = 48 - this.itemSize = 12 - - Object.defineProperty(this, 'isMatrix3Uniform', { value: true }) - } -} - -class Matrix4Uniform extends WebGPUUniform { - constructor(name, value = new Matrix4()) { - super(name, value) - - this.boundary = 64 - this.itemSize = 16 - - Object.defineProperty(this, 'isMatrix4Uniform', { value: true }) - } -} - -export { FloatUniform, Vector2Uniform, Vector3Uniform, Vector4Uniform, ColorUniform, Matrix3Uniform, Matrix4Uniform } diff --git a/src/renderers/webgpu/WebGPUUniformsGroup.js b/src/renderers/webgpu/WebGPUUniformsGroup.js deleted file mode 100644 index 4bc7a93c..00000000 --- a/src/renderers/webgpu/WebGPUUniformsGroup.js +++ /dev/null @@ -1,245 +0,0 @@ -import WebGPUBinding from './WebGPUBinding' -import { GPUBindingType } from './constants' - -class WebGPUUniformsGroup extends WebGPUBinding { - constructor(name) { - super(name) - - // the order of uniforms in this array must match the order of uniforms in the shader - - this.uniforms = [] - - this.onBeforeUpdate = function () {} - - this.bytesPerElement = Float32Array.BYTES_PER_ELEMENT - this.type = GPUBindingType.UniformBuffer - this.visibility = GPUShaderStage.VERTEX - - this.usage = GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST - - this.array = null // set by the renderer - this.bufferGPU = null // set by the renderer - - Object.defineProperty(this, 'isUniformsGroup', { value: true }) - } - - addUniform(uniform) { - this.uniforms.push(uniform) - - return this - } - - removeUniform(uniform) { - const index = this.uniforms.indexOf(uniform) - - if (index !== -1) { - this.uniforms.splice(index, 1) - } - - return this - } - - setOnBeforeUpdate(callback) { - this.onBeforeUpdate = callback - - return this - } - - getByteLength() { - let offset = 0 // global buffer offset in bytes - const chunkSize = 16 // size of a chunk in bytes (STD140 layout) - - for (let i = 0, l = this.uniforms.length; i < l; i++) { - const uniform = this.uniforms[i] - - // offset within a single chunk in bytes - - const chunkOffset = offset % chunkSize - const remainingSizeInChunk = chunkSize - chunkOffset - - // check for chunk overflow - - if (chunkOffset !== 0 && remainingSizeInChunk - uniform.boundary < 0) { - // add padding and adjust offset - - offset += chunkSize - chunkOffset - } - - uniform.offset = offset / this.bytesPerElement - - offset += uniform.itemSize * this.bytesPerElement - } - - return offset - } - - update() { - let updated = false - - for (let uniform of this.uniforms) { - if (this.updateByType(uniform) === true) { - updated = true - } - } - - return updated - } - - updateByType(uniform) { - if (uniform.isFloatUniform) return this.updateNumber(uniform) - if (uniform.isVector2Uniform) return this.updateVector2(uniform) - if (uniform.isVector3Uniform) return this.updateVector3(uniform) - if (uniform.isVector4Uniform) return this.updateVector4(uniform) - if (uniform.isColorUniform) return this.updateColor(uniform) - if (uniform.isMatrix3Uniform) return this.updateMatrix3(uniform) - if (uniform.isMatrix4Uniform) return this.updateMatrix4(uniform) - - console.error('THREE.WebGPUUniformsGroup: Unsupported uniform type.', uniform) - } - - updateNumber(uniform) { - let updated = false - - const a = this.array - const v = uniform.getValue() - const offset = uniform.offset - - if (a[offset] !== v) { - a[offset] = v - updated = true - } - - return updated - } - - updateVector2(uniform) { - let updated = false - - const a = this.array - const v = uniform.getValue() - const offset = uniform.offset - - if (a[offset + 0] !== v.x || a[offset + 1] !== v.y) { - a[offset + 0] = v.x - a[offset + 1] = v.y - - updated = true - } - - return updated - } - - updateVector3(uniform) { - let updated = false - - const a = this.array - const v = uniform.getValue() - const offset = uniform.offset - - if (a[offset + 0] !== v.x || a[offset + 1] !== v.y || a[offset + 2] !== v.z) { - a[offset + 0] = v.x - a[offset + 1] = v.y - a[offset + 2] = v.z - - updated = true - } - - return updated - } - - updateVector4(uniform) { - let updated = false - - const a = this.array - const v = uniform.getValue() - const offset = uniform.offset - - if (a[offset + 0] !== v.x || a[offset + 1] !== v.y || a[offset + 2] !== v.z || a[offset + 4] !== v.w) { - a[offset + 0] = v.x - a[offset + 1] = v.y - a[offset + 2] = v.z - a[offset + 3] = v.w - - updated = true - } - - return updated - } - - updateColor(uniform) { - let updated = false - - const a = this.array - const c = uniform.getValue() - const offset = uniform.offset - - if (a[offset + 0] !== c.r || a[offset + 1] !== c.g || a[offset + 2] !== c.b) { - a[offset + 0] = c.r - a[offset + 1] = c.g - a[offset + 2] = c.b - - updated = true - } - - return updated - } - - updateMatrix3(uniform) { - let updated = false - - const a = this.array - const e = uniform.getValue().elements - const offset = uniform.offset - - if ( - a[offset + 0] !== e[0] || - a[offset + 1] !== e[1] || - a[offset + 2] !== e[2] || - a[offset + 4] !== e[3] || - a[offset + 5] !== e[4] || - a[offset + 6] !== e[5] || - a[offset + 8] !== e[6] || - a[offset + 9] !== e[7] || - a[offset + 10] !== e[8] - ) { - a[offset + 0] = e[0] - a[offset + 1] = e[1] - a[offset + 2] = e[2] - a[offset + 4] = e[3] - a[offset + 5] = e[4] - a[offset + 6] = e[5] - a[offset + 8] = e[6] - a[offset + 9] = e[7] - a[offset + 10] = e[8] - - updated = true - } - - return updated - } - - updateMatrix4(uniform) { - let updated = false - - const a = this.array - const e = uniform.getValue().elements - const offset = uniform.offset - - if (arraysEqual(a, e, offset) === false) { - a.set(e, offset) - updated = true - } - - return updated - } -} - -function arraysEqual(a, b, offset) { - for (let i = 0, l = b.length; i < l; i++) { - if (a[offset + i] !== b[i]) return false - } - - return true -} - -export default WebGPUUniformsGroup diff --git a/src/renderers/webgpu/constants.js b/src/renderers/webgpu/constants.js deleted file mode 100644 index af75043c..00000000 --- a/src/renderers/webgpu/constants.js +++ /dev/null @@ -1,256 +0,0 @@ -export const GPUPrimitiveTopology = { - PointList: 'point-list', - LineList: 'line-list', - LineStrip: 'line-strip', - TriangleList: 'triangle-list', - TriangleStrip: 'triangle-strip', -} - -export const GPUCompareFunction = { - Never: 'never', - Less: 'less', - Equal: 'equal', - LessEqual: 'less-equal', - Greater: 'greater', - NotEqual: 'not-equal', - GreaterEqual: 'greater-equal', - Always: 'always', -} - -export const GPUStoreOp = { - Store: 'store', - Clear: 'clear', -} - -export const GPULoadOp = { - Load: 'load', -} - -export const GPUFrontFace = { - CCW: 'ccw', - CW: 'cw', -} - -export const GPUCullMode = { - None: 'none', - Front: 'front', - Back: 'back', -} - -export const GPUIndexFormat = { - Uint16: 'uint16', - Uint32: 'uint32', -} - -export const GPUVertexFormat = { - Uchar2: 'uchar2', - Uchar4: 'uchar4', - Char2: 'char2', - Char4: 'char4', - Uchar2Norm: 'uchar2norm', - Uchar4Norm: 'uchar4norm', - Char2Norm: 'char2norm', - Char4Norm: 'char4norm', - Ushort2: 'ushort2', - Ushort4: 'ushort4', - Short2: 'short2', - Short4: 'short4', - Ushort2Norm: 'ushort2norm', - Ushort4Norm: 'ushort4norm', - Short2Norm: 'short2norm', - Short4Norm: 'short4norm', - Half2: 'half2', - Half4: 'half4', - Float: 'float', - Float2: 'float2', - Float3: 'float3', - Float4: 'float4', - Uint: 'uint', - Uint2: 'uint2', - Uint3: 'uint3', - Uint4: 'uint4', - Int: 'int', - Int2: 'int2', - Int3: 'int3', - Int4: 'int4', -} - -export const GPUTextureFormat = { - // 8-bit formats - - R8Unorm: 'r8unorm', - R8Snorm: 'r8snorm', - R8Uint: 'r8uint', - R8Sint: 'r8sint', - - // 16-bit formats - - R16Uint: 'r16uint', - R16Sint: 'r16sint', - R16Float: 'r16float', - RG8Unorm: 'rg8unorm', - RG8Snorm: 'rg8snorm', - RG8Uint: 'rg8uint', - RG8Sint: 'rg8sint', - - // 32-bit formats - - R32Uint: 'r32uint', - R32Sint: 'r32sint', - R32Float: 'r32float', - RG16Uint: 'rg16uint', - RG16Sint: 'rg16sint', - RG16Float: 'rg16float', - RGBA8Unorm: 'rgba8unorm', - RGBA8UnormSRGB: 'rgba8unorm-srgb', - RGBA8Snorm: 'rgba8snorm', - RGBA8Uint: 'rgba8uint', - RGBA8Sint: 'rgba8sint', - BRGA8Unorm: 'bgra8unorm', - BRGA8UnormSRGB: 'bgra8unorm-srgb', - // Packed 32-bit formats - RGB9E5UFloat: 'rgb9e5ufloat', - RGB10A2Unorm: 'rgb10a2unorm', - RG11B10uFloat: 'rgb10a2unorm', - - // 64-bit formats - - RG32Uint: 'rg32uint', - RG32Sint: 'rg32sint', - RG32Float: 'rg32float', - RGBA16Uint: 'rgba16uint', - RGBA16Sint: 'rgba16sint', - RGBA16Float: 'rgba16float', - - // 128-bit formats - - RGBA32Uint: 'rgba32uint', - RGBA32Sint: 'rgba32sint', - RGBA32Float: 'rgba32float', - - // Depth and stencil formats - - Stencil8: 'stencil8', - Depth16Unorm: 'depth16unorm', - Depth24Plus: 'depth24plus', - Depth24PlusStencil8: 'depth24plus-stencil8', - Depth32Float: 'depth32float', - - // BC compressed formats usable if 'texture-compression-bc' is both - // supported by the device/user agent and enabled in requestDevice. - - BC1RGBAUnorm: 'bc1-rgba-unorm', - BC1RGBAUnormSRGB: 'bc1-rgba-unorm-srgb', - BC2RGBAUnorm: 'bc2-rgba-unorm', - BC2RGBAUnormSRGB: 'bc2-rgba-unorm-srgb', - BC3RGBAUnorm: 'bc3-rgba-unorm', - BC3RGBAUnormSRGB: 'bc3-rgba-unorm-srgb', - BC4RUnorm: 'bc4-r-unorm', - BC4RSNorm: 'bc4-r-snorm', - BC5RGUnorm: 'bc5-rg-unorm', - BC5RGSnorm: 'bc5-rg-snorm', - BC6HRGBUFloat: 'bc6h-rgb-ufloat', - BC6HRGBFloat: 'bc6h-rgb-float', - BC7RGBAUnorm: 'bc7-rgba-unorm', - BC7RGBAUnormSRGB: 'bc7-rgba-srgb', - - // 'depth24unorm-stencil8' extension - - Depth24UnormStencil8: 'depth24unorm-stencil8', - - // 'depth32float-stencil8' extension - - Depth32FloatStencil8: 'depth32float-stencil8', -} - -export const GPUAddressMode = { - ClampToEdge: 'clamp-to-edge', - Repeat: 'repeat', - MirrorRepeat: 'mirror-repeat', -} - -export const GPUFilterMode = { - Linear: 'linear', - Nearest: 'nearest', -} - -export const GPUBlendFactor = { - Zero: 'zero', - One: 'one', - SrcColor: 'src-color', - OneMinusSrcColor: 'one-minus-src-color', - SrcAlpha: 'src-alpha', - OneMinusSrcAlpha: 'one-minus-src-alpha', - DstColor: 'dst-color', - OneMinusDstColor: 'one-minus-dst-color', - DstAlpha: 'dst-alpha', - OneMinusDstAlpha: 'one-minus-dst-alpha', - SrcAlphaSaturated: 'src-alpha-saturated', - BlendColor: 'blend-color', - OneMinusBlendColor: 'one-minus-blend-color', -} - -export const GPUBlendOperation = { - Add: 'add', - Subtract: 'subtract', - ReverseSubtract: 'reverse-subtract', - Min: 'min', - Max: 'max', -} - -export const GPUColorWriteFlags = { - None: 0, - Red: 0x1, - Green: 0x2, - Blue: 0x4, - Alpha: 0x8, - All: 0xf, -} - -export const GPUStencilOperation = { - Keep: 'keep', - Zero: 'zero', - Replace: 'replace', - Invert: 'invert', - IncrementClamp: 'increment-clamp', - DecrementClamp: 'decrement-clamp', - IncrementWrap: 'increment-wrap', - DecrementWrap: 'decrement-wrap', -} - -export const GPUBindingType = { - UniformBuffer: 'uniform-buffer', - StorageBuffer: 'storage-buffer', - ReadonlyStorageBuffer: 'readonly-storage-buffer', - Sampler: 'sampler', - ComparisonSampler: 'comparison-sampler', - SampledTexture: 'sampled-texture', - MultisampledTexture: 'multisampled-texture', - ReadonlyStorageTexture: 'readonly-storage-texture', - WriteonlyStorageTexture: 'writeonly-storage-texture', -} - -export const GPUTextureDimension = { - OneD: '1d', - TwoD: '2d', - ThreeD: '3d', -} - -export const GPUTextureViewDimension = { - OneD: '1d', - TwoD: '2d', - TwoDArray: '2d-array', - Cube: 'cube', - CubeArray: 'cube-array', - ThreeD: '3d', -} - -export const GPUInputStepMode = { - Vertex: 'vertex', - Instance: 'instance', -} - -// @TODO: Move to src/constants.js - -export const BlendColorFactor = 211 -export const OneMinusBlendColorFactor = 212 diff --git a/src/renderers/webgpu/nodes/ShaderLib.js b/src/renderers/webgpu/nodes/ShaderLib.js deleted file mode 100644 index ee2ebce7..00000000 --- a/src/renderers/webgpu/nodes/ShaderLib.js +++ /dev/null @@ -1,41 +0,0 @@ -const ShaderLib = { - common: { - vertexShader: `#version 450 - -NODE_HEADER_ATTRIBUTES -NODE_HEADER_UNIFORMS -NODE_HEADER_VARYS - -void main(){ - NODE_BODY_VARYS - gl_Position = NODE_MVP; -}`, - fragmentShader: `#version 450 - -NODE_HEADER_ATTRIBUTES -NODE_HEADER_UNIFORMS -NODE_HEADER_VARYS - -layout(location = 0) out vec4 outColor; - -void main() { - - outColor = vec4( 1.0, 1.0, 1.0, 1.0 ); - - #ifdef NODE_COLOR - - outColor = NODE_COLOR; - - #endif - - #ifdef NODE_OPACITY - - outColor.a *= NODE_OPACITY; - - #endif - -}`, - }, -} - -export default ShaderLib diff --git a/src/renderers/webgpu/nodes/WebGPUNodeBuilder.js b/src/renderers/webgpu/nodes/WebGPUNodeBuilder.js deleted file mode 100644 index 2a609e16..00000000 --- a/src/renderers/webgpu/nodes/WebGPUNodeBuilder.js +++ /dev/null @@ -1,242 +0,0 @@ -import WebGPUNodeUniformsGroup from './WebGPUNodeUniformsGroup' -import { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} from './WebGPUNodeUniform' -import WebGPUSampler from '../WebGPUSampler' -import { WebGPUSampledTexture } from '../WebGPUSampledTexture' - -import NodeSlot from '../../nodes/core/NodeSlot' -import NodeBuilder from '../../nodes/core/NodeBuilder' -import ModelViewProjectionNode from '../../nodes/accessors/ModelViewProjectionNode' - -import ShaderLib from './ShaderLib' - -class WebGPUNodeBuilder extends NodeBuilder { - constructor(material, renderer) { - super(material, renderer) - - this.bindings = { vertex: [], fragment: [] } - this.bindingsOffset = { vertex: 0, fragment: 0 } - - this.uniformsGroup = {} - - this.nativeShader = null - - this._parseMaterial() - } - - _parseMaterial() { - const material = this.material - - // get shader - - this.nativeShader = ShaderLib.common - - // parse inputs - - if (material.isMeshBasicMaterial || material.isPointsMaterial || material.isLineBasicMaterial) { - const mvpNode = new ModelViewProjectionNode() - - if (material.positionNode !== undefined) { - mvpNode.position = material.positionNode - } - - this.addSlot('vertex', new NodeSlot(mvpNode, 'MVP', 'vec4')) - - if (material.colorNode !== undefined) { - this.addSlot('fragment', new NodeSlot(material.colorNode, 'COLOR', 'vec4')) - } - - if (material.opacityNode !== undefined) { - this.addSlot('fragment', new NodeSlot(material.opacityNode, 'OPACITY', 'float')) - } - } - } - - getTexture(textureProperty, uvSnippet) { - return `texture( sampler2D( ${textureProperty}, ${textureProperty}_sampler ), ${uvSnippet} )` - } - - getPropertyName(node) { - if (node.isNodeUniform) { - const name = node.name - const type = node.type - - if (type === 'texture') { - return name - } else { - return `nodeUniforms.${name}` - } - } - - return super.getPropertyName(node) - } - - getBindings() { - const bindings = this.bindings - - return [...bindings.vertex, ...bindings.fragment] - } - - getUniformFromNode(node, shaderStage, type) { - const uniformNode = super.getUniformFromNode(node, shaderStage, type) - const nodeData = this.getDataFromNode(node, shaderStage) - - if (nodeData.uniformGPU === undefined) { - let uniformGPU - - const bindings = this.bindings[shaderStage] - - if (type === 'texture') { - const sampler = new WebGPUSampler(`${uniformNode.name}_sampler`, uniformNode.value) - const texture = new WebGPUSampledTexture(uniformNode.name, uniformNode.value) - - // add first textures in sequence and group for last - const lastBinding = bindings[bindings.length - 1] - const index = lastBinding && lastBinding.isUniformsGroup ? bindings.length - 1 : bindings.length - - bindings.splice(index, 0, sampler, texture) - - uniformGPU = { sampler, texture } - } else { - let uniformsGroup = this.uniformsGroup[shaderStage] - - if (uniformsGroup === undefined) { - uniformsGroup = new WebGPUNodeUniformsGroup(shaderStage) - - this.uniformsGroup[shaderStage] = uniformsGroup - - bindings.push(uniformsGroup) - } - - if (type === 'float') { - uniformGPU = new FloatNodeUniform(uniformNode) - } else if (type === 'vec2') { - uniformGPU = new Vector2NodeUniform(uniformNode) - } else if (type === 'vec3') { - uniformGPU = new Vector3NodeUniform(uniformNode) - } else if (type === 'vec4') { - uniformGPU = new Vector4NodeUniform(uniformNode) - } else if (type === 'color') { - uniformGPU = new ColorNodeUniform(uniformNode) - } else if (type === 'mat3') { - uniformGPU = new Matrix3NodeUniform(uniformNode) - } else if (type === 'mat4') { - uniformGPU = new Matrix4NodeUniform(uniformNode) - } else { - throw new Error(`Uniform "${type}" not declared.`) - } - - uniformsGroup.addUniform(uniformGPU) - } - - nodeData.uniformGPU = uniformGPU - - if (shaderStage === 'vertex') { - this.bindingsOffset['fragment'] = bindings.length - } - } - - return uniformNode - } - - getAttributesHeaderSnippet(shaderStage) { - let snippet = '' - - if (shaderStage === 'vertex') { - const attributes = this.attributes - - for (let index = 0; index < attributes.length; index++) { - const attribute = attributes[index] - - snippet += `layout(location = ${index}) in ${attribute.type} ${attribute.name};` - } - } - - return snippet - } - - getVarysHeaderSnippet(shaderStage) { - let snippet = '' - - const varys = this.varys - - const ioStage = shaderStage === 'vertex' ? 'out' : 'in' - - for (let index = 0; index < varys.length; index++) { - const vary = varys[index] - - snippet += `layout(location = ${index}) ${ioStage} ${vary.type} ${vary.name};` - } - - return snippet - } - - getVarysBodySnippet(shaderStage) { - let snippet = '' - - if (shaderStage === 'vertex') { - for (let vary of this.varys) { - snippet += `${vary.name} = ${vary.snippet};` - } - } - - return snippet - } - - getUniformsHeaderSnippet(shaderStage) { - const uniforms = this.uniforms[shaderStage] - - let snippet = '' - let groupSnippet = '' - - let index = this.bindingsOffset[shaderStage] - - for (let uniform of uniforms) { - if (uniform.type === 'texture') { - snippet += `layout(set = 0, binding = ${index++}) uniform sampler ${uniform.name}_sampler;` - snippet += `layout(set = 0, binding = ${index++}) uniform texture2D ${uniform.name};` - } else { - const vectorType = this.getVectorType(uniform.type) - - groupSnippet += `uniform ${vectorType} ${uniform.name};` - } - } - - if (groupSnippet) { - snippet += `layout(set = 0, binding = ${index++}) uniform NodeUniforms { ${groupSnippet} } nodeUniforms;` - } - - return snippet - } - - composeShaderCode(code, snippet) { - // use regex maybe for security? - const versionStrIndex = code.indexOf('\n') - - let finalCode = code.substr(0, versionStrIndex) + '\n\n' - - finalCode += snippet - - finalCode += code.substr(versionStrIndex) - - return finalCode - } - - build() { - super.build() - - this.vertexShader = this.composeShaderCode(this.nativeShader.vertexShader, this.vertexShader) - this.fragmentShader = this.composeShaderCode(this.nativeShader.fragmentShader, this.fragmentShader) - - return this - } -} - -export default WebGPUNodeBuilder diff --git a/src/renderers/webgpu/nodes/WebGPUNodeUniform.js b/src/renderers/webgpu/nodes/WebGPUNodeUniform.js deleted file mode 100644 index ba64d794..00000000 --- a/src/renderers/webgpu/nodes/WebGPUNodeUniform.js +++ /dev/null @@ -1,103 +0,0 @@ -import { - FloatUniform, - Vector2Uniform, - Vector3Uniform, - Vector4Uniform, - ColorUniform, - Matrix3Uniform, - Matrix4Uniform, -} from '../WebGPUUniform' - -class FloatNodeUniform extends FloatUniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value) - - this.nodeUniform = nodeUniform - } - - getValue() { - return this.nodeUniform.value - } -} - -class Vector2NodeUniform extends Vector2Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value) - - this.nodeUniform = nodeUniform - } - - getValue() { - return this.nodeUniform.value - } -} - -class Vector3NodeUniform extends Vector3Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value) - - this.nodeUniform = nodeUniform - } - - getValue() { - return this.nodeUniform.value - } -} - -class Vector4NodeUniform extends Vector4Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value) - - this.nodeUniform = nodeUniform - } - - getValue() { - return this.nodeUniform.value - } -} - -class ColorNodeUniform extends ColorUniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value) - - this.nodeUniform = nodeUniform - } - - getValue() { - return this.nodeUniform.value - } -} - -class Matrix3NodeUniform extends Matrix3Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value) - - this.nodeUniform = nodeUniform - } - - getValue() { - return this.nodeUniform.value - } -} - -class Matrix4NodeUniform extends Matrix4Uniform { - constructor(nodeUniform) { - super(nodeUniform.name, nodeUniform.value) - - this.nodeUniform = nodeUniform - } - - getValue() { - return this.nodeUniform.value - } -} - -export { - FloatNodeUniform, - Vector2NodeUniform, - Vector3NodeUniform, - Vector4NodeUniform, - ColorNodeUniform, - Matrix3NodeUniform, - Matrix4NodeUniform, -} diff --git a/src/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js b/src/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js deleted file mode 100644 index cac0202f..00000000 --- a/src/renderers/webgpu/nodes/WebGPUNodeUniformsGroup.js +++ /dev/null @@ -1,16 +0,0 @@ -import WebGPUUniformsGroup from '../WebGPUUniformsGroup' - -class WebGPUNodeUniformsGroup extends WebGPUUniformsGroup { - constructor(shaderStage) { - super('nodeUniforms') - - let shaderStageVisibility - - if (shaderStage === 'vertex') shaderStageVisibility = GPUShaderStage.VERTEX - else if (shaderStage === 'fragment') shaderStageVisibility = GPUShaderStage.FRAGMENT - - this.setVisibility(shaderStageVisibility) - } -} - -export default WebGPUNodeUniformsGroup diff --git a/src/renderers/webgpu/nodes/WebGPUNodes.js b/src/renderers/webgpu/nodes/WebGPUNodes.js deleted file mode 100644 index 8aeed4f5..00000000 --- a/src/renderers/webgpu/nodes/WebGPUNodes.js +++ /dev/null @@ -1,53 +0,0 @@ -import WebGPUNodeBuilder from './WebGPUNodeBuilder' -import NodeFrame from '../../nodes/core/NodeFrame' - -class WebGPUNodes { - constructor(renderer) { - this.renderer = renderer - - this.nodeFrame = new NodeFrame() - - this.builders = new WeakMap() - } - - get(material) { - let nodeBuilder = this.builders.get(material) - - if (nodeBuilder === undefined) { - nodeBuilder = new WebGPUNodeBuilder(material, this.renderer).build() - - this.builders.set(material, nodeBuilder) - } - - return nodeBuilder - } - - remove(material) { - this.builders.delete(material) - } - - updateFrame() { - this.nodeFrame.update() - } - - update(object, camera) { - const material = object.material - - const nodeBuilder = this.get(material) - const nodeFrame = this.nodeFrame - - nodeFrame.material = material - nodeFrame.camera = camera - nodeFrame.object = object - - for (let node of nodeBuilder.updateNodes) { - nodeFrame.updateNode(node) - } - } - - dispose() { - this.builders = new WeakMap() - } -} - -export default WebGPUNodes diff --git a/src/shaders/BokehShader2.ts b/src/shaders/BokehShader2.ts index 1f0f4bbe..6868400f 100644 --- a/src/shaders/BokehShader2.ts +++ b/src/shaders/BokehShader2.ts @@ -1,6 +1,6 @@ import { IUniform, Texture, Vector2 } from 'three' -export interface BokehShaderUniforms { +export interface BokehShader2Uniforms { textureWidth: IUniform textureHeight: IUniform @@ -43,7 +43,7 @@ export interface BokehShaderUniforms { */ export const BokehShader2: { - uniforms: BokehShaderUniforms + uniforms: BokehShader2Uniforms vertexShader: string fragmentShader: string } = { @@ -78,7 +78,7 @@ export const BokehShader2: { pentagon: { value: 0 }, shaderFocus: { value: 1 }, - focusCoords: { value: new Vector2() }, + focusCoords: { value: /* @__PURE__ */ new Vector2() }, }, vertexShader: [ diff --git a/src/shaders/ColorCorrectionShader.ts b/src/shaders/ColorCorrectionShader.ts index 2e63a4b3..f52afb49 100644 --- a/src/shaders/ColorCorrectionShader.ts +++ b/src/shaders/ColorCorrectionShader.ts @@ -7,9 +7,9 @@ import { Vector3 } from 'three' export const ColorCorrectionShader = { uniforms: { tDiffuse: { value: null }, - powRGB: { value: new Vector3(2, 2, 2) }, - mulRGB: { value: new Vector3(1, 1, 1) }, - addRGB: { value: new Vector3(0, 0, 0) }, + powRGB: { value: /* @__PURE__ */ new Vector3(2, 2, 2) }, + mulRGB: { value: /* @__PURE__ */ new Vector3(1, 1, 1) }, + addRGB: { value: /* @__PURE__ */ new Vector3(0, 0, 0) }, }, vertexShader: [ diff --git a/src/shaders/ColorifyShader.ts b/src/shaders/ColorifyShader.ts index 68f74c5f..e1ffa16a 100644 --- a/src/shaders/ColorifyShader.ts +++ b/src/shaders/ColorifyShader.ts @@ -7,7 +7,7 @@ import { Color } from 'three' export const ColorifyShader = { uniforms: { tDiffuse: { value: null }, - color: { value: new Color(0xffffff) }, + color: { value: /* @__PURE__ */ new Color(0xffffff) }, }, vertexShader: [ diff --git a/src/shaders/ConvolutionShader.ts b/src/shaders/ConvolutionShader.ts index 3d1c5126..8574f1b2 100644 --- a/src/shaders/ConvolutionShader.ts +++ b/src/shaders/ConvolutionShader.ts @@ -32,7 +32,7 @@ export const ConvolutionShader: IConvolutionShader = { uniforms: { tDiffuse: { value: null }, - uImageIncrement: { value: new Vector2(0.001953125, 0.0) }, + uImageIncrement: { value: /* @__PURE__ */ new Vector2(0.001953125, 0.0) }, cKernel: { value: [] }, }, diff --git a/src/shaders/DepthLimitedBlurShader.ts b/src/shaders/DepthLimitedBlurShader.ts index 2e962893..9ba6f8fa 100644 --- a/src/shaders/DepthLimitedBlurShader.ts +++ b/src/shaders/DepthLimitedBlurShader.ts @@ -33,8 +33,8 @@ export const DepthLimitedBlurShader: IDepthLimitedBlurShader = { }, uniforms: { tDiffuse: { value: null }, - size: { value: new Vector2(512, 512) }, - sampleUvOffsets: { value: [new Vector2(0, 0)] }, + size: { value: /* @__PURE__ */ new Vector2(512, 512) }, + sampleUvOffsets: { value: [/* @__PURE__ */ new Vector2(0, 0)] }, sampleWeights: { value: [1.0] }, tDepth: { value: null }, cameraNear: { value: 10 }, diff --git a/src/shaders/DotScreenShader.ts b/src/shaders/DotScreenShader.ts index aa01e7b1..5f5c9ba7 100644 --- a/src/shaders/DotScreenShader.ts +++ b/src/shaders/DotScreenShader.ts @@ -9,8 +9,8 @@ import { Vector2 } from 'three' export const DotScreenShader = { uniforms: { tDiffuse: { value: null }, - tSize: { value: new Vector2(256, 256) }, - center: { value: new Vector2(0.5, 0.5) }, + tSize: { value: /* @__PURE__ */ new Vector2(256, 256) }, + center: { value: /* @__PURE__ */ new Vector2(0.5, 0.5) }, angle: { value: 1.57 }, scale: { value: 1.0 }, }, diff --git a/src/shaders/FXAAShader.ts b/src/shaders/FXAAShader.ts index 795b5677..ec0e60b9 100644 --- a/src/shaders/FXAAShader.ts +++ b/src/shaders/FXAAShader.ts @@ -10,7 +10,7 @@ import { Vector2 } from 'three' export const FXAAShader = { uniforms: { tDiffuse: { value: null }, - resolution: { value: new Vector2(1 / 1024, 1 / 512) }, + resolution: { value: /* @__PURE__ */ new Vector2(1 / 1024, 1 / 512) }, }, vertexShader: [ diff --git a/src/shaders/FreiChenShader.ts b/src/shaders/FreiChenShader.ts index 4a0e2ea7..632915d6 100644 --- a/src/shaders/FreiChenShader.ts +++ b/src/shaders/FreiChenShader.ts @@ -10,7 +10,7 @@ import { Vector2 } from 'three' export const FreiChenShader = { uniforms: { tDiffuse: { value: null }, - aspect: { value: new Vector2(512, 512) }, + aspect: { value: /* @__PURE__ */ new Vector2(512, 512) }, }, vertexShader: [ diff --git a/src/shaders/GodRaysShader.ts b/src/shaders/GodRaysShader.ts index 6cfa2629..48baa1e5 100644 --- a/src/shaders/GodRaysShader.ts +++ b/src/shaders/GodRaysShader.ts @@ -73,7 +73,7 @@ export const GodRaysGenerateShader = { value: 1.0, }, vSunPositionScreenSpace: { - value: new Vector3(), + value: /* @__PURE__ */ new Vector3(), }, }, @@ -235,7 +235,7 @@ export const GodRaysCombineShader = { export const GodRaysFakeSunShader = { uniforms: { vSunPositionScreenSpace: { - value: new Vector3(), + value: /* @__PURE__ */ new Vector3(), }, fAspect: { @@ -243,11 +243,11 @@ export const GodRaysFakeSunShader = { }, sunColor: { - value: new Color(0xffee00), + value: /* @__PURE__ */ new Color(0xffee00), }, bgColor: { - value: new Color(0x000000), + value: /* @__PURE__ */ new Color(0x000000), }, }, diff --git a/src/shaders/LuminosityHighPassShader.ts b/src/shaders/LuminosityHighPassShader.ts index 88709b49..2bbd713c 100644 --- a/src/shaders/LuminosityHighPassShader.ts +++ b/src/shaders/LuminosityHighPassShader.ts @@ -12,7 +12,7 @@ export const LuminosityHighPassShader = { tDiffuse: { value: null }, luminosityThreshold: { value: 1.0 }, smoothWidth: { value: 1.0 }, - defaultColor: { value: new Color(0x000000) }, + defaultColor: { value: /* @__PURE__ */ new Color(0x000000) }, defaultOpacity: { value: 0.0 }, }, diff --git a/src/shaders/NormalMapShader.ts b/src/shaders/NormalMapShader.ts index 56258ad7..92963499 100644 --- a/src/shaders/NormalMapShader.ts +++ b/src/shaders/NormalMapShader.ts @@ -8,8 +8,8 @@ import { Vector2 } from 'three' export const NormalMapShader = { uniforms: { heightMap: { value: null }, - resolution: { value: new Vector2(512, 512) }, - scale: { value: new Vector2(1, 1) }, + resolution: { value: /* @__PURE__ */ new Vector2(512, 512) }, + scale: { value: /* @__PURE__ */ new Vector2(1, 1) }, height: { value: 0.05 }, }, diff --git a/src/shaders/SAOShader.ts b/src/shaders/SAOShader.ts index 69b1546c..8add68db 100644 --- a/src/shaders/SAOShader.ts +++ b/src/shaders/SAOShader.ts @@ -43,12 +43,12 @@ export const SAOShader: ISAOShader = { tDepth: { value: null }, tDiffuse: { value: null }, tNormal: { value: null }, - size: { value: new Vector2(512, 512) }, + size: { value: /* @__PURE__ */ new Vector2(512, 512) }, cameraNear: { value: 1 }, cameraFar: { value: 100 }, - cameraProjectionMatrix: { value: new Matrix4() }, - cameraInverseProjectionMatrix: { value: new Matrix4() }, + cameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + cameraInverseProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, scale: { value: 1.0 }, intensity: { value: 0.1 }, diff --git a/src/shaders/SMAAShader.ts b/src/shaders/SMAAShader.ts index 95813a82..23484a7a 100644 --- a/src/shaders/SMAAShader.ts +++ b/src/shaders/SMAAShader.ts @@ -13,7 +13,7 @@ export const SMAAEdgesShader = { uniforms: { tDiffuse: { value: null }, - resolution: { value: new Vector2(1 / 1024, 1 / 512) }, + resolution: { value: /* @__PURE__ */ new Vector2(1 / 1024, 1 / 512) }, }, vertexShader: [ @@ -117,7 +117,7 @@ export const SMAAWeightsShader = { tDiffuse: { value: null }, tArea: { value: null }, tSearch: { value: null }, - resolution: { value: new Vector2(1 / 1024, 1 / 512) }, + resolution: { value: /* @__PURE__ */ new Vector2(1 / 1024, 1 / 512) }, }, vertexShader: [ @@ -357,7 +357,7 @@ export const SMAABlendShader = { uniforms: { tDiffuse: { value: null }, tColor: { value: null }, - resolution: { value: new Vector2(1 / 1024, 1 / 512) }, + resolution: { value: /* @__PURE__ */ new Vector2(1 / 1024, 1 / 512) }, }, vertexShader: [ diff --git a/src/shaders/SSAOShader.ts b/src/shaders/SSAOShader.ts index 98475a18..8a54d07c 100644 --- a/src/shaders/SSAOShader.ts +++ b/src/shaders/SSAOShader.ts @@ -21,9 +21,9 @@ export const SSAOShader = { kernel: { value: null }, cameraNear: { value: null }, cameraFar: { value: null }, - resolution: { value: new Vector2() }, - cameraProjectionMatrix: { value: new Matrix4() }, - cameraInverseProjectionMatrix: { value: new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + cameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + cameraInverseProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, kernelRadius: { value: 8 }, minDistance: { value: 0.005 }, maxDistance: { value: 0.05 }, @@ -227,7 +227,7 @@ export const SSAODepthShader = { export const SSAOBlurShader = { uniforms: { tDiffuse: { value: null }, - resolution: { value: new Vector2() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, }, vertexShader: [ diff --git a/src/shaders/SSRShader.ts b/src/shaders/SSRShader.ts index a212b977..fe073f1f 100644 --- a/src/shaders/SSRShader.ts +++ b/src/shaders/SSRShader.ts @@ -21,9 +21,9 @@ export const SSRShader = { tDepth: { value: null }, cameraNear: { value: null }, cameraFar: { value: null }, - resolution: { value: new Vector2() }, - cameraProjectionMatrix: { value: new Matrix4() }, - cameraInverseProjectionMatrix: { value: new Matrix4() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, + cameraProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, + cameraInverseProjectionMatrix: { value: /* @__PURE__ */ new Matrix4() }, opacity: { value: 0.5 }, maxDistance: { value: 180 }, cameraRange: { value: 0 }, @@ -278,7 +278,7 @@ export const SSRDepthShader = { export const SSRBlurShader = { uniforms: { tDiffuse: { value: null }, - resolution: { value: new Vector2() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, opacity: { value: 0.5 }, }, diff --git a/src/shaders/SobelOperatorShader.ts b/src/shaders/SobelOperatorShader.ts index 2401781f..c4bd0b9e 100644 --- a/src/shaders/SobelOperatorShader.ts +++ b/src/shaders/SobelOperatorShader.ts @@ -10,7 +10,7 @@ import { Vector2 } from 'three' export const SobelOperatorShader = { uniforms: { tDiffuse: { value: null }, - resolution: { value: new Vector2() }, + resolution: { value: /* @__PURE__ */ new Vector2() }, }, vertexShader: [ diff --git a/src/shaders/SubsurfaceScatteringShader.ts b/src/shaders/SubsurfaceScatteringShader.ts index b55504ca..1f0324f8 100644 --- a/src/shaders/SubsurfaceScatteringShader.ts +++ b/src/shaders/SubsurfaceScatteringShader.ts @@ -8,68 +8,95 @@ import { Color, ShaderChunk, ShaderLib, UniformsUtils } from 'three' *------------------------------------------------------------------------------------------ */ -function replaceAll(string: string, find: string, replace: string): string { - return string.split(find).join(replace) -} +let _SubsurfaceScatteringShader: any -const meshphong_frag_head = ShaderChunk['meshphong_frag'].slice( - 0, - ShaderChunk['meshphong_frag'].indexOf('void main() {'), -) -const meshphong_frag_body = ShaderChunk['meshphong_frag'].slice(ShaderChunk['meshphong_frag'].indexOf('void main() {')) +function get() { + if (_SubsurfaceScatteringShader) return _SubsurfaceScatteringShader -export const SubsurfaceScatteringShader = { - uniforms: UniformsUtils.merge([ - ShaderLib['phong'].uniforms, - { - thicknessMap: { value: null }, - thicknessColor: { value: new Color(0xffffff) }, - thicknessDistortion: { value: 0.1 }, - thicknessAmbient: { value: 0.0 }, - thicknessAttenuation: { value: 0.1 }, - thicknessPower: { value: 2.0 }, - thicknessScale: { value: 10.0 }, - }, - ]), + const meshphong_frag_head = ShaderChunk['meshphong_frag'].slice( + 0, + ShaderChunk['meshphong_frag'].indexOf('void main() {'), + ) + const meshphong_frag_body = ShaderChunk['meshphong_frag'].slice( + ShaderChunk['meshphong_frag'].indexOf('void main() {'), + ) - vertexShader: ['#define USE_UV', ShaderChunk['meshphong_vert']].join('\n'), + _SubsurfaceScatteringShader = { + uniforms: /* @__PURE__ */ UniformsUtils.merge([ + ShaderLib['phong'].uniforms, + { + thicknessMap: { value: null }, + thicknessColor: { value: /* @__PURE__ */ new Color(0xffffff) }, + thicknessDistortion: { value: 0.1 }, + thicknessAmbient: { value: 0.0 }, + thicknessAttenuation: { value: 0.1 }, + thicknessPower: { value: 2.0 }, + thicknessScale: { value: 10.0 }, + }, + ]), - fragmentShader: [ - '#define USE_UV', - '#define SUBSURFACE', + vertexShader: /* glsl */ ` + #define USE_UV + ${ShaderChunk['meshphong_vert']} + `, + fragmentShader: /* glsl */ ` + #define USE_UV', + #define SUBSURFACE', - meshphong_frag_head, + ${meshphong_frag_head} - 'uniform sampler2D thicknessMap;', - 'uniform float thicknessPower;', - 'uniform float thicknessScale;', - 'uniform float thicknessDistortion;', - 'uniform float thicknessAmbient;', - 'uniform float thicknessAttenuation;', - 'uniform vec3 thicknessColor;', + uniform sampler2D thicknessMap; + uniform float thicknessPower; + uniform float thicknessScale; + uniform float thicknessDistortion; + uniform float thicknessAmbient; + uniform float thicknessAttenuation; + uniform vec3 thicknessColor; - 'void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in GeometricContext geometry, inout ReflectedLight reflectedLight) {', - ' vec3 thickness = thicknessColor * texture2D(thicknessMap, uv).r;', - ' vec3 scatteringHalf = normalize(directLight.direction + (geometry.normal * thicknessDistortion));', - ' float scatteringDot = pow(saturate(dot(geometry.viewDir, -scatteringHalf)), thicknessPower) * thicknessScale;', - ' vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness;', - ' reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color;', - '}', + void RE_Direct_Scattering(const in IncidentLight directLight, const in vec2 uv, const in GeometricContext geometry, inout ReflectedLight reflectedLight) { + vec3 thickness = thicknessColor * texture2D(thicknessMap, uv).r; + vec3 scatteringHalf = normalize(directLight.direction + (geometry.normal * thicknessDistortion)); + float scatteringDot = pow(saturate(dot(geometry.viewDir, -scatteringHalf)), thicknessPower) * thicknessScale; + vec3 scatteringIllu = (scatteringDot + thicknessAmbient) * thickness; + reflectedLight.directDiffuse += scatteringIllu * thicknessAttenuation * directLight.color; + } - meshphong_frag_body.replace( + ${meshphong_frag_body.replace( '#include ', + ShaderChunk['lights_fragment_begin'].replace( + /RE_Direct\( directLight, geometry, material, reflectedLight \);/g, + /* glsl */ ` + RE_Direct( directLight, geometry, material, reflectedLight ); - replaceAll( - ShaderChunk['lights_fragment_begin'], - 'RE_Direct( directLight, geometry, material, reflectedLight );', - [ - 'RE_Direct( directLight, geometry, material, reflectedLight );', - - '#if defined( SUBSURFACE ) && defined( USE_UV )', - ' RE_Direct_Scattering(directLight, vUv, geometry, reflectedLight);', - '#endif', - ].join('\n'), + #if defined( SUBSURFACE ) && defined( USE_UV ) + RE_Direct_Scattering(directLight, vUv, geometry, reflectedLight); + #endif + `, ), - ), - ].join('\n'), + )} + `, + } + + return _SubsurfaceScatteringShader +} + +export const SubsurfaceScatteringShader = { + get uniforms() { + return get().uniforms + }, + set uniforms(value) { + get().uniforms = value + }, + get vertexShader() { + return get().vertexShader + }, + set vertexShader(value) { + get().vertexShader = value + }, + get fragmentShader() { + return get().vertexShader + }, + set fragmentShader(value) { + get().vertexShader = value + }, } diff --git a/src/shaders/ToonShader.ts b/src/shaders/ToonShader.ts index d541f81c..c0d8975d 100644 --- a/src/shaders/ToonShader.ts +++ b/src/shaders/ToonShader.ts @@ -11,12 +11,12 @@ import { Color, Vector3 } from 'three' export const ToonShader1 = { uniforms: { - uDirLightPos: { value: new Vector3() }, - uDirLightColor: { value: new Color(0xeeeeee) }, + uDirLightPos: { value: /* @__PURE__ */ new Vector3() }, + uDirLightColor: { value: /* @__PURE__ */ new Color(0xeeeeee) }, - uAmbientLightColor: { value: new Color(0x050505) }, + uAmbientLightColor: { value: /* @__PURE__ */ new Color(0x050505) }, - uBaseColor: { value: new Color(0xffffff) }, + uBaseColor: { value: /* @__PURE__ */ new Color(0xffffff) }, }, vertexShader: [ @@ -79,16 +79,16 @@ export const ToonShader1 = { export const ToonShader2 = { uniforms: { - uDirLightPos: { value: new Vector3() }, - uDirLightColor: { value: new Color(0xeeeeee) }, + uDirLightPos: { value: /* @__PURE__ */ new Vector3() }, + uDirLightColor: { value: /* @__PURE__ */ new Color(0xeeeeee) }, - uAmbientLightColor: { value: new Color(0x050505) }, + uAmbientLightColor: { value: /* @__PURE__ */ new Color(0x050505) }, - uBaseColor: { value: new Color(0xeeeeee) }, - uLineColor1: { value: new Color(0x808080) }, - uLineColor2: { value: new Color(0x000000) }, - uLineColor3: { value: new Color(0x000000) }, - uLineColor4: { value: new Color(0x000000) }, + uBaseColor: { value: /* @__PURE__ */ new Color(0xeeeeee) }, + uLineColor1: { value: /* @__PURE__ */ new Color(0x808080) }, + uLineColor2: { value: /* @__PURE__ */ new Color(0x000000) }, + uLineColor3: { value: /* @__PURE__ */ new Color(0x000000) }, + uLineColor4: { value: /* @__PURE__ */ new Color(0x000000) }, }, vertexShader: [ @@ -141,16 +141,16 @@ export const ToonShader2 = { export const ToonShaderHatching = { uniforms: { - uDirLightPos: { value: new Vector3() }, - uDirLightColor: { value: new Color(0xeeeeee) }, + uDirLightPos: { value: /* @__PURE__ */ new Vector3() }, + uDirLightColor: { value: /* @__PURE__ */ new Color(0xeeeeee) }, - uAmbientLightColor: { value: new Color(0x050505) }, + uAmbientLightColor: { value: /* @__PURE__ */ new Color(0x050505) }, - uBaseColor: { value: new Color(0xffffff) }, - uLineColor1: { value: new Color(0x000000) }, - uLineColor2: { value: new Color(0x000000) }, - uLineColor3: { value: new Color(0x000000) }, - uLineColor4: { value: new Color(0x000000) }, + uBaseColor: { value: /* @__PURE__ */ new Color(0xffffff) }, + uLineColor1: { value: /* @__PURE__ */ new Color(0x000000) }, + uLineColor2: { value: /* @__PURE__ */ new Color(0x000000) }, + uLineColor3: { value: /* @__PURE__ */ new Color(0x000000) }, + uLineColor4: { value: /* @__PURE__ */ new Color(0x000000) }, }, vertexShader: [ @@ -231,13 +231,13 @@ export const ToonShaderHatching = { export const ToonShaderDotted = { uniforms: { - uDirLightPos: { value: new Vector3() }, - uDirLightColor: { value: new Color(0xeeeeee) }, + uDirLightPos: { value: /* @__PURE__ */ new Vector3() }, + uDirLightColor: { value: /* @__PURE__ */ new Color(0xeeeeee) }, - uAmbientLightColor: { value: new Color(0x050505) }, + uAmbientLightColor: { value: /* @__PURE__ */ new Color(0x050505) }, - uBaseColor: { value: new Color(0xffffff) }, - uLineColor1: { value: new Color(0x000000) }, + uBaseColor: { value: /* @__PURE__ */ new Color(0xffffff) }, + uLineColor1: { value: /* @__PURE__ */ new Color(0x000000) }, }, vertexShader: [ diff --git a/src/shaders/TriangleBlurShader.ts b/src/shaders/TriangleBlurShader.ts index e24c0838..5f7864d0 100644 --- a/src/shaders/TriangleBlurShader.ts +++ b/src/shaders/TriangleBlurShader.ts @@ -13,7 +13,7 @@ import { Vector2 } from 'three' export const TriangleBlurShader = { uniforms: { texture: { value: null }, - delta: { value: new Vector2(1, 1) }, + delta: { value: /* @__PURE__ */ new Vector2(1, 1) }, }, vertexShader: [ diff --git a/src/shaders/VolumeShader.ts b/src/shaders/VolumeShader.ts index eb321679..770c4e78 100644 --- a/src/shaders/VolumeShader.ts +++ b/src/shaders/VolumeShader.ts @@ -8,10 +8,10 @@ import { Vector2, Vector3 } from 'three' export const VolumeRenderShader1 = { uniforms: { - u_size: { value: new Vector3(1, 1, 1) }, + u_size: { value: /* @__PURE__ */ new Vector3(1, 1, 1) }, u_renderstyle: { value: 0 }, u_renderthreshold: { value: 0.5 }, - u_clim: { value: new Vector2(1, 1) }, + u_clim: { value: /* @__PURE__ */ new Vector2(1, 1) }, u_data: { value: null }, u_cmdata: { value: null }, }, diff --git a/src/types/Loader.ts b/src/types/Loader.ts new file mode 100644 index 00000000..4bf1959e --- /dev/null +++ b/src/types/Loader.ts @@ -0,0 +1,9 @@ +import * as THREE from 'three' + +// https://github.com/pmndrs/three-stdlib/issues/237 +export class Loader extends THREE.Loader { + loadAsync(url: string, onProgress?: (event: ProgressEvent) => void): Promise { + // @ts-ignore + return super.loadAsync(url, onProgress) + } +} diff --git a/src/utils/BufferGeometryUtils.ts b/src/utils/BufferGeometryUtils.ts index 25f7b316..4da6917c 100644 --- a/src/utils/BufferGeometryUtils.ts +++ b/src/utils/BufferGeometryUtils.ts @@ -249,7 +249,7 @@ export const mergeBufferAttributes = (attributes: BufferAttribute[]): BufferAttr }) if (TypedArray && itemSize) { - // @ts-expect-error this works in JS and TS is complaining but it's such a tiny thing I can live with the guilt + // @ts-ignore this works in JS and TS is complaining but it's such a tiny thing I can live with the guilt const array = new TypedArray(arrayLength) let offset = 0 @@ -288,7 +288,7 @@ export const interleaveAttributes = (attributes: BufferAttribute[]): Interleaved } // Create the set of buffer attributes - // @ts-expect-error this works in JS and TS is complaining but it's such a tiny thing I can live with the guilt + // @ts-ignore this works in JS and TS is complaining but it's such a tiny thing I can live with the guilt const interleavedBuffer = new InterleavedBuffer(new TypedArray(arrayLength), stride) let offset = 0 const res = [] @@ -606,7 +606,7 @@ export function computeMorphedAttributes(object: Mesh | Line | Points): Computed const morphInfluences = object.morphTargetInfluences if ( - // @ts-expect-error + // @ts-ignore material.morphTargets && morphAttribute && morphInfluences @@ -642,11 +642,11 @@ export function computeMorphedAttributes(object: Mesh | Line | Points): Computed } if ((object as SkinnedMesh).isSkinnedMesh) { - // @ts-expect-error – https://github.com/three-types/three-ts-types/issues/37 + // @ts-ignore – https://github.com/three-types/three-ts-types/issues/37 object.boneTransform(a, _vA) - // @ts-expect-error – https://github.com/three-types/three-ts-types/issues/37 + // @ts-ignore – https://github.com/three-types/three-ts-types/issues/37 object.boneTransform(b, _vB) - // @ts-expect-error – https://github.com/three-types/three-ts-types/issues/37 + // @ts-ignore – https://github.com/three-types/three-ts-types/issues/37 object.boneTransform(c, _vC) } diff --git a/src/utils/RoughnessMipmapper.d.ts b/src/utils/RoughnessMipmapper.d.ts new file mode 100644 index 00000000..80c0f292 --- /dev/null +++ b/src/utils/RoughnessMipmapper.d.ts @@ -0,0 +1,8 @@ +import { WebGLRenderer } from 'three' + +export class RoughnessMipmapper { + constructor(renderer: WebGLRenderer) + + generateMipmaps(material: THREE.Material): void + dispose(): void +} diff --git a/src/utils/RoughnessMipmapper.js b/src/utils/RoughnessMipmapper.js index 0b6efec6..6b58a42b 100644 --- a/src/utils/RoughnessMipmapper.js +++ b/src/utils/RoughnessMipmapper.js @@ -17,26 +17,22 @@ import { WebGLRenderTarget, } from 'three' -var _mipmapMaterial = _getMipmapMaterial() +var _mipmapMaterial = /* @__PURE__ */ _getMipmapMaterial() -var _mesh = new Mesh(new PlaneGeometry(2, 2), _mipmapMaterial) +var _mesh = /* @__PURE__ */ new Mesh(/* @__PURE__ */ new PlaneGeometry(2, 2), _mipmapMaterial) -var _flatCamera = new OrthographicCamera(0, 1, 0, 1, 0, 1) +var _flatCamera = /* @__PURE__ */ new OrthographicCamera(0, 1, 0, 1, 0, 1) var _tempTarget = null -var _renderer = null +class RoughnessMipmapper { + constructor(renderer) { + this._renderer = renderer -function RoughnessMipmapper(renderer) { - _renderer = renderer + this._renderer.compile(_mesh, _flatCamera) + } - _renderer.compile(_mesh, _flatCamera) -} - -RoughnessMipmapper.prototype = { - constructor: RoughnessMipmapper, - - generateMipmaps: function (material) { + generateMipmaps = function (material) { if ('roughnessMap' in material === false) return var { roughnessMap, normalMap } = material @@ -58,11 +54,11 @@ RoughnessMipmapper.prototype = { if (!MathUtils.isPowerOfTwo(width) || !MathUtils.isPowerOfTwo(height)) return - var oldTarget = _renderer.getRenderTarget() + var oldTarget = this._renderer.getRenderTarget() - var autoClear = _renderer.autoClear + var autoClear = this._renderer.autoClear - _renderer.autoClear = false + this._renderer.autoClear = false if (_tempTarget === null || _tempTarget.width !== width || _tempTarget.height !== height) { if (_tempTarget !== null) _tempTarget.dispose() @@ -89,7 +85,7 @@ RoughnessMipmapper.prototype = { // Setting the render target causes the memory to be allocated. - _renderer.setRenderTarget(newRoughnessTarget) + this._renderer.setRenderTarget(newRoughnessTarget) material.roughnessMap = newRoughnessTarget.texture @@ -119,29 +115,29 @@ RoughnessMipmapper.prototype = { _tempTarget.scissor.set(position.x, position.y, width, height) - _renderer.setRenderTarget(_tempTarget) + this._renderer.setRenderTarget(_tempTarget) - _renderer.render(_mesh, _flatCamera) + this._renderer.render(_mesh, _flatCamera) - _renderer.copyFramebufferToTexture(position, material.roughnessMap, mip) + this._renderer.copyFramebufferToTexture(position, material.roughnessMap, mip) _mipmapMaterial.uniforms.roughnessMap.value = material.roughnessMap } if (roughnessMap !== material.roughnessMap) roughnessMap.dispose() - _renderer.setRenderTarget(oldTarget) + this._renderer.setRenderTarget(oldTarget) - _renderer.autoClear = autoClear - }, + this._renderer.autoClear = autoClear + } - dispose: function () { + dispose = function () { _mipmapMaterial.dispose() _mesh.geometry.dispose() if (_tempTarget != null) _tempTarget.dispose() - }, + } } function _getMipmapMaterial() { diff --git a/src/utils/ShadowMapViewer.js b/src/utils/ShadowMapViewer.js index e8fde5cd..4a6b0c49 100644 --- a/src/utils/ShadowMapViewer.js +++ b/src/utils/ShadowMapViewer.js @@ -21,12 +21,12 @@ import { UnpackDepthRGBAShader } from '../shaders/UnpackDepthRGBAShader' * 1) Import ShadowMapViewer into your app. * * 2) Create a shadow casting light and name it optionally: - * var light = new DirectionalLight( 0xffffff, 1 ); + * let light = new DirectionalLight( 0xffffff, 1 ); * light.castShadow = true; * light.name = 'Sun'; * * 3) Create a shadow map viewer for that light and set its size and position optionally: - * var shadowMapViewer = new ShadowMapViewer( light ); + * let shadowMapViewer = new ShadowMapViewer( light ); * shadowMapViewer.size.set( 128, 128 ); //width, height default: 256, 256 * shadowMapViewer.position.set( 10, 10 ); //x, y in pixel default: 0, 0 (top left corner) * @@ -39,159 +39,155 @@ import { UnpackDepthRGBAShader } from '../shaders/UnpackDepthRGBAShader' * 6) If you set the position or size members directly, you need to call shadowMapViewer.update(); */ -var ShadowMapViewer = function (light) { - //- Internals - var scope = this - var doRenderLabel = light.name !== undefined && light.name !== '' - var userAutoClearSetting - - //Holds the initial position and dimension of the HUD - var frame = { - x: 10, - y: 10, - width: 256, - height: 256, - } +class ShadowMapViewer { + constructor(light) { + //- Internals + const scope = this + const doRenderLabel = light.name !== undefined && light.name !== '' + let userAutoClearSetting + + //Holds the initial position and dimension of the HUD + const frame = { + x: 10, + y: 10, + width: 256, + height: 256, + } - var camera = new OrthographicCamera( - window.innerWidth / -2, - window.innerWidth / 2, - window.innerHeight / 2, - window.innerHeight / -2, - 1, - 10, - ) - camera.position.set(0, 0, 2) - var scene = new Scene() - - //HUD for shadow map - var shader = UnpackDepthRGBAShader - - var uniforms = UniformsUtils.clone(shader.uniforms) - var material = new ShaderMaterial({ - uniforms: uniforms, - vertexShader: shader.vertexShader, - fragmentShader: shader.fragmentShader, - }) - var plane = new PlaneGeometry(frame.width, frame.height) - var mesh = new Mesh(plane, material) - - scene.add(mesh) - - //Label for light's name - var labelCanvas, labelMesh - - if (doRenderLabel) { - labelCanvas = document.createElement('canvas') - - var context = labelCanvas.getContext('2d') - context.font = 'Bold 20px Arial' - - var labelWidth = context.measureText(light.name).width - labelCanvas.width = labelWidth - labelCanvas.height = 25 //25 to account for g, p, etc. - - context.font = 'Bold 20px Arial' - context.fillStyle = 'rgba( 255, 0, 0, 1 )' - context.fillText(light.name, 0, 20) - - var labelTexture = new Texture(labelCanvas) - labelTexture.magFilter = LinearFilter - labelTexture.minFilter = LinearFilter - labelTexture.needsUpdate = true - - var labelMaterial = new MeshBasicMaterial({ - map: labelTexture, - side: DoubleSide, + const camera = new OrthographicCamera( + window.innerWidth / -2, + window.innerWidth / 2, + window.innerHeight / 2, + window.innerHeight / -2, + 1, + 10, + ) + camera.position.set(0, 0, 2) + const scene = new Scene() + + //HUD for shadow map + const shader = UnpackDepthRGBAShader + + const uniforms = UniformsUtils.clone(shader.uniforms) + const material = new ShaderMaterial({ + uniforms: uniforms, + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, }) - labelMaterial.transparent = true + const plane = new PlaneGeometry(frame.width, frame.height) + const mesh = new Mesh(plane, material) - var labelPlane = new PlaneGeometry(labelCanvas.width, labelCanvas.height) - labelMesh = new Mesh(labelPlane, labelMaterial) + scene.add(mesh) - scene.add(labelMesh) - } + //Label for light's name + let labelCanvas, labelMesh - function resetPosition() { - scope.position.set(scope.position.x, scope.position.y) - } + if (doRenderLabel) { + labelCanvas = document.createElement('canvas') - //- API - // Set to false to disable displaying this shadow map - this.enabled = true + const context = labelCanvas.getContext('2d') + context.font = 'Bold 20px Arial' - // Set the size of the displayed shadow map on the HUD - this.size = { - width: frame.width, - height: frame.height, - set: function (width, height) { - this.width = width - this.height = height + const labelWidth = context.measureText(light.name).width + labelCanvas.width = labelWidth + labelCanvas.height = 25 //25 to account for g, p, etc. - mesh.scale.set(this.width / frame.width, this.height / frame.height, 1) + context.font = 'Bold 20px Arial' + context.fillStyle = 'rgba( 255, 0, 0, 1 )' + context.fillText(light.name, 0, 20) - //Reset the position as it is off when we scale stuff - resetPosition() - }, - } + const labelTexture = new Texture(labelCanvas) + labelTexture.magFilter = LinearFilter + labelTexture.minFilter = LinearFilter + labelTexture.needsUpdate = true - // Set the position of the displayed shadow map on the HUD - this.position = { - x: frame.x, - y: frame.y, - set: function (x, y) { - this.x = x - this.y = y + const labelMaterial = new MeshBasicMaterial({ map: labelTexture, side: DoubleSide }) + labelMaterial.transparent = true - var width = scope.size.width - var height = scope.size.height + const labelPlane = new PlaneGeometry(labelCanvas.width, labelCanvas.height) + labelMesh = new Mesh(labelPlane, labelMaterial) - mesh.position.set(-window.innerWidth / 2 + width / 2 + this.x, window.innerHeight / 2 - height / 2 - this.y, 0) + scene.add(labelMesh) + } - if (doRenderLabel) { - labelMesh.position.set(mesh.position.x, mesh.position.y - scope.size.height / 2 + labelCanvas.height / 2, 0) - } - }, - } + function resetPosition() { + scope.position.set(scope.position.x, scope.position.y) + } + + //- API + // Set to false to disable displaying this shadow map + this.enabled = true + + // Set the size of the displayed shadow map on the HUD + this.size = { + width: frame.width, + height: frame.height, + set: function (width, height) { + this.width = width + this.height = height - this.render = function (renderer) { - if (this.enabled) { - //Because a light's .shadowMap is only initialised after the first render pass - //we have to make sure the correct map is sent into the shader, otherwise we - //always end up with the scene's first added shadow casting light's shadowMap - //in the shader - //See: https://github.com/mrdoob/three.js/issues/5932 - uniforms.tDiffuse.value = light.shadow.map.texture - - userAutoClearSetting = renderer.autoClear - renderer.autoClear = false // To allow render overlay - renderer.clearDepth() - renderer.render(scene, camera) - renderer.autoClear = userAutoClearSetting //Restore user's setting + mesh.scale.set(this.width / frame.width, this.height / frame.height, 1) + + //Reset the position as it is off when we scale stuff + resetPosition() + }, } - } - this.updateForWindowResize = function () { - if (this.enabled) { - camera.left = window.innerWidth / -2 - camera.right = window.innerWidth / 2 - camera.top = window.innerHeight / 2 - camera.bottom = window.innerHeight / -2 - camera.updateProjectionMatrix() + // Set the position of the displayed shadow map on the HUD + this.position = { + x: frame.x, + y: frame.y, + set: function (x, y) { + this.x = x + this.y = y + + const width = scope.size.width + const height = scope.size.height - this.update() + mesh.position.set(-window.innerWidth / 2 + width / 2 + this.x, window.innerHeight / 2 - height / 2 - this.y, 0) + + if (doRenderLabel) + labelMesh.position.set(mesh.position.x, mesh.position.y - scope.size.height / 2 + labelCanvas.height / 2, 0) + }, } - } - this.update = function () { - this.position.set(this.position.x, this.position.y) - this.size.set(this.size.width, this.size.height) - } + this.render = function (renderer) { + if (this.enabled) { + //Because a light's .shadowMap is only initialised after the first render pass + //we have to make sure the correct map is sent into the shader, otherwise we + //always end up with the scene's first added shadow casting light's shadowMap + //in the shader + //See: https://github.com/mrdoob/three.js/issues/5932 + uniforms.tDiffuse.value = light.shadow.map.texture + + userAutoClearSetting = renderer.autoClear + renderer.autoClear = false // To allow render overlay + renderer.clearDepth() + renderer.render(scene, camera) + renderer.autoClear = userAutoClearSetting //Restore user's setting + } + } - //Force an update to set position/size - this.update() -} + this.updateForWindowResize = function () { + if (this.enabled) { + camera.left = window.innerWidth / -2 + camera.right = window.innerWidth / 2 + camera.top = window.innerHeight / 2 + camera.bottom = window.innerHeight / -2 + camera.updateProjectionMatrix() -ShadowMapViewer.prototype.constructor = ShadowMapViewer + this.update() + } + } + + this.update = function () { + this.position.set(this.position.x, this.position.y) + this.size.set(this.size.width, this.size.height) + } + + //Force an update to set position/size + this.update() + } +} export { ShadowMapViewer } diff --git a/src/utils/SkeletonUtils.d.ts b/src/utils/SkeletonUtils.d.ts index 33063f5c..a2ffedb3 100644 --- a/src/utils/SkeletonUtils.d.ts +++ b/src/utils/SkeletonUtils.d.ts @@ -1,30 +1,14 @@ -import { AnimationClip, Bone, Matrix4, Object3D, Skeleton, SkeletonHelper } from 'three' +import { AnimationClip, Object3D, Skeleton } from 'three' export namespace SkeletonUtils { - function retarget(target: Object3D | Skeleton, source: Object3D | Skeleton, options: {}): void + export function clone(source: Object3D): Object3D - function retargetClip( + export function retarget(target: Object3D | Skeleton, source: Object3D | Skeleton, options: {}): void + + export function retargetClip( target: Skeleton | Object3D, source: Skeleton | Object3D, clip: AnimationClip, options: {}, ): AnimationClip - - function getHelperFromSkeleton(skeleton: Skeleton): SkeletonHelper - - function getSkeletonOffsets(target: Object3D | Skeleton, source: Object3D | Skeleton, options: {}): Matrix4[] - - function renameBones(skeleton: Skeleton, names: {}): any - - function getBones(skeleton: Skeleton | Bone[]): Bone[] - - function getBoneByName(name: string, skeleton: Skeleton): Bone - - function getNearestBone(bone: Bone, names: {}): Bone - - function findBoneTrackData(name: string, tracks: any[]): {} - - function getEqualsBonesNames(skeleton: Skeleton, targetSkeleton: Skeleton): string[] - - function clone(source: Object3D): Object3D } diff --git a/src/utils/SkeletonUtils.js b/src/utils/SkeletonUtils.js index bdc3cd74..1d8b4b5d 100644 --- a/src/utils/SkeletonUtils.js +++ b/src/utils/SkeletonUtils.js @@ -1,460 +1,309 @@ import { AnimationClip, AnimationMixer, - Euler, Matrix4, Quaternion, QuaternionKeyframeTrack, SkeletonHelper, - Vector2, Vector3, VectorKeyframeTrack, } from 'three' -var SkeletonUtils = { - retarget: (function () { - var pos = new Vector3(), - quat = new Quaternion(), - scale = new Vector3(), - bindBoneMatrix = new Matrix4(), - relativeMatrix = new Matrix4(), - globalMatrix = new Matrix4() - - return function (target, source, options) { - options = options || {} - options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true - options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true - options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false - options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false - options.hip = options.hip !== undefined ? options.hip : 'hip' - options.names = options.names || {} - - var sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones(source), - bones = target.isObject3D ? target.skeleton.bones : this.getBones(target), - bindBones, - bone, - name, - boneTo, - bonesPosition, - i - - // reset bones - - if (target.isObject3D) { - target.skeleton.pose() - } else { - options.useTargetMatrix = true - options.preserveMatrix = false - } - - if (options.preservePosition) { - bonesPosition = [] - - for (i = 0; i < bones.length; i++) { - bonesPosition.push(bones[i].position.clone()) - } - } - - if (options.preserveMatrix) { - // reset matrix - - target.updateMatrixWorld() - - target.matrixWorld.identity() - - // reset children matrix - - for (i = 0; i < target.children.length; ++i) { - target.children[i].updateMatrixWorld(true) - } - } - - if (options.offsets) { - bindBones = [] - - for (i = 0; i < bones.length; ++i) { - bone = bones[i] - name = options.names[bone.name] || bone.name - - if (options.offsets && options.offsets[name]) { - bone.matrix.multiply(options.offsets[name]) - - bone.matrix.decompose(bone.position, bone.quaternion, bone.scale) - - bone.updateMatrixWorld() - } - - bindBones.push(bone.matrixWorld.clone()) - } - } - - for (i = 0; i < bones.length; ++i) { - bone = bones[i] - name = options.names[bone.name] || bone.name - - boneTo = this.getBoneByName(name, sourceBones) - - globalMatrix.copy(bone.matrixWorld) - - if (boneTo) { - boneTo.updateMatrixWorld() +function retarget(target, source, options = {}) { + const pos = new Vector3(), + quat = new Quaternion(), + scale = new Vector3(), + bindBoneMatrix = new Matrix4(), + relativeMatrix = new Matrix4(), + globalMatrix = new Matrix4() + + options.preserveMatrix = options.preserveMatrix !== undefined ? options.preserveMatrix : true + options.preservePosition = options.preservePosition !== undefined ? options.preservePosition : true + options.preserveHipPosition = options.preserveHipPosition !== undefined ? options.preserveHipPosition : false + options.useTargetMatrix = options.useTargetMatrix !== undefined ? options.useTargetMatrix : false + options.hip = options.hip !== undefined ? options.hip : 'hip' + options.names = options.names || {} + + const sourceBones = source.isObject3D ? source.skeleton.bones : getBones(source), + bones = target.isObject3D ? target.skeleton.bones : getBones(target) + + let bindBones, bone, name, boneTo, bonesPosition + + // reset bones + + if (target.isObject3D) { + target.skeleton.pose() + } else { + options.useTargetMatrix = true + options.preserveMatrix = false + } - if (options.useTargetMatrix) { - relativeMatrix.copy(boneTo.matrixWorld) - } else { - relativeMatrix.copy(target.matrixWorld).invert() - relativeMatrix.multiply(boneTo.matrixWorld) - } + if (options.preservePosition) { + bonesPosition = [] - // ignore scale to extract rotation + for (let i = 0; i < bones.length; i++) { + bonesPosition.push(bones[i].position.clone()) + } + } - scale.setFromMatrixScale(relativeMatrix) - relativeMatrix.scale(scale.set(1 / scale.x, 1 / scale.y, 1 / scale.z)) + if (options.preserveMatrix) { + // reset matrix - // apply to global matrix + target.updateMatrixWorld() - globalMatrix.makeRotationFromQuaternion(quat.setFromRotationMatrix(relativeMatrix)) + target.matrixWorld.identity() - if (target.isObject3D) { - var boneIndex = bones.indexOf(bone), - wBindMatrix = bindBones - ? bindBones[boneIndex] - : bindBoneMatrix.copy(target.skeleton.boneInverses[boneIndex]).invert() + // reset children matrix - globalMatrix.multiply(wBindMatrix) - } + for (let i = 0; i < target.children.length; ++i) { + target.children[i].updateMatrixWorld(true) + } + } - globalMatrix.copyPosition(relativeMatrix) - } + if (options.offsets) { + bindBones = [] - if (bone.parent && bone.parent.isBone) { - bone.matrix.copy(bone.parent.matrixWorld).invert() - bone.matrix.multiply(globalMatrix) - } else { - bone.matrix.copy(globalMatrix) - } + for (let i = 0; i < bones.length; ++i) { + bone = bones[i] + name = options.names[bone.name] || bone.name - if (options.preserveHipPosition && name === options.hip) { - bone.matrix.setPosition(pos.set(0, bone.position.y, 0)) - } + if (options.offsets[name]) { + bone.matrix.multiply(options.offsets[name]) bone.matrix.decompose(bone.position, bone.quaternion, bone.scale) bone.updateMatrixWorld() } - if (options.preservePosition) { - for (i = 0; i < bones.length; ++i) { - bone = bones[i] - name = options.names[bone.name] || bone.name - - if (name !== options.hip) { - bone.position.copy(bonesPosition[i]) - } - } - } - - if (options.preserveMatrix) { - // restore matrix - - target.updateMatrixWorld(true) - } + bindBones.push(bone.matrixWorld.clone()) } - })(), - - retargetClip: function (target, source, clip, options) { - options = options || {} - options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false - options.fps = options.fps !== undefined ? options.fps : 30 - options.names = options.names || [] - - if (!source.isObject3D) { - source = this.getHelperFromSkeleton(source) - } - - var numFrames = Math.round(clip.duration * (options.fps / 1000) * 1000), - delta = 1 / options.fps, - convertedTracks = [], - mixer = new AnimationMixer(source), - bones = this.getBones(target.skeleton), - boneDatas = [], - positionOffset, - bone, - boneTo, - boneData, - name, - i, - j - - mixer.clipAction(clip).play() - mixer.update(0) - - source.updateMatrixWorld() - - for (i = 0; i < numFrames; ++i) { - var time = i * delta + } - this.retarget(target, source, options) + for (let i = 0; i < bones.length; ++i) { + bone = bones[i] + name = options.names[bone.name] || bone.name - for (j = 0; j < bones.length; ++j) { - name = options.names[bones[j].name] || bones[j].name + boneTo = getBoneByName(name, sourceBones) - boneTo = this.getBoneByName(name, source.skeleton) + globalMatrix.copy(bone.matrixWorld) - if (boneTo) { - bone = bones[j] - boneData = boneDatas[j] = boneDatas[j] || { bone: bone } + if (boneTo) { + boneTo.updateMatrixWorld() - if (options.hip === name) { - if (!boneData.pos) { - boneData.pos = { - times: new Float32Array(numFrames), - values: new Float32Array(numFrames * 3), - } - } - - if (options.useFirstFramePosition) { - if (i === 0) { - positionOffset = bone.position.clone() - } + if (options.useTargetMatrix) { + relativeMatrix.copy(boneTo.matrixWorld) + } else { + relativeMatrix.copy(target.matrixWorld).invert() + relativeMatrix.multiply(boneTo.matrixWorld) + } - bone.position.sub(positionOffset) - } + // ignore scale to extract rotation - boneData.pos.times[i] = time + scale.setFromMatrixScale(relativeMatrix) + relativeMatrix.scale(scale.set(1 / scale.x, 1 / scale.y, 1 / scale.z)) - bone.position.toArray(boneData.pos.values, i * 3) - } + // apply to global matrix - if (!boneData.quat) { - boneData.quat = { - times: new Float32Array(numFrames), - values: new Float32Array(numFrames * 4), - } - } + globalMatrix.makeRotationFromQuaternion(quat.setFromRotationMatrix(relativeMatrix)) - boneData.quat.times[i] = time + if (target.isObject3D) { + const boneIndex = bones.indexOf(bone), + wBindMatrix = bindBones + ? bindBones[boneIndex] + : bindBoneMatrix.copy(target.skeleton.boneInverses[boneIndex]).invert() - bone.quaternion.toArray(boneData.quat.values, i * 4) - } + globalMatrix.multiply(wBindMatrix) } - mixer.update(delta) - - source.updateMatrixWorld() + globalMatrix.copyPosition(relativeMatrix) } - for (i = 0; i < boneDatas.length; ++i) { - boneData = boneDatas[i] - - if (boneData) { - if (boneData.pos) { - convertedTracks.push( - new VectorKeyframeTrack( - '.bones[' + boneData.bone.name + '].position', - boneData.pos.times, - boneData.pos.values, - ), - ) - } - - convertedTracks.push( - new QuaternionKeyframeTrack( - '.bones[' + boneData.bone.name + '].quaternion', - boneData.quat.times, - boneData.quat.values, - ), - ) - } + if (bone.parent && bone.parent.isBone) { + bone.matrix.copy(bone.parent.matrixWorld).invert() + bone.matrix.multiply(globalMatrix) + } else { + bone.matrix.copy(globalMatrix) } - mixer.uncacheAction(clip) - - return new AnimationClip(clip.name, -1, convertedTracks) - }, - - getHelperFromSkeleton: function (skeleton) { - var source = new SkeletonHelper(skeleton.bones[0]) - source.skeleton = skeleton + if (options.preserveHipPosition && name === options.hip) { + bone.matrix.setPosition(pos.set(0, bone.position.y, 0)) + } - return source - }, + bone.matrix.decompose(bone.position, bone.quaternion, bone.scale) - getSkeletonOffsets: (function () { - var targetParentPos = new Vector3(), - targetPos = new Vector3(), - sourceParentPos = new Vector3(), - sourcePos = new Vector3(), - targetDir = new Vector2(), - sourceDir = new Vector2() + bone.updateMatrixWorld() + } - return function (target, source, options) { - options = options || {} - options.hip = options.hip !== undefined ? options.hip : 'hip' - options.names = options.names || {} + if (options.preservePosition) { + for (let i = 0; i < bones.length; ++i) { + bone = bones[i] + name = options.names[bone.name] || bone.name - if (!source.isObject3D) { - source = this.getHelperFromSkeleton(source) + if (name !== options.hip) { + bone.position.copy(bonesPosition[i]) } + } + } + + if (options.preserveMatrix) { + // restore matrix - var nameKeys = Object.keys(options.names), - nameValues = Object.values(options.names), - sourceBones = source.isObject3D ? source.skeleton.bones : this.getBones(source), - bones = target.isObject3D ? target.skeleton.bones : this.getBones(target), - offsets = [], - bone, - boneTo, - name, - i + target.updateMatrixWorld(true) + } +} - target.skeleton.pose() +function retargetClip(target, source, clip, options = {}) { + options.useFirstFramePosition = options.useFirstFramePosition !== undefined ? options.useFirstFramePosition : false + options.fps = options.fps !== undefined ? options.fps : 30 + options.names = options.names || [] - for (i = 0; i < bones.length; ++i) { - bone = bones[i] - name = options.names[bone.name] || bone.name + if (!source.isObject3D) { + source = getHelperFromSkeleton(source) + } - boneTo = this.getBoneByName(name, sourceBones) + const numFrames = Math.round(clip.duration * (options.fps / 1000) * 1000), + delta = 1 / options.fps, + convertedTracks = [], + mixer = new AnimationMixer(source), + bones = getBones(target.skeleton), + boneDatas = [] + let positionOffset, bone, boneTo, boneData, name - if (boneTo && name !== options.hip) { - var boneParent = this.getNearestBone(bone.parent, nameKeys), - boneToParent = this.getNearestBone(boneTo.parent, nameValues) + mixer.clipAction(clip).play() + mixer.update(0) - boneParent.updateMatrixWorld() - boneToParent.updateMatrixWorld() + source.updateMatrixWorld() - targetParentPos.setFromMatrixPosition(boneParent.matrixWorld) - targetPos.setFromMatrixPosition(bone.matrixWorld) + for (let i = 0; i < numFrames; ++i) { + const time = i * delta - sourceParentPos.setFromMatrixPosition(boneToParent.matrixWorld) - sourcePos.setFromMatrixPosition(boneTo.matrixWorld) + retarget(target, source, options) - targetDir - .subVectors(new Vector2(targetPos.x, targetPos.y), new Vector2(targetParentPos.x, targetParentPos.y)) - .normalize() + for (let j = 0; j < bones.length; ++j) { + name = options.names[bones[j].name] || bones[j].name - sourceDir - .subVectors(new Vector2(sourcePos.x, sourcePos.y), new Vector2(sourceParentPos.x, sourceParentPos.y)) - .normalize() + boneTo = getBoneByName(name, source.skeleton) - var laterialAngle = targetDir.angle() - sourceDir.angle() + if (boneTo) { + bone = bones[j] + boneData = boneDatas[j] = boneDatas[j] || { bone: bone } - var offset = new Matrix4().makeRotationFromEuler(new Euler(0, 0, laterialAngle)) + if (options.hip === name) { + if (!boneData.pos) { + boneData.pos = { + times: new Float32Array(numFrames), + values: new Float32Array(numFrames * 3), + } + } - bone.matrix.multiply(offset) + if (options.useFirstFramePosition) { + if (i === 0) { + positionOffset = bone.position.clone() + } - bone.matrix.decompose(bone.position, bone.quaternion, bone.scale) + bone.position.sub(positionOffset) + } - bone.updateMatrixWorld() + boneData.pos.times[i] = time - offsets[name] = offset + bone.position.toArray(boneData.pos.values, i * 3) } - } - - return offsets - } - })(), - renameBones: function (skeleton, names) { - var bones = this.getBones(skeleton) + if (!boneData.quat) { + boneData.quat = { + times: new Float32Array(numFrames), + values: new Float32Array(numFrames * 4), + } + } - for (let i = 0; i < bones.length; ++i) { - var bone = bones[i] + boneData.quat.times[i] = time - if (names[bone.name]) { - bone.name = names[bone.name] + bone.quaternion.toArray(boneData.quat.values, i * 4) } } - return this - }, + mixer.update(delta) - getBones: function (skeleton) { - return Array.isArray(skeleton) ? skeleton : skeleton.bones - }, + source.updateMatrixWorld() + } - getBoneByName: function (name, skeleton) { - for (let i = 0, bones = this.getBones(skeleton); i < bones.length; i++) { - if (name === bones[i].name) return bones[i] - } - }, + for (let i = 0; i < boneDatas.length; ++i) { + boneData = boneDatas[i] - getNearestBone: function (bone, names) { - while (bone.isBone) { - if (names.indexOf(bone.name) !== -1) { - return bone + if (boneData) { + if (boneData.pos) { + convertedTracks.push( + new VectorKeyframeTrack( + '.bones[' + boneData.bone.name + '].position', + boneData.pos.times, + boneData.pos.values, + ), + ) } - bone = bone.parent - } - }, - - findBoneTrackData: function (name, tracks) { - var regexp = /\[(.*)\]\.(.*)/, - result = { name: name } - - for (let i = 0; i < tracks.length; ++i) { - // 1 is track name - // 2 is track type - var trackData = regexp.exec(tracks[i].name) - - if (trackData && name === trackData[1]) { - result[trackData[2]] = i - } + convertedTracks.push( + new QuaternionKeyframeTrack( + '.bones[' + boneData.bone.name + '].quaternion', + boneData.quat.times, + boneData.quat.values, + ), + ) } + } - return result - }, + mixer.uncacheAction(clip) - getEqualsBonesNames: function (skeleton, targetSkeleton) { - var sourceBones = this.getBones(skeleton), - targetBones = this.getBones(targetSkeleton), - bones = [] + return new AnimationClip(clip.name, -1, convertedTracks) +} - search: for (let i = 0; i < sourceBones.length; i++) { - var boneName = sourceBones[i].name +function clone(source) { + const sourceLookup = new Map() + const cloneLookup = new Map() - for (let j = 0; j < targetBones.length; j++) { - if (boneName === targetBones[j].name) { - bones.push(boneName) + const clone = source.clone() - continue search - } - } - } + parallelTraverse(source, clone, function (sourceNode, clonedNode) { + sourceLookup.set(clonedNode, sourceNode) + cloneLookup.set(sourceNode, clonedNode) + }) - return bones - }, + clone.traverse(function (node) { + if (!node.isSkinnedMesh) return - clone: function (source) { - var sourceLookup = new Map() - var cloneLookup = new Map() + const clonedMesh = node + const sourceMesh = sourceLookup.get(node) + const sourceBones = sourceMesh.skeleton.bones - var clone = source.clone() + clonedMesh.skeleton = sourceMesh.skeleton.clone() + clonedMesh.bindMatrix.copy(sourceMesh.bindMatrix) - parallelTraverse(source, clone, function (sourceNode, clonedNode) { - sourceLookup.set(clonedNode, sourceNode) - cloneLookup.set(sourceNode, clonedNode) + clonedMesh.skeleton.bones = sourceBones.map(function (bone) { + return cloneLookup.get(bone) }) - clone.traverse(function (node) { - if (!node.isSkinnedMesh) return + clonedMesh.bind(clonedMesh.skeleton, clonedMesh.bindMatrix) + }) - var clonedMesh = node - var sourceMesh = sourceLookup.get(node) - var sourceBones = sourceMesh.skeleton.bones + return clone +} - clonedMesh.skeleton = sourceMesh.skeleton.clone() - clonedMesh.bindMatrix.copy(sourceMesh.bindMatrix) +// internal helper - clonedMesh.skeleton.bones = sourceBones.map(function (bone) { - return cloneLookup.get(bone) - }) +function getBoneByName(name, skeleton) { + for (let i = 0, bones = getBones(skeleton); i < bones.length; i++) { + if (name === bones[i].name) return bones[i] + } +} - clonedMesh.bind(clonedMesh.skeleton, clonedMesh.bindMatrix) - }) +function getBones(skeleton) { + return Array.isArray(skeleton) ? skeleton : skeleton.bones +} + +function getHelperFromSkeleton(skeleton) { + const source = new SkeletonHelper(skeleton.bones[0]) + source.skeleton = skeleton - return clone - }, + return source } function parallelTraverse(a, b, callback) { @@ -465,4 +314,4 @@ function parallelTraverse(a, b, callback) { } } -export { SkeletonUtils } +export const SkeletonUtils = { retarget, retargetClip, clone } diff --git a/src/utils/UVsDebug.js b/src/utils/UVsDebug.js index 91dbf1bf..12d3da02 100644 --- a/src/utils/UVsDebug.js +++ b/src/utils/UVsDebug.js @@ -8,24 +8,24 @@ import { Vector2 } from 'three' * */ -var UVsDebug = function (geometry, size) { +function UVsDebug(geometry, size = 1024) { // handles wrapping of uv.x > 1 only - var abc = 'abc' - var a = new Vector2() - var b = new Vector2() + const abc = 'abc' + const a = new Vector2() + const b = new Vector2() - var uvs = [new Vector2(), new Vector2(), new Vector2()] + const uvs = [new Vector2(), new Vector2(), new Vector2()] - var face = [] + const face = [] - var canvas = document.createElement('canvas') - var width = size || 1024 // power of 2 required for wrapping - var height = size || 1024 + const canvas = document.createElement('canvas') + const width = size // power of 2 required for wrapping + const height = size canvas.width = width canvas.height = height - var ctx = canvas.getContext('2d') + const ctx = canvas.getContext('2d') ctx.lineWidth = 1 ctx.strokeStyle = 'rgb( 63, 63, 63 )' ctx.textAlign = 'center' @@ -35,41 +35,36 @@ var UVsDebug = function (geometry, size) { ctx.fillStyle = 'rgb( 255, 255, 255 )' ctx.fillRect(0, 0, width, height) - if (geometry.isGeometry) { - console.error('THREE.UVsDebug no longer supports Geometry. Use THREE.BufferGeometry instead.') - return - } else { - var index = geometry.index - var uvAttribute = geometry.attributes.uv + const index = geometry.index + const uvAttribute = geometry.attributes.uv - if (index) { - // indexed geometry + if (index) { + // indexed geometry - for (let i = 0, il = index.count; i < il; i += 3) { - face[0] = index.getX(i) - face[1] = index.getX(i + 1) - face[2] = index.getX(i + 2) + for (let i = 0, il = index.count; i < il; i += 3) { + face[0] = index.getX(i) + face[1] = index.getX(i + 1) + face[2] = index.getX(i + 2) - uvs[0].fromBufferAttribute(uvAttribute, face[0]) - uvs[1].fromBufferAttribute(uvAttribute, face[1]) - uvs[2].fromBufferAttribute(uvAttribute, face[2]) + uvs[0].fromBufferAttribute(uvAttribute, face[0]) + uvs[1].fromBufferAttribute(uvAttribute, face[1]) + uvs[2].fromBufferAttribute(uvAttribute, face[2]) - processFace(face, uvs, i / 3) - } - } else { - // non-indexed geometry + processFace(face, uvs, i / 3) + } + } else { + // non-indexed geometry - for (let i = 0, il = uvAttribute.count; i < il; i += 3) { - face[0] = i - face[1] = i + 1 - face[2] = i + 2 + for (let i = 0, il = uvAttribute.count; i < il; i += 3) { + face[0] = i + face[1] = i + 1 + face[2] = i + 2 - uvs[0].fromBufferAttribute(uvAttribute, face[0]) - uvs[1].fromBufferAttribute(uvAttribute, face[1]) - uvs[2].fromBufferAttribute(uvAttribute, face[2]) + uvs[0].fromBufferAttribute(uvAttribute, face[0]) + uvs[1].fromBufferAttribute(uvAttribute, face[1]) + uvs[2].fromBufferAttribute(uvAttribute, face[2]) - processFace(face, uvs, i / 3) - } + processFace(face, uvs, i / 3) } } @@ -83,7 +78,7 @@ var UVsDebug = function (geometry, size) { a.set(0, 0) for (let j = 0, jl = uvs.length; j < jl; j++) { - var uv = uvs[j] + const uv = uvs[j] a.x += uv.x a.y += uv.y @@ -121,11 +116,11 @@ var UVsDebug = function (geometry, size) { // label uv edge orders - for (j = 0, jl = uvs.length; j < jl; j++) { - var uv = uvs[j] + for (let j = 0, jl = uvs.length; j < jl; j++) { + const uv = uvs[j] b.addVectors(a, uv).divideScalar(2) - var vnum = face[j] + const vnum = face[j] ctx.fillText(abc[j] + vnum, b.x * width, (1 - b.y) * height) if (b.x > 0.95) { diff --git a/src/webxr/ARButton.ts b/src/webxr/ARButton.ts index 372f6fc4..d29b80dc 100644 --- a/src/webxr/ARButton.ts +++ b/src/webxr/ARButton.ts @@ -1,4 +1,4 @@ -import { Navigator, WebGLRenderer, XRSession, XRSessionInit } from 'three' +import { WebGLRenderer } from 'three' class ARButton { static createButton(renderer: WebGLRenderer, sessionInit: XRSessionInit = {}): HTMLButtonElement | HTMLAnchorElement { @@ -44,7 +44,7 @@ class ARButton { renderer.xr.setReferenceSpaceType('local') - await renderer.xr.setSession(session) + await renderer.xr.setSession(session as any) button.textContent = 'STOP AR' ;(sessionInit as any).domOverlay!.root.style.display = '' @@ -131,7 +131,7 @@ class ARButton { // Query for session mode ;(navigator as Navigator) .xr!.isSessionSupported('immersive-ar') - .then(function (supported) { + .then(function (supported: boolean) { supported ? showStartAR() : showARNotSupported() }) .catch(showARNotSupported) diff --git a/src/webxr/OculusHandModel.ts b/src/webxr/OculusHandModel.ts index 5b84deab..18acee9d 100644 --- a/src/webxr/OculusHandModel.ts +++ b/src/webxr/OculusHandModel.ts @@ -1,4 +1,4 @@ -import { Object3D, Sphere, Box3, Mesh, Texture, XRInputSource, Vector3 } from 'three' +import { Object3D, Sphere, Box3, Mesh, Texture, Vector3 } from 'three' import { XRHandMeshModel } from './XRHandMeshModel' const TOUCH_RADIUS = 0.01 diff --git a/src/webxr/OculusHandPointerModel.d.ts b/src/webxr/OculusHandPointerModel.d.ts index a66a46a9..cf1c957d 100644 --- a/src/webxr/OculusHandPointerModel.d.ts +++ b/src/webxr/OculusHandPointerModel.d.ts @@ -5,7 +5,7 @@ import { MeshBasicMaterial, Object3D, Raycaster, - SphereBufferGeometry, + SphereGeometry, Texture, Vector3, } from 'three' @@ -26,7 +26,7 @@ export class OculusHandPointerModel extends Object3D { pinched: boolean attached: boolean - cursorObject: Mesh | null + cursorObject: Mesh | null raycaster: Raycaster diff --git a/src/webxr/OculusHandPointerModel.js b/src/webxr/OculusHandPointerModel.js index 3431e17f..1169076e 100644 --- a/src/webxr/OculusHandPointerModel.js +++ b/src/webxr/OculusHandPointerModel.js @@ -13,8 +13,8 @@ const POINTER_LENGTH = 0.035 const POINTER_SEGMENTS = 16 const POINTER_RINGS = 12 const POINTER_HEMISPHERE_ANGLE = 110 -const YAXIS = new THREE.Vector3(0, 1, 0) -const ZAXIS = new THREE.Vector3(0, 0, 1) +const YAXIS = /* @__PURE__ */ new THREE.Vector3(0, 1, 0) +const ZAXIS = /* @__PURE__ */ new THREE.Vector3(0, 0, 1) const CURSOR_RADIUS = 0.02 const CURSOR_MAX_DISTANCE = 1.5 diff --git a/src/webxr/VRButton.ts b/src/webxr/VRButton.ts index 8cd157e0..8d14c500 100644 --- a/src/webxr/VRButton.ts +++ b/src/webxr/VRButton.ts @@ -1,4 +1,4 @@ -import { Navigator, WebGLRenderer, XRSession, XRSessionInit } from 'three' +import { WebGLRenderer } from 'three' class VRButton { static createButton(renderer: WebGLRenderer, sessionInit: XRSessionInit = {}): HTMLButtonElement | HTMLAnchorElement { @@ -10,7 +10,7 @@ class VRButton { async function onSessionStarted(session: XRSession): Promise { session.addEventListener('end', onSessionEnded) - await renderer.xr.setSession(session) + await renderer.xr.setSession(session as any) button.textContent = 'EXIT VR' currentSession = session @@ -104,7 +104,7 @@ class VRButton { button.style.display = 'none' // Query for session mode - ;(navigator as Navigator).xr!.isSessionSupported('immersive-vr').then((supported) => { + navigator.xr!.isSessionSupported('immersive-vr').then((supported) => { supported ? showEnterVR() : showWebXRNotFound() if (supported && VRButton.xrSessionIsGranted) { diff --git a/src/webxr/XRControllerModelFactory.ts b/src/webxr/XRControllerModelFactory.ts index 011ef21e..ee911d60 100644 --- a/src/webxr/XRControllerModelFactory.ts +++ b/src/webxr/XRControllerModelFactory.ts @@ -1,6 +1,5 @@ import { Mesh, Object3D, SphereGeometry, MeshBasicMaterial } from 'three' import type { Texture, Group } from 'three' -// @ts-ignore import { GLTFLoader } from '../loaders/GLTFLoader' import { fetchProfile, MotionController, MotionControllerConstants } from '../libs/MotionControllers' diff --git a/src/webxr/XRHandMeshModel.ts b/src/webxr/XRHandMeshModel.ts index ca81d796..24b3e771 100644 --- a/src/webxr/XRHandMeshModel.ts +++ b/src/webxr/XRHandMeshModel.ts @@ -1,5 +1,4 @@ import { Object3D } from 'three' -// @ts-ignore import { GLTFLoader } from '../loaders/GLTFLoader' const DEFAULT_HAND_PROFILE_PATH = diff --git a/src/webxr/XRHandPrimitiveModel.js b/src/webxr/XRHandPrimitiveModel.js index 9e98456a..9f072dcb 100644 --- a/src/webxr/XRHandPrimitiveModel.js +++ b/src/webxr/XRHandPrimitiveModel.js @@ -8,8 +8,8 @@ import { Vector3, } from 'three' -const _matrix = new Matrix4() -const _vector = new Vector3() +const _matrix = /* @__PURE__ */ /* @__PURE__ */ new Matrix4() +const _vector = /* @__PURE__ */ new Vector3() class XRHandPrimitiveModel { constructor(handModel, controller, path, handedness, options) { diff --git a/yarn.lock b/yarn.lock index b407c9dd..a03852db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -149,21 +149,16 @@ resolved "https://registry.yarnpkg.com/@types/offscreencanvas/-/offscreencanvas-2019.6.4.tgz#64f6d120b53925028299c744fcdd32d2cd525963" integrity sha512-u8SAgdZ8ROtkTF+mfZGOscl0or6BSj9A4g37e6nvxDc+YB/oDut0wHkK2PBBiC2bNR8TS0CPV+1gAk4fNisr1Q== -"@types/three@^0.139.0": - version "0.139.0" - resolved "https://registry.yarnpkg.com/@types/three/-/three-0.139.0.tgz#69af1f0c52f8eea390f513e05478af1dd7f49e6f" - integrity sha512-4V/jZhyq7Mv05coUzxL3bz8AuBOSi/1F0RY7ujisHTV0Amy/fnYJ+s7TSJ1/hXjZukSkpuFRgV+wvWUEMbsMbQ== +"@types/three@^0.128.0": + version "0.128.0" + resolved "https://registry.yarnpkg.com/@types/three/-/three-0.128.0.tgz#e5ce4c42d138a64dd90e83dbdb9919cf0d424d8e" + integrity sha512-Jwq5XYUkzAcPTo34hlGAQGUyAI0b2F3aCCFWG/v7ZhJBEG5HGcusMSr70GhDlT8Gs0f02QnSPZ2RCA1MrCOa/w== "@types/webxr@^0.5.2": version "0.5.2" resolved "https://registry.yarnpkg.com/@types/webxr/-/webxr-0.5.2.tgz#5d9627b0ffe223aa3b166de7112ac8a9460dc54f" integrity sha512-szL74BnIcok9m7QwYtVmQ+EdIKwbjPANudfuvDrAF8Cljg9MKUlIoc1w5tjj9PMpeSH3U1Xnx//czQybJ0EfSw== -"@webgpu/glslang@^0.0.15": - version "0.0.15" - resolved "https://registry.yarnpkg.com/@webgpu/glslang/-/glslang-0.0.15.tgz#f5ccaf6015241e6175f4b90906b053f88483d1f2" - integrity sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q== - ansi-regex@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" @@ -527,10 +522,10 @@ strip-ansi@^6.0.0: dependencies: ansi-regex "^5.0.0" -three@^0.139.2: - version "0.139.2" - resolved "https://registry.yarnpkg.com/three/-/three-0.139.2.tgz#b110799a15736df673b9293e31653a4ac73648dd" - integrity sha512-gV7q7QY8rogu7HLFZR9cWnOQAUedUhu2WXAnpr2kdXZP9YDKsG/0ychwQvWkZN5PlNw9mv5MoCTin6zNTXoONg== +three@^0.128.0: + version "0.128.0" + resolved "https://registry.yarnpkg.com/three/-/three-0.128.0.tgz#884dacca6a330e45600a09ec5439283f50b76aa6" + integrity sha512-i0ap/E+OaSfzw7bD1TtYnPo3VEplkl70WX5fZqZnfZsE3k3aSFudqrrC9ldFZfYFkn1zwDmBcdGfiIm/hnbyZA== through2@^2.0.1: version "2.0.5" @@ -545,10 +540,10 @@ tiny-inflate@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== -typescript@^4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.4.tgz#8610b59747de028fda898a8aef0e103f156d0961" - integrity sha512-V+evlYHZnQkaz8TRBuxTA92yZBPotr5H+WhQ7bD3hZUndx5tGOa1fuCgeSjxAzM1RiN5IzvadIXTVefuuwZCRg== +typescript@^4.7.4: + version "4.9.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" + integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== untildify@^4.0.0: version "4.0.0"