diff --git a/jsm/animation/AnimationClipCreator.js b/jsm/animation/AnimationClipCreator.js new file mode 100644 index 0000000..2646c7c --- /dev/null +++ b/jsm/animation/AnimationClipCreator.js @@ -0,0 +1,114 @@ +import { + AnimationClip, + BooleanKeyframeTrack, + ColorKeyframeTrack, + NumberKeyframeTrack, + Vector3, + VectorKeyframeTrack +} from 'three'; + +class AnimationClipCreator { + + static CreateRotationAnimation( period, axis = 'x' ) { + + const times = [ 0, period ], values = [ 0, 360 ]; + + const trackName = '.rotation[' + axis + ']'; + + const track = new NumberKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, period, [ track ] ); + + } + + static CreateScaleAxisAnimation( period, axis = 'x' ) { + + const times = [ 0, period ], values = [ 0, 1 ]; + + const trackName = '.scale[' + axis + ']'; + + const track = new NumberKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, period, [ track ] ); + + } + + static CreateShakeAnimation( duration, shakeScale ) { + + const times = [], values = [], tmp = new Vector3(); + + 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 ); + + } + + const trackName = '.position'; + + const track = new VectorKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + + static CreatePulsationAnimation( duration, pulseScale ) { + + const times = [], values = [], tmp = new Vector3(); + + 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 trackName = '.scale'; + + const track = new VectorKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + + static CreateVisibilityAnimation( duration ) { + + const times = [ 0, duration / 2, duration ], values = [ true, false, true ]; + + const trackName = '.visible'; + + const track = new BooleanKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + + static CreateMaterialColorAnimation( duration, colors ) { + + const times = [], values = [], + timeStep = duration / colors.length; + + for ( let i = 0; i <= colors.length; i ++ ) { + + times.push( i * timeStep ); + values.push( colors[ i % colors.length ] ); + + } + + const trackName = '.material[0].color'; + + const track = new ColorKeyframeTrack( trackName, times, values ); + + return new AnimationClip( null, duration, [ track ] ); + + } + +} + +export { AnimationClipCreator }; diff --git a/jsm/animation/CCDIKSolver.js b/jsm/animation/CCDIKSolver.js new file mode 100644 index 0000000..7282c76 --- /dev/null +++ b/jsm/animation/CCDIKSolver.js @@ -0,0 +1,458 @@ +import { + BufferAttribute, + BufferGeometry, + Color, + Line, + LineBasicMaterial, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + Quaternion, + SphereGeometry, + Vector3 +} from 'three'; + +const _q = new Quaternion(); +const _targetPos = new Vector3(); +const _targetVec = new Vector3(); +const _effectorPos = new Vector3(); +const _effectorVec = new Vector3(); +const _linkPos = new Vector3(); +const _invLinkQ = new Quaternion(); +const _linkScale = new Vector3(); +const _axis = new Vector3(); +const _vector = new Vector3(); +const _matrix = 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.mesh.geometry.userData.MMD.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 = [] ) { + + super(); + + this.root = mesh; + this.iks = iks; + + this.matrix.copy( mesh.matrixWorld ); + this.matrixAutoUpdate = false; + + this.sphereGeometry = new SphereGeometry( 0.25, 16, 8 ); + + this.targetSphereMaterial = new MeshBasicMaterial( { + color: new Color( 0xff8888 ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this.effectorSphereMaterial = new MeshBasicMaterial( { + color: new Color( 0x88ff88 ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this.linkSphereMaterial = new MeshBasicMaterial( { + color: new Color( 0x8888ff ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this.lineMaterial = new LineBasicMaterial( { + color: new Color( 0xff0000 ), + depthTest: false, + depthWrite: false, + transparent: true + } ); + + this._init(); + + } + + /** + * Updates IK bones visualization. + */ + updateMatrixWorld( force ) { + + const mesh = this.root; + + if ( this.visible ) { + + let offset = 0; + + const iks = this.iks; + const bones = mesh.skeleton.bones; + + _matrix.copy( mesh.matrixWorld ).invert(); + + for ( let i = 0, il = iks.length; i < il; i ++ ) { + + const ik = iks[ i ]; + + const targetBone = bones[ ik.target ]; + const effectorBone = bones[ ik.effector ]; + + const targetMesh = this.children[ offset ++ ]; + const effectorMesh = this.children[ offset ++ ]; + + 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 ]; + const linkBone = bones[ link.index ]; + + const linkMesh = this.children[ offset ++ ]; + + linkMesh.position.copy( getPosition( linkBone, _matrix ) ); + + } + + const line = this.children[ offset ++ ]; + const array = line.geometry.attributes.position.array; + + 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 ]; + setPositionOfBoneToAttributeArray( array, j + 2, linkBone, _matrix ); + + } + + line.geometry.attributes.position.needsUpdate = true; + + } + + } + + this.matrix.copy( mesh.matrixWorld ); + + super.updateMatrixWorld( force ); + + } + + // private method + + _init() { + + const scope = this; + const iks = this.iks; + + 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; + + } + + 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 ) ); + + } + + } + +} + +export { CCDIKSolver, CCDIKHelper }; diff --git a/jsm/animation/MMDAnimationHelper.js b/jsm/animation/MMDAnimationHelper.js new file mode 100644 index 0000000..b24dea1 --- /dev/null +++ b/jsm/animation/MMDAnimationHelper.js @@ -0,0 +1,1207 @@ +import { + AnimationMixer, + Object3D, + Quaternion, + Vector3 +} from 'three'; +import { CCDIKSolver } from '../animation/CCDIKSolver.js'; +import { MMDPhysics } from '../animation/MMDPhysics.js'; + +/** + * MMDAnimationHelper handles animation of MMD assets loaded by MMDLoader + * with MMD special features as IK, Grant, and Physics. + * + * Dependencies + * - ammo.js https://github.com/kripken/ammo.js + * - MMDPhysics + * - CCDIKSolver + * + * TODO + * - more precise grant skinning support. + */ +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. + */ + constructor( params = {} ) { + + this.meshes = []; + + this.camera = null; + this.cameraTarget = new Object3D(); + this.cameraTarget.name = 'target'; + + this.audio = null; + this.audioManager = null; + + this.objects = new WeakMap(); + + this.configuration = { + 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 = { + animation: true, + ik: true, + grant: true, + physics: true, + cameraAnimation: true + }; + + this.onBeforePhysics = function ( /* mesh */ ) {}; + + // experimental + this.sharedPhysics = false; + this.masterPhysics = null; + + } + + /** + * 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( 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(); + + return this; + + } + + /** + * Updates the animation. + * + * @param {Number} delta + * @return {MMDAnimationHelper} + */ + update( delta ) { + + if ( this.audioManager !== null ) this.audioManager.control( delta ); + + for ( let i = 0; i < this.meshes.length; i ++ ) { + + this._animateMesh( this.meshes[ i ], delta ); + + } + + if ( this.sharedPhysics ) this._updateSharedPhysics( delta ); + + if ( this.camera !== null ) this._animateCamera( this.camera, delta ); + + return this; + + } + + /** + * 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 bones = mesh.skeleton.bones; + const boneParams = vpd.bones; + + const boneNameDictionary = {}; + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + boneNameDictionary[ bones[ i ].name ] = i; + + } + + const vector = new Vector3(); + const quaternion = new Quaternion(); + + for ( let i = 0, il = boneParams.length; i < il; i ++ ) { + + const boneParam = boneParams[ i ]; + const boneIndex = boneNameDictionary[ boneParam.name ]; + + 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 ); + + // 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(); + + } + + if ( params.grant !== false ) { + + this.createGrantSolver( mesh ).update(); + + } + + } + + return this; + + } + + /** + * 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 ); + + } + + this.enabled[ key ] = enabled; + + if ( key === 'physics' ) { + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + this._optimizeIK( this.meshes[ i ], enabled ); + + } + + } + + return this; + + } + + /** + * Creates an GrantSolver instance. + * + * @param {THREE.SkinnedMesh} mesh + * @return {GrantSolver} + */ + createGrantSolver( mesh ) { + + return new GrantSolver( mesh, mesh.geometry.userData.MMD.grants ); + + } + + // private methods + + _addMesh( mesh, params ) { + + if ( this.meshes.indexOf( mesh ) >= 0 ) { + + throw new Error( 'THREE.MMDAnimationHelper._addMesh: ' + + 'SkinnedMesh \'' + mesh.name + '\' has already been added.' ); + + } + + this.meshes.push( mesh ); + this.objects.set( mesh, { looped: false } ); + + this._setupMeshAnimation( mesh, params.animation ); + + if ( params.physics !== false ) { + + this._setupMeshPhysics( mesh, params ); + + } + + return this; + + } + + _setupCamera( camera, params ) { + + if ( this.camera === camera ) { + + throw new Error( 'THREE.MMDAnimationHelper._setupCamera: ' + + 'Camera \'' + camera.name + '\' has already been set.' ); + + } + + if ( this.camera ) this.clearCamera( this.camera ); + + this.camera = camera; + + camera.add( this.cameraTarget ); + + this.objects.set( camera, {} ); + + if ( params.animation !== undefined ) { + + this._setupCameraAnimation( camera, params.animation ); + + } + + return this; + + } + + _setupAudio( audio, params ) { + + if ( this.audio === audio ) { + + throw new Error( 'THREE.MMDAnimationHelper._setupAudio: ' + + 'Audio \'' + audio.name + '\' has already been set.' ); + + } + + if ( this.audio ) this.clearAudio( this.audio ); + + this.audio = audio; + this.audioManager = new AudioManager( audio, params ); + + this.objects.set( this.audioManager, { + duration: this.audioManager.duration + } ); + + return this; + + } + + _removeMesh( mesh ) { + + let found = false; + let writeIndex = 0; + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + if ( this.meshes[ i ] === mesh ) { + + this.objects.delete( mesh ); + found = true; + + continue; + + } + + this.meshes[ writeIndex ++ ] = this.meshes[ i ]; + + } + + if ( ! found ) { + + throw new Error( 'THREE.MMDAnimationHelper._removeMesh: ' + + 'SkinnedMesh \'' + mesh.name + '\' has not been added yet.' ); + + } + + this.meshes.length = writeIndex; + + return this; + + } + + _clearCamera( camera ) { + + if ( camera !== this.camera ) { + + throw new Error( 'THREE.MMDAnimationHelper._clearCamera: ' + + 'Camera \'' + camera.name + '\' has not been set yet.' ); + + } + + this.camera.remove( this.cameraTarget ); + + this.objects.delete( this.camera ); + this.camera = null; + + return this; + + } + + _clearAudio( audio ) { + + if ( audio !== this.audio ) { + + throw new Error( 'THREE.MMDAnimationHelper._clearAudio: ' + + 'Audio \'' + audio.name + '\' has not been set yet.' ); + + } + + this.objects.delete( this.audioManager ); + + this.audio = null; + this.audioManager = null; + + return this; + + } + + _setupMeshAnimation( mesh, animation ) { + + const objects = this.objects.get( mesh ); + + if ( animation !== undefined ) { + + const animations = Array.isArray( animation ) + ? animation : [ animation ]; + + objects.mixer = new AnimationMixer( mesh ); + + for ( let i = 0, il = animations.length; i < il; i ++ ) { + + objects.mixer.clipAction( animations[ i ] ).play(); + + } + + // 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; + + if ( tracks.length > 0 && tracks[ 0 ].name.slice( 0, 6 ) !== '.bones' ) return; + + objects.looped = true; + + } ); + + } + + objects.ikSolver = this._createCCDIKSolver( mesh ); + objects.grantSolver = this.createGrantSolver( mesh ); + + return this; + + } + + _setupCameraAnimation( camera, animation ) { + + const animations = Array.isArray( animation ) + ? animation : [ animation ]; + + const objects = this.objects.get( camera ); + + objects.mixer = new AnimationMixer( camera ); + + for ( let i = 0, il = animations.length; i < il; i ++ ) { + + objects.mixer.clipAction( animations[ i ] ).play(); + + } + + } + + _setupMeshPhysics( mesh, params ) { + + const objects = this.objects.get( mesh ); + + // shared physics is experimental + + if ( params.world === undefined && this.sharedPhysics ) { + + const masterPhysics = this._getMasterPhysics(); + + if ( masterPhysics !== null ) world = masterPhysics.world; // eslint-disable-line no-undef + + } + + 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 ); + + this._optimizeIK( mesh, true ); + + } + + _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; + + if ( mixer && this.enabled.animation ) { + + // alternate solution to save/restore bones but less performant? + //mesh.pose(); + //this._updatePropertyMixersBuffer( mesh ); + + this._restoreBones( mesh ); + + mixer.update( delta ); + + 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(); + + } + + if ( grantSolver && this.enabled.grant ) { + + grantSolver.update(); + + } + + } + + } + + if ( looped === true && this.enabled.physics ) { + + if ( physics && this.configuration.resetPhysicsOnLoop ) physics.reset(); + + objects.looped = false; + + } + + 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; + + } + + } ); + + } + + // 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 ); + + } + + mesh.updateMatrixWorld( true ); + return this; + + } + + _animateCamera( camera, delta ) { + + const mixer = this.objects.get( camera ).mixer; + + if ( mixer && this.enabled.cameraAnimation ) { + + mixer.update( delta ); + + camera.updateProjectionMatrix(); + + camera.up.set( 0, 1, 0 ); + camera.up.applyQuaternion( camera.quaternion ); + camera.lookAt( this.cameraTarget.position ); + + } + + } + + _optimizeIK( mesh, physicsEnabled ) { + + const iks = mesh.geometry.userData.MMD.iks; + const bones = mesh.geometry.userData.MMD.bones; + + 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( mesh ) { + + if ( CCDIKSolver === undefined ) { + + throw new Error( 'THREE.MMDAnimationHelper: Import CCDIKSolver.' ); + + } + + return new CCDIKSolver( mesh, mesh.geometry.userData.MMD.iks ); + + } + + _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 ); + + } + + /* + * 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; + + // get the longest duration + + for ( let i = 0, il = meshes.length; i < il; i ++ ) { + + const mixer = this.objects.get( meshes[ i ] ).mixer; + + if ( mixer === undefined ) continue; + + for ( let j = 0; j < mixer._actions.length; j ++ ) { + + const clip = mixer._actions[ j ]._clip; + + if ( ! objects.has( clip ) ) { + + objects.set( clip, { + duration: clip.duration + } ); + + } + + max = Math.max( max, objects.get( clip ).duration ); + + } + + } + + 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, { + duration: clip.duration + } ); + + } + + max = Math.max( max, objects.get( clip ).duration ); + + } + + } + + } + + if ( audioManager !== null ) { + + max = Math.max( max, objects.get( audioManager ).duration ); + + } + + max += this.configuration.afterglow; + + // update the 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 ( mixer !== undefined ) { + + for ( let i = 0, il = mixer._actions.length; i < il; i ++ ) { + + mixer._actions[ i ]._clip.duration = max; + + } + + } + + } + + if ( audioManager !== null ) { + + audioManager.duration = max; + + } + + } + + // workaround + + _updatePropertyMixersBuffer( mesh ) { + + const mixer = this.objects.get( mesh ).mixer; + + const propertyMixers = mixer._bindings; + const accuIndex = mixer._accuIndex; + + 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; + + 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( 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; + + } + + 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 ); + + } + + } + + _restoreBones( mesh ) { + + const objects = this.objects.get( mesh ); + + const backupBones = objects.backupBones; + + if ( backupBones === undefined ) return; + + const bones = mesh.skeleton.bones; + + 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 ); + + } + + } + + // experimental + + _getMasterPhysics() { + + if ( this.masterPhysics !== null ) return this.masterPhysics; + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + const physics = this.meshes[ i ].physics; + + if ( physics !== undefined && physics !== null ) { + + this.masterPhysics = physics; + return this.masterPhysics; + + } + + } + + return null; + + } + + _updateSharedPhysics( delta ) { + + if ( this.meshes.length === 0 || ! this.enabled.physics || ! this.sharedPhysics ) return; + + const physics = this._getMasterPhysics(); + + 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(); + + } + + } + + physics.stepSimulation( delta ); + + for ( let i = 0, il = this.meshes.length; i < il; i ++ ) { + + const p = this.meshes[ i ].physics; + + if ( p !== null && p !== undefined ) { + + p.updateBones(); + + } + + } + + } + +} + +// Keep working quaternions for less GC +const _quaternions = []; +let _quaternionIndex = 0; + +function getQuaternion() { + + if ( _quaternionIndex >= _quaternions.length ) { + + _quaternions.push( new Quaternion() ); + + } + + return _quaternions[ _quaternionIndex ++ ]; + +} + +// Save rotation whose grant and IK are already applied +// used by grant children +const _grantResultMap = new Map(); + +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 ]; + + // 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 + */ + constructor( audio, params = {} ) { + + this.audio = audio; + + this.elapsedTime = 0.0; + this.currentTime = 0.0; + this.delayTime = params.delayTime !== undefined + ? params.delayTime : 0.0; + + this.audioDuration = this.audio.buffer.duration; + this.duration = this.audioDuration + this.delayTime; + + } + + /** + * @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 + + _shouldStartAudio() { + + if ( this.audio.isPlaying ) return false; + + while ( this.currentTime >= this.duration ) { + + this.currentTime -= this.duration; + + } + + if ( this.currentTime < this.delayTime ) return false; + + // 'duration' can be bigger than 'audioDuration + delayTime' because of sync configuration + if ( ( this.currentTime - this.delayTime ) > this.audioDuration ) return false; + + return true; + + } + + _shouldStopAudio() { + + return this.audio.isPlaying && + this.currentTime >= this.duration; + + } + +} + +const _q = new Quaternion(); + +/** + * 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; + + } + + /** + * Solve all the grant bones + * @return {GrantSolver} + */ + update() { + + const grants = this.grants; + + for ( let i = 0, il = grants.length; i < il; i ++ ) { + + this.updateOne( grants[ i ] ); + + } + + return this; + + } + + /** + * Solve a grant bone + * @param {Object} grant - grant parameter + * @return {GrantSolver} + */ + 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 ) { + + } + + // TODO: implement + if ( grant.affectRotation ) { + + } + + } else { + + // TODO: implement + if ( grant.affectPosition ) { + + } + + if ( grant.affectRotation ) { + + this.addGrantRotation( bone, parentBone.quaternion, grant.ratio ); + + } + + } + + return this; + + } + + 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/jsm/animation/MMDPhysics.js b/jsm/animation/MMDPhysics.js new file mode 100644 index 0000000..92d789f --- /dev/null +++ b/jsm/animation/MMDPhysics.js @@ -0,0 +1,1400 @@ +import { + Bone, + BoxGeometry, + Color, + CylinderGeometry, + Euler, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + Quaternion, + SphereGeometry, + Vector3 +} from 'three'; + +/** + * Dependencies + * - Ammo.js https://github.com/kripken/ammo.js + * + * MMDPhysics calculates physics with Ammo(Bullet based JavaScript Physics engine) + * for MMD model loaded by MMDLoader. + * + * TODO + * - Physics in Worker + */ + +/* global Ammo */ + +class MMDPhysics { + + /** + * @param {THREE.SkinnedMesh} mesh + * @param {Array} rigidBodyParams + * @param {Array} (optional) constraintParams + * @param {Object} params - (optional) + * @param {Number} params.unitStep - Default is 1 / 65. + * @param {Integer} params.maxStepNum - Default is 3. + * @param {Vector3} params.gravity - Default is ( 0, - 9.8 * 10, 0 ) + */ + constructor( mesh, rigidBodyParams, constraintParams = [], params = {} ) { + + if ( typeof Ammo === 'undefined' ) { + + throw new Error( 'THREE.MMDPhysics: Import ammo.js https://github.com/kripken/ammo.js' ); + + } + + this.manager = new ResourceManager(); + + this.mesh = mesh; + + /* + * I don't know why but 1/60 unitStep easily breaks models + * so I set it 1/65 so far. + * Don't set too small unitStep because + * the smaller unitStep can make the performance worse. + */ + this.unitStep = ( params.unitStep !== undefined ) ? params.unitStep : 1 / 65; + this.maxStepNum = ( params.maxStepNum !== undefined ) ? params.maxStepNum : 3; + this.gravity = new Vector3( 0, - 9.8 * 10, 0 ); + + if ( params.gravity !== undefined ) this.gravity.copy( params.gravity ); + + this.world = params.world !== undefined ? params.world : null; // experimental + + this.bodies = []; + this.constraints = []; + + this._init( mesh, rigidBodyParams, constraintParams ); + + } + + /** + * Advances Physics calculation and updates bones. + * + * @param {Number} delta - time in second + * @return {MMDPhysics} + */ + update( 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. + + let isNonDefaultScale = false; + + const position = manager.allocThreeVector3(); + const quaternion = manager.allocThreeQuaternion(); + const scale = manager.allocThreeVector3(); + + mesh.matrixWorld.decompose( position, quaternion, scale ); + + if ( scale.x !== 1 || scale.y !== 1 || scale.z !== 1 ) { + + isNonDefaultScale = true; + + } + + let parent; + + if ( isNonDefaultScale ) { + + parent = mesh.parent; + + if ( parent !== null ) mesh.parent = null; + + scale.copy( this.mesh.scale ); + + mesh.scale.set( 1, 1, 1 ); + mesh.updateMatrixWorld( true ); + + } + + // calculate physics and update bones + + this._updateRigidBodies(); + this._stepSimulation( delta ); + this._updateBones(); + + // restore mesh if converted above + + if ( isNonDefaultScale ) { + + if ( parent !== null ) mesh.parent = parent; + + mesh.scale.copy( scale ); + + } + + manager.freeThreeVector3( scale ); + manager.freeThreeQuaternion( quaternion ); + manager.freeThreeVector3( position ); + + 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(); + + } + + 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 ); + + } + + 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 ); + + return this; + + } + + /** + * Creates MMDPhysicsHelper + * + * @return {MMDPhysicsHelper} + */ + createHelper() { + + return new MMDPhysicsHelper( this.mesh, this ); + + } + + // private methods + + _init( 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) + + let parent = mesh.parent; + + if ( parent !== null ) parent = null; + + const currentPosition = manager.allocThreeVector3(); + const currentQuaternion = manager.allocThreeQuaternion(); + const currentScale = manager.allocThreeVector3(); + + 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.updateMatrixWorld( true ); + + if ( this.world === null ) { + + this.world = this._createWorld(); + this.setGravity( this.gravity ); + + } + + this._initRigidBodies( rigidBodyParams ); + this._initConstraints( constraintParams ); + + if ( parent !== null ) mesh.parent = parent; + + mesh.position.copy( currentPosition ); + mesh.quaternion.copy( currentQuaternion ); + mesh.scale.copy( currentScale ); + + mesh.updateMatrixWorld( true ); + + this.reset(); + + manager.freeThreeVector3( currentPosition ); + manager.freeThreeQuaternion( currentQuaternion ); + manager.freeThreeVector3( currentScale ); + + } + + _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; + + } + + _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 ) ); + + } + + } + + _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 ) ); + + } + + } + + _stepSimulation( delta ) { + + const unitStep = this.unitStep; + let stepTime = delta; + let maxStepNum = ( ( delta / unitStep ) | 0 ) + 1; + + if ( stepTime < unitStep ) { + + stepTime = unitStep; + maxStepNum = 1; + + } + + if ( maxStepNum > this.maxStepNum ) { + + maxStepNum = this.maxStepNum; + + } + + this.world.stepSimulation( stepTime, maxStepNum, unitStep ); + + } + + _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 = []; + this.threeQuaternions = []; + this.threeEulers = []; + + // for Ammo.js + this.transforms = []; + this.quaternions = []; + this.vector3s = []; + + } + + allocThreeVector3() { + + return ( this.threeVector3s.length > 0 ) + ? this.threeVector3s.pop() + : new Vector3(); + + } + + freeThreeVector3( v ) { + + this.threeVector3s.push( v ); + + } + + allocThreeMatrix4() { + + return ( this.threeMatrix4s.length > 0 ) + ? this.threeMatrix4s.pop() + : new Matrix4(); + + } + + freeThreeMatrix4( m ) { + + this.threeMatrix4s.push( m ); + + } + + allocThreeQuaternion() { + + return ( this.threeQuaternions.length > 0 ) + ? this.threeQuaternions.pop() + : new Quaternion(); + + } + + freeThreeQuaternion( q ) { + + this.threeQuaternions.push( q ); + + } + + allocThreeEuler() { + + return ( this.threeEulers.length > 0 ) + ? this.threeEulers.pop() + : new Euler(); + + } + + freeThreeEuler( e ) { + + this.threeEulers.push( e ); + + } + + allocTransform() { + + return ( this.transforms.length > 0 ) + ? this.transforms.pop() + : new Ammo.btTransform(); + + } + + freeTransform( t ) { + + this.transforms.push( t ); + + } + + allocQuaternion() { + + return ( this.quaternions.length > 0 ) + ? this.quaternions.pop() + : new Ammo.btQuaternion(); + + } + + freeQuaternion( q ) { + + this.quaternions.push( q ); + + } + + allocVector3() { + + return ( this.vector3s.length > 0 ) + ? this.vector3s.pop() + : new Ammo.btVector3(); + + } + + freeVector3( v ) { + + this.vector3s.push( v ); + + } + + setIdentity( t ) { + + t.setIdentity(); + + } + + getBasis( t ) { + + var q = this.allocQuaternion(); + t.getBasis().getRotation( q ); + return q; + + } + + getBasisAsMatrix3( t ) { + + var q = this.getBasis( t ); + var m = this.quaternionToMatrix3( q ); + this.freeQuaternion( q ); + return m; + + } + + getOrigin( t ) { + + return t.getOrigin(); + + } + + 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; + this.manager = manager; + + this.body = null; + this.bone = null; + this.boneOffsetForm = null; + this.boneOffsetFormInverse = null; + + this._init(); + + } + + /** + * Resets rigid body transform to the current bone's. + * + * @return {RigidBody} + */ + reset() { + + this._setTransformFromBone(); + return this; + + } + + /** + * 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 bone from the current ridid body's transform. + * + * @return {RidigBody} + */ + updateBone() { + + if ( this.params.type === 0 || this.params.boneIndex === - 1 ) { + + return this; + + } + + this._updateBoneRotation(); + + if ( this.params.type === 1 ) { + + this._updateBonePosition(); + + } + + this.bone.updateMatrixWorld( true ); + + if ( this.params.type === 2 ) { + + this._setPositionFromBone(); + + } + + return this; + + } + + // private methods + + _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 2: + return new Ammo.btCapsuleShape( p.width, p.height ); + + 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 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 ); + + } + + 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 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 body = new Ammo.btRigidBody( info ); + + 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 ); + + } + + body.setDamping( params.positionDamping, params.rotationDamping ); + body.setSleepingThresholds( 0, 0 ); + + this.world.addRigidBody( body, 1 << params.groupIndex, params.groupTarget ); + + 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 ); + + } + + _getBoneTransform() { + + const manager = this.manager; + const p = manager.allocThreeVector3(); + const q = manager.allocThreeQuaternion(); + const s = manager.allocThreeVector3(); + + this.bone.matrixWorld.decompose( p, q, s ); + + const tr = manager.allocTransform(); + manager.setOriginFromThreeVector3( tr, p ); + manager.setBasisFromThreeQuaternion( tr, q ); + + const form = manager.multiplyTransforms( tr, this.boneOffsetForm ); + + manager.freeTransform( tr ); + manager.freeThreeVector3( s ); + manager.freeThreeQuaternion( q ); + manager.freeThreeVector3( p ); + + return form; + + } + + _getWorldTransformForBone() { + + const manager = this.manager; + const tr = this.body.getCenterOfMassTransform(); + return manager.multiplyTransforms( tr, this.boneOffsetFormInverse ); + + } + + _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 ); + + manager.freeTransform( form ); + + } + + _setPositionFromBone() { + + const manager = this.manager; + const form = this._getBoneTransform(); + + 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 ); + + manager.freeTransform( tr ); + manager.freeTransform( form ); + + } + + _updateBoneRotation() { + + const manager = this.manager; + + const tr = this._getWorldTransformForBone(); + const q = manager.getBasis( tr ); + + 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 ); + + //this.bone.quaternion.multiply( thQ2 ); + + 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() ); + + manager.freeThreeQuaternion( thQ ); + manager.freeThreeQuaternion( thQ2 ); + manager.freeThreeQuaternion( thQ3 ); + + manager.freeQuaternion( q ); + manager.freeTransform( tr ); + + } + + _updateBonePosition() { + + const manager = this.manager; + + const tr = this._getWorldTransformForBone(); + + const thV = manager.allocThreeVector3(); + + const o = manager.getOrigin( tr ); + thV.set( o.x(), o.y(), o.z() ); + + if ( this.bone.parent ) { + + this.bone.parent.worldToLocal( thV ); + + } + + this.bone.position.copy( thV ); + + manager.freeThreeVector3( thV ); + + manager.freeTransform( tr ); + + } + +} + +// + +class Constraint { + + /** + * @param {THREE.SkinnedMesh} mesh + * @param {Ammo.btDiscreteDynamicsWorld} world + * @param {RigidBody} bodyA + * @param {RigidBody} bodyB + * @param {Object} params + * @param {ResourceManager} manager + */ + constructor( mesh, world, bodyA, bodyB, params, manager ) { + + this.mesh = mesh; + this.world = world; + this.bodyA = bodyA; + this.bodyB = bodyB; + this.params = params; + this.manager = manager; + + this.constraint = null; + + this._init(); + + } + + // private method + + _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 formA = manager.allocTransform(); + const formB = manager.allocTransform(); + + bodyA.body.getMotionState().getWorldTransform( formA ); + bodyB.body.getMotionState().getWorldTransform( formB ); + + const formInverseA = manager.inverseTransform( formA ); + const formInverseB = manager.inverseTransform( formB ); + + 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 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 ] ); + + 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.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 ++ ) { + + 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 ); + + } + +} + +// + +const _position = new Vector3(); +const _quaternion = new Quaternion(); +const _scale = new Vector3(); +const _matrixWorldInv = new Matrix4(); + +class MMDPhysicsHelper extends Object3D { + + /** + * Visualize Rigid bodies + * + * @param {THREE.SkinnedMesh} mesh + * @param {Physics} physics + */ + constructor( mesh, physics ) { + + super(); + + this.root = mesh; + this.physics = physics; + + this.matrix.copy( mesh.matrixWorld ); + this.matrixAutoUpdate = false; + + this.materials = []; + + this.materials.push( + new MeshBasicMaterial( { + color: new Color( 0xff8888 ), + wireframe: true, + depthTest: false, + depthWrite: false, + opacity: 0.25, + transparent: true + } ) + ); + + this.materials.push( + new MeshBasicMaterial( { + color: new Color( 0x88ff88 ), + wireframe: true, + depthTest: false, + depthWrite: false, + opacity: 0.25, + transparent: true + } ) + ); + + this.materials.push( + new MeshBasicMaterial( { + color: new Color( 0x8888ff ), + wireframe: true, + depthTest: false, + depthWrite: false, + opacity: 0.25, + transparent: true + } ) + ); + + this._init(); + + } + + /** + * Updates Rigid Bodies visualization. + */ + updateMatrixWorld( force ) { + + var mesh = this.root; + + if ( this.visible ) { + + var bodies = this.physics.bodies; + + _matrixWorldInv + .copy( mesh.matrixWorld ) + .decompose( _position, _quaternion, _scale ) + .compose( _position, _quaternion, _scale.set( 1, 1, 1 ) ) + .invert(); + + for ( var i = 0, il = bodies.length; i < il; i ++ ) { + + var body = bodies[ i ].body; + var child = this.children[ i ]; + + 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() ) + ); + + } + + } + + this.matrix + .copy( mesh.matrixWorld ) + .decompose( _position, _quaternion, _scale ) + .compose( _position, _quaternion, _scale.set( 1, 1, 1 ) ); + + super.updateMatrixWorld( force ); + + } + + // private method + + _init() { + + var bodies = this.physics.bodies; + + 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 createCapsuleGeometry( param.width, param.height, 16, 8 ); + + default: + return null; + + } + + } + + function createCapsuleGeometry( radius, cylinderHeight, segmentsRadius, segmentsHeight ) { + + var geometry = new CylinderGeometry( radius, radius, cylinderHeight, segmentsRadius, segmentsHeight, true ); + var upperSphere = new Mesh( new SphereGeometry( radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, 0, Math.PI / 2 ) ); + var lowerSphere = new Mesh( new SphereGeometry( radius, segmentsRadius, segmentsHeight, 0, Math.PI * 2, Math.PI / 2, Math.PI / 2 ) ); + + upperSphere.position.set( 0, cylinderHeight / 2, 0 ); + lowerSphere.position.set( 0, - cylinderHeight / 2, 0 ); + + upperSphere.updateMatrix(); + lowerSphere.updateMatrix(); + + geometry.merge( upperSphere.geometry, upperSphere.matrix ); + geometry.merge( lowerSphere.geometry, lowerSphere.matrix ); + + return geometry; + + } + + 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/jsm/cameras/CinematicCamera.js b/jsm/cameras/CinematicCamera.js new file mode 100644 index 0000000..47b94e7 --- /dev/null +++ b/jsm/cameras/CinematicCamera.js @@ -0,0 +1,209 @@ +import { + Mesh, + OrthographicCamera, + PerspectiveCamera, + PlaneGeometry, + Scene, + ShaderMaterial, + UniformsUtils, + WebGLRenderTarget +} from 'three'; + +import { BokehShader } from '../shaders/BokehShader2.js'; +import { BokehDepthShader } from '../shaders/BokehShader2.js'; + +class CinematicCamera extends PerspectiveCamera { + + constructor( fov, aspect, near, far ) { + + super( fov, aspect, near, far ); + + this.type = 'CinematicCamera'; + + this.postprocessing = { enabled: true }; + this.shaderSettings = { + rings: 3, + samples: 4 + }; + + const depthShader = BokehDepthShader; + + this.materialDepth = new ShaderMaterial( { + uniforms: depthShader.uniforms, + vertexShader: depthShader.vertexShader, + fragmentShader: depthShader.fragmentShader + } ); + + this.materialDepth.uniforms[ 'mNear' ].value = near; + this.materialDepth.uniforms[ 'mFar' ].value = far; + + // In case of cinematicCamera, having a default lens set is important + this.setLens(); + + this.initPostProcessing(); + + } + + // providing fnumber and coc(Circle of Confusion) as extra arguments + // In case of cinematicCamera, having a default lens set is important + // if fnumber and coc are not provided, cinematicCamera tries to act as a basic PerspectiveCamera + setLens( focalLength = 35, filmGauge = 35, fNumber = 8, coc = 0.019 ) { + + this.filmGauge = filmGauge; + + this.setFocalLength( focalLength ); + + this.fNumber = fNumber; + this.coc = coc; + + // fNumber is focalLength by aperture + this.aperture = focalLength / this.fNumber; + + // hyperFocal is required to calculate depthOfField when a lens tries to focus at a distance with given fNumber and focalLength + this.hyperFocal = ( focalLength * focalLength ) / ( this.aperture * this.coc ); + + } + + linearize( depth ) { + + const zfar = this.far; + const znear = this.near; + return - zfar * znear / ( depth * ( zfar - znear ) - zfar ); + + } + + smoothstep( near, far, depth ) { + + const x = this.saturate( ( depth - near ) / ( far - near ) ); + return x * x * ( 3 - 2 * x ); + + } + + saturate( x ) { + + return Math.max( 0, Math.min( 1, x ) ); + + } + + // function for focusing at a distance from the camera + focusAt( focusDistance = 20 ) { + + const focalLength = this.getFocalLength(); + + // distance from the camera (normal to frustrum) to focus on + this.focus = focusDistance; + + // the nearest point from the camera which is in focus (unused) + this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) ); + + // the farthest point from the camera which is in focus (unused) + this.farPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal - ( this.focus - focalLength ) ); + + // the gap or width of the space in which is everything is in focus (unused) + this.depthOfField = this.farPoint - this.nearPoint; + + // Considering minimum distance of focus for a standard lens (unused) + if ( this.depthOfField < 0 ) this.depthOfField = 0; + + this.sdistance = this.smoothstep( this.near, this.far, this.focus ); + + this.ldistance = this.linearize( 1 - this.sdistance ); + + this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance; + + } + + initPostProcessing() { + + if ( this.postprocessing.enabled ) { + + this.postprocessing.scene = new Scene(); + + this.postprocessing.camera = new OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 ); + + this.postprocessing.scene.add( this.postprocessing.camera ); + + this.postprocessing.rtTextureDepth = new WebGLRenderTarget( window.innerWidth, window.innerHeight ); + this.postprocessing.rtTextureColor = new WebGLRenderTarget( window.innerWidth, window.innerHeight ); + + const bokeh_shader = BokehShader; + + this.postprocessing.bokeh_uniforms = UniformsUtils.clone( bokeh_shader.uniforms ); + + this.postprocessing.bokeh_uniforms[ 'tColor' ].value = this.postprocessing.rtTextureColor.texture; + this.postprocessing.bokeh_uniforms[ 'tDepth' ].value = this.postprocessing.rtTextureDepth.texture; + + this.postprocessing.bokeh_uniforms[ 'manualdof' ].value = 0; + this.postprocessing.bokeh_uniforms[ 'shaderFocus' ].value = 0; + + this.postprocessing.bokeh_uniforms[ 'fstop' ].value = 2.8; + + this.postprocessing.bokeh_uniforms[ 'showFocus' ].value = 1; + + this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = 0.1; + + //console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value ); + + this.postprocessing.bokeh_uniforms[ 'znear' ].value = this.near; + this.postprocessing.bokeh_uniforms[ 'zfar' ].value = this.near; + + + this.postprocessing.bokeh_uniforms[ 'textureWidth' ].value = window.innerWidth; + + this.postprocessing.bokeh_uniforms[ 'textureHeight' ].value = window.innerHeight; + + this.postprocessing.materialBokeh = new ShaderMaterial( { + uniforms: this.postprocessing.bokeh_uniforms, + vertexShader: bokeh_shader.vertexShader, + fragmentShader: bokeh_shader.fragmentShader, + defines: { + RINGS: this.shaderSettings.rings, + SAMPLES: this.shaderSettings.samples, + DEPTH_PACKING: 1 + } + } ); + + this.postprocessing.quad = new Mesh( new PlaneGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh ); + this.postprocessing.quad.position.z = - 500; + this.postprocessing.scene.add( this.postprocessing.quad ); + + } + + } + + renderCinematic( scene, renderer ) { + + if ( this.postprocessing.enabled ) { + + const currentRenderTarget = renderer.getRenderTarget(); + + renderer.clear(); + + // Render scene into texture + + scene.overrideMaterial = null; + renderer.setRenderTarget( this.postprocessing.rtTextureColor ); + renderer.clear(); + renderer.render( scene, this ); + + // Render depth into texture + + scene.overrideMaterial = this.materialDepth; + renderer.setRenderTarget( this.postprocessing.rtTextureDepth ); + renderer.clear(); + renderer.render( scene, this ); + + // Render bokeh composite + + renderer.setRenderTarget( null ); + renderer.render( this.postprocessing.scene, this.postprocessing.camera ); + + renderer.setRenderTarget( currentRenderTarget ); + + } + + } + +} + +export { CinematicCamera }; diff --git a/jsm/capabilities/WebGL.js b/jsm/capabilities/WebGL.js new file mode 100644 index 0000000..08666fe --- /dev/null +++ b/jsm/capabilities/WebGL.js @@ -0,0 +1,91 @@ +class WebGL { + + static isWebGLAvailable() { + + try { + + const canvas = document.createElement( 'canvas' ); + return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) ); + + } catch ( e ) { + + return false; + + } + + } + + static isWebGL2Available() { + + try { + + const canvas = document.createElement( 'canvas' ); + return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) ); + + } catch ( e ) { + + return false; + + } + + } + + static getWebGLErrorMessage() { + + return this.getErrorMessage( 1 ); + + } + + static getWebGL2ErrorMessage() { + + return this.getErrorMessage( 2 ); + + } + + static getErrorMessage( version ) { + + const names = { + 1: 'WebGL', + 2: 'WebGL 2' + }; + + const contexts = { + 1: window.WebGLRenderingContext, + 2: window.WebGL2RenderingContext + }; + + let message = 'Your $0 does not seem to support $1'; + + const element = document.createElement( 'div' ); + element.id = 'webglmessage'; + 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'; + + if ( contexts[ version ] ) { + + message = message.replace( '$0', 'graphics card' ); + + } else { + + message = message.replace( '$0', 'browser' ); + + } + + message = message.replace( '$1', names[ version ] ); + + element.innerHTML = message; + + return element; + + } + +} + +export default WebGL; diff --git a/jsm/capabilities/WebGPU.js b/jsm/capabilities/WebGPU.js new file mode 100644 index 0000000..5351ea3 --- /dev/null +++ b/jsm/capabilities/WebGPU.js @@ -0,0 +1,33 @@ +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/jsm/controls/ArcballControls.js b/jsm/controls/ArcballControls.js new file mode 100644 index 0000000..e7e57c7 --- /dev/null +++ b/jsm/controls/ArcballControls.js @@ -0,0 +1,3201 @@ +import { + GridHelper, + EllipseCurve, + BufferGeometry, + Line, + LineBasicMaterial, + Raycaster, + Group, + Box3, + Sphere, + Quaternion, + Vector2, + Vector3, + Matrix4, + MathUtils, + EventDispatcher +} from 'three'; + +//trackball state +const STATE = { + + IDLE: Symbol(), + ROTATE: Symbol(), + PAN: Symbol(), + SCALE: Symbol(), + FOV: Symbol(), + FOCUS: Symbol(), + ZROTATE: Symbol(), + TOUCH_MULTI: Symbol(), + ANIMATION_FOCUS: Symbol(), + ANIMATION_ROTATE: Symbol() + +}; + +const INPUT = { + + NONE: Symbol(), + ONE_FINGER: Symbol(), + ONE_FINGER_SWITCHED: Symbol(), + TWO_FINGER: Symbol(), + MULT_FINGER: Symbol(), + CURSOR: Symbol() + +}; + +//cursor center coordinates +const _center = { + + x: 0, + y: 0 + +}; + +//transformation matrices for gizmos and camera +const _transformation = { + + camera: new Matrix4(), + gizmos: new Matrix4() + +}; + +//events +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +const _raycaster = new Raycaster(); +const _offset = new Vector3(); + +const _gizmoMatrixStateTemp = new Matrix4(); +const _cameraMatrixStateTemp = new Matrix4(); +const _scalePointTemp = new Vector3(); +/** + * + * @param {Camera} camera Virtual camera used in the scene + * @param {HTMLElement} domElement Renderer's dom element + * @param {Scene} scene The scene to be rendered + */ +class ArcballControls extends EventDispatcher { + + constructor( camera, domElement, scene = null ) { + + super(); + this.camera = null; + this.domElement = domElement; + this.scene = scene; + this.target = new Vector3(); + this._currentTarget = new Vector3(); + this.radiusFactor = 0.67; + + this.mouseActions = []; + this._mouseOp = null; + + + //global vectors and matrices that are used in some operations to avoid creating new objects every time (e.g. every time cursor moves) + this._v2_1 = new Vector2(); + this._v3_1 = new Vector3(); + this._v3_2 = new Vector3(); + + this._m4_1 = new Matrix4(); + this._m4_2 = new Matrix4(); + + this._quat = new Quaternion(); + + //transformation matrices + this._translationMatrix = new Matrix4(); //matrix for translation operation + this._rotationMatrix = new Matrix4(); //matrix for rotation operation + this._scaleMatrix = new Matrix4(); //matrix for scaling operation + + this._rotationAxis = new Vector3(); //axis for rotate operation + + + //camera state + this._cameraMatrixState = new Matrix4(); + this._cameraProjectionState = new Matrix4(); + + this._fovState = 1; + this._upState = new Vector3(); + this._zoomState = 1; + this._nearPos = 0; + this._farPos = 0; + + this._gizmoMatrixState = new Matrix4(); + + //initial values + this._up0 = new Vector3(); + this._zoom0 = 1; + this._fov0 = 0; + this._initialNear = 0; + this._nearPos0 = 0; + this._initialFar = 0; + this._farPos0 = 0; + this._cameraMatrixState0 = new Matrix4(); + this._gizmoMatrixState0 = new Matrix4(); + + //pointers array + this._button = - 1; + this._touchStart = []; + this._touchCurrent = []; + this._input = INPUT.NONE; + + //two fingers touch interaction + this._switchSensibility = 32; //minimum movement to be performed to fire single pan start after the second finger has been released + this._startFingerDistance = 0; //distance between two fingers + this._currentFingerDistance = 0; + this._startFingerRotation = 0; //amount of rotation performed with two fingers + this._currentFingerRotation = 0; + + //double tap + this._devPxRatio = 0; + this._downValid = true; + this._nclicks = 0; + this._downEvents = []; + this._downStart = 0; //pointerDown time + this._clickStart = 0; //first click time + this._maxDownTime = 250; + this._maxInterval = 300; + this._posThreshold = 24; + this._movementThreshold = 24; + + //cursor positions + this._currentCursorPosition = new Vector3(); + this._startCursorPosition = new Vector3(); + + //grid + this._grid = null; //grid to be visualized during pan operation + this._gridPosition = new Vector3(); + + //gizmos + this._gizmos = new Group(); + this._curvePts = 128; + + + //animations + this._timeStart = - 1; //initial time + this._animationId = - 1; + + //focus animation + this.focusAnimationTime = 500; //duration of focus animation in ms + + //rotate animation + this._timePrev = 0; //time at which previous rotate operation has been detected + this._timeCurrent = 0; //time at which current rotate operation has been detected + this._anglePrev = 0; //angle of previous rotation + this._angleCurrent = 0; //angle of current rotation + this._cursorPosPrev = new Vector3(); //cursor position when previous rotate operation has been detected + this._cursorPosCurr = new Vector3();//cursor position when current rotate operation has been detected + this._wPrev = 0; //angular velocity of the previous rotate operation + this._wCurr = 0; //angular velocity of the current rotate operation + + + //parameters + this.adjustNearFar = false; + this.scaleFactor = 1.1; //zoom/distance multiplier + this.dampingFactor = 25; + this.wMax = 20; //maximum angular velocity allowed + this.enableAnimations = true; //if animations should be performed + this.enableGrid = false; //if grid should be showed during pan operation + this.cursorZoom = false; //if wheel zoom should be cursor centered + this.minFov = 5; + this.maxFov = 90; + + this.enabled = true; + this.enablePan = true; + this.enableRotate = true; + this.enableZoom = true; + this.enableGizmos = true; + + this.minDistance = 0; + this.maxDistance = Infinity; + this.minZoom = 0; + this.maxZoom = Infinity; + + //trackball parameters + this._tbRadius = 1; + + //FSA + this._state = STATE.IDLE; + + this.setCamera( camera ); + + if ( this.scene != null ) { + + this.scene.add( this._gizmos ); + + } + + this.domElement.style.touchAction = 'none'; + this._devPxRatio = window.devicePixelRatio; + + this.initializeMouseActions(); + + this.domElement.addEventListener( 'contextmenu', this.onContextMenu ); + this.domElement.addEventListener( 'wheel', this.onWheel ); + this.domElement.addEventListener( 'pointerdown', this.onPointerDown ); + this.domElement.addEventListener( 'pointercancel', this.onPointerCancel ); + + window.addEventListener( 'resize', this.onWindowResize ); + + } + + //listeners + + onWindowResize = () => { + + const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3; + this._tbRadius = this.calculateTbRadius( this.camera ); + + const newRadius = this._tbRadius / scale; + const curve = new EllipseCurve( 0, 0, newRadius, newRadius ); + const points = curve.getPoints( this._curvePts ); + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + + for ( const gizmo in this._gizmos.children ) { + + this._gizmos.children[ gizmo ].geometry = curveGeometry; + + } + + this.dispatchEvent( _changeEvent ); + + }; + + onContextMenu = ( event ) => { + + if ( ! this.enabled ) { + + return; + + } + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + if ( this.mouseActions[ i ].mouse == 2 ) { + + //prevent only if button 2 is actually used + event.preventDefault(); + break; + + } + + } + + }; + + onPointerCancel = () => { + + this._touchStart.splice( 0, this._touchStart.length ); + this._touchCurrent.splice( 0, this._touchCurrent.length ); + this._input = INPUT.NONE; + + }; + + onPointerDown = ( event ) => { + + if ( event.button == 0 && event.isPrimary ) { + + this._downValid = true; + this._downEvents.push( event ); + this._downStart = performance.now(); + + } else { + + this._downValid = false; + + } + + if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) { + + this._touchStart.push( event ); + this._touchCurrent.push( event ); + + switch ( this._input ) { + + case INPUT.NONE: + + //singleStart + this._input = INPUT.ONE_FINGER; + this.onSinglePanStart( event, 'ROTATE' ); + + window.addEventListener( 'pointermove', this.onPointerMove ); + window.addEventListener( 'pointerup', this.onPointerUp ); + + break; + + case INPUT.ONE_FINGER: + case INPUT.ONE_FINGER_SWITCHED: + + //doubleStart + this._input = INPUT.TWO_FINGER; + + this.onRotateStart(); + this.onPinchStart(); + this.onDoublePanStart(); + + break; + + case INPUT.TWO_FINGER: + + //multipleStart + this._input = INPUT.MULT_FINGER; + this.onTriplePanStart( event ); + break; + + } + + } else if ( event.pointerType != 'touch' && this._input == INPUT.NONE ) { + + let modifier = null; + + if ( event.ctrlKey || event.metaKey ) { + + modifier = 'CTRL'; + + } else if ( event.shiftKey ) { + + modifier = 'SHIFT'; + + } + + this._mouseOp = this.getOpFromAction( event.button, modifier ); + if ( this._mouseOp != null ) { + + window.addEventListener( 'pointermove', this.onPointerMove ); + window.addEventListener( 'pointerup', this.onPointerUp ); + + //singleStart + this._input = INPUT.CURSOR; + this._button = event.button; + this.onSinglePanStart( event, this._mouseOp ); + + } + + } + + }; + + onPointerMove = ( event ) => { + + if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) { + + switch ( this._input ) { + + case INPUT.ONE_FINGER: + + //singleMove + this.updateTouchEvent( event ); + + this.onSinglePanMove( event, STATE.ROTATE ); + break; + + case INPUT.ONE_FINGER_SWITCHED: + + const movement = this.calculatePointersDistance( this._touchCurrent[ 0 ], event ) * this._devPxRatio; + + if ( movement >= this._switchSensibility ) { + + //singleMove + this._input = INPUT.ONE_FINGER; + this.updateTouchEvent( event ); + + this.onSinglePanStart( event, 'ROTATE' ); + break; + + } + + break; + + case INPUT.TWO_FINGER: + + //rotate/pan/pinchMove + this.updateTouchEvent( event ); + + this.onRotateMove(); + this.onPinchMove(); + this.onDoublePanMove(); + + break; + + case INPUT.MULT_FINGER: + + //multMove + this.updateTouchEvent( event ); + + this.onTriplePanMove( event ); + break; + + } + + } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) { + + let modifier = null; + + if ( event.ctrlKey || event.metaKey ) { + + modifier = 'CTRL'; + + } else if ( event.shiftKey ) { + + modifier = 'SHIFT'; + + } + + const mouseOpState = this.getOpStateFromAction( this._button, modifier ); + + if ( mouseOpState != null ) { + + this.onSinglePanMove( event, mouseOpState ); + + } + + } + + //checkDistance + if ( this._downValid ) { + + const movement = this.calculatePointersDistance( this._downEvents[ this._downEvents.length - 1 ], event ) * this._devPxRatio; + if ( movement > this._movementThreshold ) { + + this._downValid = false; + + } + + } + + }; + + onPointerUp = ( event ) => { + + if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) { + + const nTouch = this._touchCurrent.length; + + for ( let i = 0; i < nTouch; i ++ ) { + + if ( this._touchCurrent[ i ].pointerId == event.pointerId ) { + + this._touchCurrent.splice( i, 1 ); + this._touchStart.splice( i, 1 ); + break; + + } + + } + + switch ( this._input ) { + + case INPUT.ONE_FINGER: + case INPUT.ONE_FINGER_SWITCHED: + + //singleEnd + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + this._input = INPUT.NONE; + this.onSinglePanEnd(); + + break; + + case INPUT.TWO_FINGER: + + //doubleEnd + this.onDoublePanEnd( event ); + this.onPinchEnd( event ); + this.onRotateEnd( event ); + + //switching to singleStart + this._input = INPUT.ONE_FINGER_SWITCHED; + + break; + + case INPUT.MULT_FINGER: + + if ( this._touchCurrent.length == 0 ) { + + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + //multCancel + this._input = INPUT.NONE; + this.onTriplePanEnd(); + + } + + break; + + } + + } else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) { + + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + this._input = INPUT.NONE; + this.onSinglePanEnd(); + this._button = - 1; + + } + + if ( event.isPrimary ) { + + if ( this._downValid ) { + + const downTime = event.timeStamp - this._downEvents[ this._downEvents.length - 1 ].timeStamp; + + if ( downTime <= this._maxDownTime ) { + + if ( this._nclicks == 0 ) { + + //first valid click detected + this._nclicks = 1; + this._clickStart = performance.now(); + + } else { + + const clickInterval = event.timeStamp - this._clickStart; + const movement = this.calculatePointersDistance( this._downEvents[ 1 ], this._downEvents[ 0 ] ) * this._devPxRatio; + + if ( clickInterval <= this._maxInterval && movement <= this._posThreshold ) { + + //second valid click detected + //fire double tap and reset values + this._nclicks = 0; + this._downEvents.splice( 0, this._downEvents.length ); + this.onDoubleTap( event ); + + } else { + + //new 'first click' + this._nclicks = 1; + this._downEvents.shift(); + this._clickStart = performance.now(); + + } + + } + + } else { + + this._downValid = false; + this._nclicks = 0; + this._downEvents.splice( 0, this._downEvents.length ); + + } + + } else { + + this._nclicks = 0; + this._downEvents.splice( 0, this._downEvents.length ); + + } + + } + + }; + + onWheel = ( event ) => { + + if ( this.enabled && this.enableZoom ) { + + let modifier = null; + + if ( event.ctrlKey || event.metaKey ) { + + modifier = 'CTRL'; + + } else if ( event.shiftKey ) { + + modifier = 'SHIFT'; + + } + + const mouseOp = this.getOpFromAction( 'WHEEL', modifier ); + + if ( mouseOp != null ) { + + event.preventDefault(); + this.dispatchEvent( _startEvent ); + + const notchDeltaY = 125; //distance of one notch of mouse wheel + let sgn = event.deltaY / notchDeltaY; + + let size = 1; + + if ( sgn > 0 ) { + + size = 1 / this.scaleFactor; + + } else if ( sgn < 0 ) { + + size = this.scaleFactor; + + } + + switch ( mouseOp ) { + + case 'ZOOM': + + this.updateTbState( STATE.SCALE, true ); + + if ( sgn > 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, sgn ) ); + + } else if ( sgn < 0 ) { + + size = Math.pow( this.scaleFactor, - sgn ); + + } + + if ( this.cursorZoom && this.enablePan ) { + + let scalePoint; + + if ( this.camera.isOrthographicCamera ) { + + scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._gizmos.position ); + + } else if ( this.camera.isPerspectiveCamera ) { + + scalePoint = this.unprojectOnTbPlane( this.camera, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.camera.quaternion ).add( this._gizmos.position ); + + } + + this.applyTransformMatrix( this.scale( size, scalePoint ) ); + + } else { + + this.applyTransformMatrix( this.scale( size, this._gizmos.position ) ); + + } + + if ( this._grid != null ) { + + this.disposeGrid(); + this.drawGrid(); + + } + + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _endEvent ); + + break; + + case 'FOV': + + if ( this.camera.isPerspectiveCamera ) { + + this.updateTbState( STATE.FOV, true ); + + + //Vertigo effect + + // fov / 2 + // |\ + // | \ + // | \ + // x | \ + // | \ + // | \ + // | _ _ _\ + // y + + //check for iOs shift shortcut + if ( event.deltaX != 0 ) { + + sgn = event.deltaX / notchDeltaY; + + size = 1; + + if ( sgn > 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, sgn ) ); + + } else if ( sgn < 0 ) { + + size = Math.pow( this.scaleFactor, - sgn ); + + } + + } + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + const x = this._v3_1.distanceTo( this._gizmos.position ); + let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed + + //check min and max distance + xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance ); + + const y = x * Math.tan( MathUtils.DEG2RAD * this.camera.fov * 0.5 ); + + //calculate new fov + let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); + + //check min and max fov + if ( newFov > this.maxFov ) { + + newFov = this.maxFov; + + } else if ( newFov < this.minFov ) { + + newFov = this.minFov; + + } + + const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) ); + size = x / newDistance; + + this.setFov( newFov ); + this.applyTransformMatrix( this.scale( size, this._gizmos.position, false ) ); + + } + + if ( this._grid != null ) { + + this.disposeGrid(); + this.drawGrid(); + + } + + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _endEvent ); + + break; + + } + + } + + } + + }; + + onSinglePanStart = ( event, operation ) => { + + if ( this.enabled ) { + + this.dispatchEvent( _startEvent ); + + this.setCenter( event.clientX, event.clientY ); + + switch ( operation ) { + + case 'PAN': + + if ( ! this.enablePan ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + this.updateTbState( STATE.PAN, true ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + if ( this.enableGrid ) { + + this.drawGrid(); + this.dispatchEvent( _changeEvent ); + + } + + break; + + case 'ROTATE': + + if ( ! this.enableRotate ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + } + + this.updateTbState( STATE.ROTATE, true ); + this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + this.activateGizmos( true ); + if ( this.enableAnimations ) { + + this._timePrev = this._timeCurrent = performance.now(); + this._angleCurrent = this._anglePrev = 0; + this._cursorPosPrev.copy( this._startCursorPosition ); + this._cursorPosCurr.copy( this._cursorPosPrev ); + this._wCurr = 0; + this._wPrev = this._wCurr; + + } + + this.dispatchEvent( _changeEvent ); + break; + + case 'FOV': + + if ( ! this.camera.isPerspectiveCamera || ! this.enableZoom ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + this.updateTbState( STATE.FOV, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + break; + + case 'ZOOM': + + if ( ! this.enableZoom ) { + + return; + + } + + if ( this._animationId != - 1 ) { + + cancelAnimationFrame( this._animationId ); + this._animationId = - 1; + this._timeStart = - 1; + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + this.updateTbState( STATE.SCALE, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + break; + + } + + } + + }; + + onSinglePanMove = ( event, opState ) => { + + if ( this.enabled ) { + + const restart = opState != this._state; + this.setCenter( event.clientX, event.clientY ); + + switch ( opState ) { + + case STATE.PAN: + + if ( this.enablePan ) { + + if ( restart ) { + + //switch to pan operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + if ( this.enableGrid ) { + + this.drawGrid(); + + } + + this.activateGizmos( false ); + + } else { + + //continue with pan operation + this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) ); + this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition ) ); + + } + + } + + break; + + case STATE.ROTATE: + + if ( this.enableRotate ) { + + if ( restart ) { + + //switch to rotate operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( true ); + + } else { + + //continue with rotate operation + this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.camera, _center.x, _center.y, this.domElement, this._tbRadius ) ); + + const distance = this._startCursorPosition.distanceTo( this._currentCursorPosition ); + const angle = this._startCursorPosition.angleTo( this._currentCursorPosition ); + const amount = Math.max( distance / this._tbRadius, angle ); //effective rotation angle + + this.applyTransformMatrix( this.rotate( this.calculateRotationAxis( this._startCursorPosition, this._currentCursorPosition ), amount ) ); + + if ( this.enableAnimations ) { + + this._timePrev = this._timeCurrent; + this._timeCurrent = performance.now(); + this._anglePrev = this._angleCurrent; + this._angleCurrent = amount; + this._cursorPosPrev.copy( this._cursorPosCurr ); + this._cursorPosCurr.copy( this._currentCursorPosition ); + this._wPrev = this._wCurr; + this._wCurr = this.calculateAngularSpeed( this._anglePrev, this._angleCurrent, this._timePrev, this._timeCurrent ); + + } + + } + + } + + break; + + case STATE.SCALE: + + if ( this.enableZoom ) { + + if ( restart ) { + + //switch to zoom operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( false ); + + } else { + + //continue with zoom operation + const screenNotches = 8; //how many wheel notches corresponds to a full screen pan + this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + + const movement = this._currentCursorPosition.y - this._startCursorPosition.y; + + let size = 1; + + if ( movement < 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) ); + + } else if ( movement > 0 ) { + + size = Math.pow( this.scaleFactor, movement * screenNotches ); + + } + + this._v3_1.setFromMatrixPosition(this._gizmoMatrixState); + + this.applyTransformMatrix( this.scale( size, this._v3_1 ) ); + + } + + } + + break; + + case STATE.FOV: + + if ( this.enableZoom && this.camera.isPerspectiveCamera ) { + + if ( restart ) { + + //switch to fov operation + + this.dispatchEvent( _endEvent ); + this.dispatchEvent( _startEvent ); + + this.updateTbState( opState, true ); + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( false ); + + } else { + + //continue with fov operation + const screenNotches = 8; //how many wheel notches corresponds to a full screen pan + this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + + const movement = this._currentCursorPosition.y - this._startCursorPosition.y; + + let size = 1; + + if ( movement < 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) ); + + } else if ( movement > 0 ) { + + size = Math.pow( this.scaleFactor, movement * screenNotches ); + + } + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + const x = this._v3_1.distanceTo( this._gizmos.position ); + let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed + + //check min and max distance + xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance ); + + const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 ); + + //calculate new fov + let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); + + //check min and max fov + newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov ); + + const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) ); + size = x / newDistance; + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + + this.setFov( newFov ); + this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); + + //adjusting distance + _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x ); + this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); + + } + + } + + break; + + } + + this.dispatchEvent( _changeEvent ); + + } + + }; + + onSinglePanEnd = () => { + + if ( this._state == STATE.ROTATE ) { + + + if ( ! this.enableRotate ) { + + return; + + } + + if ( this.enableAnimations ) { + + //perform rotation animation + const deltaTime = ( performance.now() - this._timeCurrent ); + if ( deltaTime < 120 ) { + + const w = Math.abs( ( this._wPrev + this._wCurr ) / 2 ); + + const self = this; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.updateTbState( STATE.ANIMATION_ROTATE, true ); + const rotationAxis = self.calculateRotationAxis( self._cursorPosPrev, self._cursorPosCurr ); + + self.onRotationAnim( t, rotationAxis, Math.min( w, self.wMax ) ); + + } ); + + } else { + + //cursor has been standing still for over 120 ms since last movement + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + } else { + + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + } else if ( this._state == STATE.PAN || this._state == STATE.IDLE ) { + + this.updateTbState( STATE.IDLE, false ); + + if ( this.enableGrid ) { + + this.disposeGrid(); + + } + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + + } + + this.dispatchEvent( _endEvent ); + + }; + + onDoubleTap = ( event ) => { + + if ( this.enabled && this.enablePan && this.scene != null ) { + + this.dispatchEvent( _startEvent ); + + this.setCenter( event.clientX, event.clientY ); + const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.camera ); + + if ( hitP != null && this.enableAnimations ) { + + const self = this; + if ( this._animationId != - 1 ) { + + window.cancelAnimationFrame( this._animationId ); + + } + + this._timeStart = - 1; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.updateTbState( STATE.ANIMATION_FOCUS, true ); + self.onFocusAnim( t, hitP, self._cameraMatrixState, self._gizmoMatrixState ); + + } ); + + } else if ( hitP != null && ! this.enableAnimations ) { + + this.updateTbState( STATE.FOCUS, true ); + this.focus( hitP, this.scaleFactor ); + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _changeEvent ); + + } + + } + + this.dispatchEvent( _endEvent ); + + }; + + onDoublePanStart = () => { + + if ( this.enabled && this.enablePan ) { + + this.dispatchEvent( _startEvent ); + + this.updateTbState( STATE.PAN, true ); + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + this._startCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + this.activateGizmos( false ); + + } + + }; + + onDoublePanMove = () => { + + if ( this.enabled && this.enablePan ) { + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + + if ( this._state != STATE.PAN ) { + + this.updateTbState( STATE.PAN, true ); + this._startCursorPosition.copy( this._currentCursorPosition ); + + } + + this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement, true ) ); + this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition, true ) ); + this.dispatchEvent( _changeEvent ); + + } + + }; + + onDoublePanEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _endEvent ); + + }; + + + onRotateStart = () => { + + if ( this.enabled && this.enableRotate ) { + + this.dispatchEvent( _startEvent ); + + this.updateTbState( STATE.ZROTATE, true ); + + //this._startFingerRotation = event.rotation; + + this._startFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] ); + this._currentFingerRotation = this._startFingerRotation; + + this.camera.getWorldDirection( this._rotationAxis ); //rotation axis + + if ( ! this.enablePan && ! this.enableZoom ) { + + this.activateGizmos( true ); + + } + + } + + }; + + onRotateMove = () => { + + if ( this.enabled && this.enableRotate ) { + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + let rotationPoint; + + if ( this._state != STATE.ZROTATE ) { + + this.updateTbState( STATE.ZROTATE, true ); + this._startFingerRotation = this._currentFingerRotation; + + } + + //this._currentFingerRotation = event.rotation; + this._currentFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] ); + + if ( ! this.enablePan ) { + + rotationPoint = new Vector3().setFromMatrixPosition( this._gizmoMatrixState ); + + } else { + + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + rotationPoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ).applyQuaternion( this.camera.quaternion ).multiplyScalar( 1 / this.camera.zoom ).add( this._v3_2 ); + + } + + const amount = MathUtils.DEG2RAD * ( this._startFingerRotation - this._currentFingerRotation ); + + this.applyTransformMatrix( this.zRotate( rotationPoint, amount ) ); + this.dispatchEvent( _changeEvent ); + + } + + }; + + onRotateEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + this.dispatchEvent( _endEvent ); + + }; + + onPinchStart = () => { + + if ( this.enabled && this.enableZoom ) { + + this.dispatchEvent( _startEvent ); + this.updateTbState( STATE.SCALE, true ); + + this._startFingerDistance = this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ); + this._currentFingerDistance = this._startFingerDistance; + + this.activateGizmos( false ); + + } + + }; + + onPinchMove = () => { + + if ( this.enabled && this.enableZoom ) { + + this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 ); + const minDistance = 12; //minimum distance between fingers (in css pixels) + + if ( this._state != STATE.SCALE ) { + + this._startFingerDistance = this._currentFingerDistance; + this.updateTbState( STATE.SCALE, true ); + + } + + this._currentFingerDistance = Math.max( this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ), minDistance * this._devPxRatio ); + const amount = this._currentFingerDistance / this._startFingerDistance; + + let scalePoint; + + if ( ! this.enablePan ) { + + scalePoint = this._gizmos.position; + + } else { + + if ( this.camera.isOrthographicCamera ) { + + 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.isPerspectiveCamera ) { + + scalePoint = this.unprojectOnTbPlane( this.camera, _center.x, _center.y, this.domElement ) + .applyQuaternion( this.camera.quaternion ) + .add( this._gizmos.position ); + + } + + } + + this.applyTransformMatrix( this.scale( amount, scalePoint ) ); + this.dispatchEvent( _changeEvent ); + + } + + }; + + onPinchEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _endEvent ); + + }; + + onTriplePanStart = () => { + + if ( this.enabled && this.enableZoom ) { + + this.dispatchEvent( _startEvent ); + + this.updateTbState( STATE.SCALE, true ); + + //const center = event.center; + let clientX = 0; + let clientY = 0; + const nFingers = this._touchCurrent.length; + + for ( let i = 0; i < nFingers; i ++ ) { + + clientX += this._touchCurrent[ i ].clientX; + clientY += this._touchCurrent[ i ].clientY; + + } + + this.setCenter( clientX / nFingers, clientY / nFingers ); + + this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + this._currentCursorPosition.copy( this._startCursorPosition ); + + } + + }; + + onTriplePanMove = () => { + + if ( this.enabled && this.enableZoom ) { + + // fov / 2 + // |\ + // | \ + // | \ + // x | \ + // | \ + // | \ + // | _ _ _\ + // y + + //const center = event.center; + let clientX = 0; + let clientY = 0; + const nFingers = this._touchCurrent.length; + + for ( let i = 0; i < nFingers; i ++ ) { + + clientX += this._touchCurrent[ i ].clientX; + clientY += this._touchCurrent[ i ].clientY; + + } + + this.setCenter( clientX / nFingers, clientY / nFingers ); + + const screenNotches = 8; //how many wheel notches corresponds to a full screen pan + this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 ); + + const movement = this._currentCursorPosition.y - this._startCursorPosition.y; + + let size = 1; + + if ( movement < 0 ) { + + size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) ); + + } else if ( movement > 0 ) { + + size = Math.pow( this.scaleFactor, movement * screenNotches ); + + } + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + const x = this._v3_1.distanceTo( this._gizmos.position ); + let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed + + //check min and max distance + xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance ); + + const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 ); + + //calculate new fov + let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 ); + + //check min and max fov + newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov ); + + const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) ); + size = x / newDistance; + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + + this.setFov( newFov ); + this.applyTransformMatrix( this.scale( size, this._v3_2, false ) ); + + //adjusting distance + _offset.copy( this._gizmos.position ).sub( this.camera.position ).normalize().multiplyScalar( newDistance / x ); + this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); + + this.dispatchEvent( _changeEvent ); + + } + + }; + + onTriplePanEnd = () => { + + this.updateTbState( STATE.IDLE, false ); + this.dispatchEvent( _endEvent ); + //this.dispatchEvent( _changeEvent ); + + }; + + /** + * Set _center's x/y coordinates + * @param {Number} clientX + * @param {Number} clientY + */ + setCenter = ( clientX, clientY ) => { + + _center.x = clientX; + _center.y = clientY; + + }; + + /** + * Set default mouse actions + */ + initializeMouseActions = () => { + + this.setMouseAction( 'PAN', 0, 'CTRL' ); + this.setMouseAction( 'PAN', 2 ); + + this.setMouseAction( 'ROTATE', 0 ); + + this.setMouseAction( 'ZOOM', 'WHEEL' ); + this.setMouseAction( 'ZOOM', 1 ); + + this.setMouseAction( 'FOV', 'WHEEL', 'SHIFT' ); + this.setMouseAction( 'FOV', 1, 'SHIFT' ); + + + }; + + /** + * Compare two mouse actions + * @param {Object} action1 + * @param {Object} action2 + * @returns {Boolean} True if action1 and action 2 are the same mouse action, false otherwise + */ + compareMouseAction = ( action1, action2 ) => { + + if ( action1.operation == action2.operation ) { + + if ( action1.mouse == action2.mouse && action1.key == action2.key ) { + + return true; + + } else { + + return false; + + } + + } else { + + return false; + + } + + }; + + /** + * Set a new mouse action by specifying the operation to be performed and a mouse/key combination. In case of conflict, replaces the existing one + * @param {String} operation The operation to be performed ('PAN', 'ROTATE', 'ZOOM', 'FOV) + * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches + * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed + * @returns {Boolean} True if the mouse action has been successfully added, false otherwise + */ + setMouseAction = ( operation, mouse, key = null ) => { + + const operationInput = [ 'PAN', 'ROTATE', 'ZOOM', 'FOV' ]; + const mouseInput = [ 0, 1, 2, 'WHEEL' ]; + const keyInput = [ 'CTRL', 'SHIFT', null ]; + let state; + + if ( ! operationInput.includes( operation ) || ! mouseInput.includes( mouse ) || ! keyInput.includes( key ) ) { + + //invalid parameters + return false; + + } + + if ( mouse == 'WHEEL' ) { + + if ( operation != 'ZOOM' && operation != 'FOV' ) { + + //cannot associate 2D operation to 1D input + return false; + + } + + } + + switch ( operation ) { + + case 'PAN': + + state = STATE.PAN; + break; + + case 'ROTATE': + + state = STATE.ROTATE; + break; + + case 'ZOOM': + + state = STATE.SCALE; + break; + + case 'FOV': + + state = STATE.FOV; + break; + + } + + const action = { + + operation: operation, + mouse: mouse, + key: key, + state: state + + }; + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + if ( this.mouseActions[ i ].mouse == action.mouse && this.mouseActions[ i ].key == action.key ) { + + this.mouseActions.splice( i, 1, action ); + return true; + + } + + } + + this.mouseActions.push( action ); + return true; + + }; + + /** + * Remove a mouse action by specifying its mouse/key combination + * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches + * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed + * @returns {Boolean} True if the operation has been succesfully removed, false otherwise + */ + unsetMouseAction = ( mouse, key = null ) => { + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + if ( this.mouseActions[ i ].mouse == mouse && this.mouseActions[ i ].key == key ) { + + this.mouseActions.splice( i, 1 ); + return true; + + } + + } + + return false; + + }; + + /** + * Return the operation associated to a mouse/keyboard combination + * @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches + * @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed + * @returns The operation if it has been found, null otherwise + */ + getOpFromAction = ( mouse, key ) => { + + let action; + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == key ) { + + return action.operation; + + } + + } + + if ( key != null ) { + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == null ) { + + return action.operation; + + } + + } + + } + + return null; + + }; + + /** + * Get the operation associated to mouse and key combination and returns the corresponding FSA state + * @param {Number} mouse Mouse button + * @param {String} key Keyboard modifier + * @returns The FSA state obtained from the operation associated to mouse/keyboard combination + */ + getOpStateFromAction = ( mouse, key ) => { + + let action; + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == key ) { + + return action.state; + + } + + } + + if ( key != null ) { + + for ( let i = 0; i < this.mouseActions.length; i ++ ) { + + action = this.mouseActions[ i ]; + if ( action.mouse == mouse && action.key == null ) { + + return action.state; + + } + + } + + } + + return null; + + }; + + /** + * Calculate the angle between two pointers + * @param {PointerEvent} p1 + * @param {PointerEvent} p2 + * @returns {Number} The angle between two pointers in degrees + */ + getAngle = ( p1, p2 ) => { + + return Math.atan2( p2.clientY - p1.clientY, p2.clientX - p1.clientX ) * 180 / Math.PI; + + }; + + /** + * Update a PointerEvent inside current pointerevents array + * @param {PointerEvent} event + */ + updateTouchEvent = ( event ) => { + + for ( let i = 0; i < this._touchCurrent.length; i ++ ) { + + if ( this._touchCurrent[ i ].pointerId == event.pointerId ) { + + this._touchCurrent.splice( i, 1, event ); + break; + + } + + } + + }; + + /** + * Apply a transformation matrix, to the camera and gizmos + * @param {Object} transformation Object containing matrices to apply to camera and gizmos + */ + applyTransformMatrix( transformation ) { + + if ( transformation.camera != null ) { + + this._m4_1.copy( this._cameraMatrixState ).premultiply( transformation.camera ); + this._m4_1.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + this.camera.updateMatrix(); + + //update camera up vector + if ( this._state == STATE.ROTATE || this._state == STATE.ZROTATE || this._state == STATE.ANIMATION_ROTATE ) { + + this.camera.up.copy( this._upState ).applyQuaternion( this.camera.quaternion ); + + } + + } + + if ( transformation.gizmos != null ) { + + this._m4_1.copy( this._gizmoMatrixState ).premultiply( transformation.gizmos ); + this._m4_1.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + this._gizmos.updateMatrix(); + + } + + if ( this._state == STATE.SCALE || this._state == STATE.FOCUS || this._state == STATE.ANIMATION_FOCUS ) { + + this._tbRadius = this.calculateTbRadius( this.camera ); + + if ( this.adjustNearFar ) { + + const cameraDistance = this.camera.position.distanceTo( this._gizmos.position ); + + const bb = new Box3(); + bb.setFromObject( this._gizmos ); + const sphere = new Sphere(); + bb.getBoundingSphere( sphere ); + + const adjustedNearPosition = Math.max( this._nearPos0, sphere.radius + sphere.center.length() ); + const regularNearPosition = cameraDistance - this._initialNear; + + const minNearPos = Math.min( adjustedNearPosition, regularNearPosition ); + this.camera.near = cameraDistance - minNearPos; + + + const adjustedFarPosition = Math.min( this._farPos0, - sphere.radius + sphere.center.length() ); + const regularFarPosition = cameraDistance - this._initialFar; + + const minFarPos = Math.min( adjustedFarPosition, regularFarPosition ); + this.camera.far = cameraDistance - minFarPos; + + this.camera.updateProjectionMatrix(); + + } else { + + let update = false; + + if ( this.camera.near != this._initialNear ) { + + this.camera.near = this._initialNear; + update = true; + + } + + if ( this.camera.far != this._initialFar ) { + + this.camera.far = this._initialFar; + update = true; + + } + + if ( update ) { + + this.camera.updateProjectionMatrix(); + + } + + } + + } + + } + + /** + * Calculate the angular speed + * @param {Number} p0 Position at t0 + * @param {Number} p1 Position at t1 + * @param {Number} t0 Initial time in milliseconds + * @param {Number} t1 Ending time in milliseconds + */ + calculateAngularSpeed = ( p0, p1, t0, t1 ) => { + + const s = p1 - p0; + const t = ( t1 - t0 ) / 1000; + if ( t == 0 ) { + + return 0; + + } + + return s / t; + + }; + + /** + * Calculate the distance between two pointers + * @param {PointerEvent} p0 The first pointer + * @param {PointerEvent} p1 The second pointer + * @returns {number} The distance between the two pointers + */ + calculatePointersDistance = ( p0, p1 ) => { + + return Math.sqrt( Math.pow( p1.clientX - p0.clientX, 2 ) + Math.pow( p1.clientY - p0.clientY, 2 ) ); + + }; + + /** + * Calculate the rotation axis as the vector perpendicular between two vectors + * @param {Vector3} vec1 The first vector + * @param {Vector3} vec2 The second vector + * @returns {Vector3} The normalized rotation axis + */ + calculateRotationAxis = ( vec1, vec2 ) => { + + this._rotationMatrix.extractRotation( this._cameraMatrixState ); + this._quat.setFromRotationMatrix( this._rotationMatrix ); + + this._rotationAxis.crossVectors( vec1, vec2 ).applyQuaternion( this._quat ); + return this._rotationAxis.normalize().clone(); + + }; + + /** + * Calculate the trackball radius so that gizmo's diamater will be 2/3 of the minimum side of the camera frustum + * @param {Camera} camera + * @returns {Number} The trackball radius + */ + calculateTbRadius = ( camera ) => { + + const distance = camera.position.distanceTo( this._gizmos.position ); + + if ( camera.type == '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 * this.radiusFactor; + + } else if ( camera.type == 'OrthographicCamera' ) { + + return Math.min( camera.top, camera.right ) * this.radiusFactor; + + } + + }; + + /** + * Focus operation consist of positioning the point of interest in front of the camera and a slightly zoom in + * @param {Vector3} point The point of interest + * @param {Number} size Scale factor + * @param {Number} amount Amount of operation to be completed (used for focus animations, default is complete full operation) + */ + focus = ( point, size, amount = 1 ) => { + + //move center of camera (along with gizmos) towards point of interest + _offset.copy( point ).sub( this._gizmos.position ).multiplyScalar( amount ); + this._translationMatrix.makeTranslation( _offset.x, _offset.y, _offset.z ); + + _gizmoMatrixStateTemp.copy( this._gizmoMatrixState ); + this._gizmoMatrixState.premultiply( this._translationMatrix ); + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + _cameraMatrixStateTemp.copy( this._cameraMatrixState ); + this._cameraMatrixState.premultiply( this._translationMatrix ); + this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + + //apply zoom + if ( this.enableZoom ) { + + this.applyTransformMatrix( this.scale( size, this._gizmos.position ) ); + + } + + this._gizmoMatrixState.copy( _gizmoMatrixStateTemp ); + this._cameraMatrixState.copy( _cameraMatrixStateTemp ); + + }; + + /** + * Draw a grid and add it to the scene + */ + drawGrid = () => { + + if ( this.scene != null ) { + + const color = 0x888888; + const multiplier = 3; + let size, divisions, maxLength, tick; + + if ( this.camera.isOrthographicCamera ) { + + const width = this.camera.right - this.camera.left; + const height = this.camera.bottom - this.camera.top; + + maxLength = Math.max( width, height ); + tick = maxLength / 20; + + size = maxLength / this.camera.zoom * multiplier; + divisions = size / tick * this.camera.zoom; + + } else if ( this.camera.isPerspectiveCamera ) { + + 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 ) ); + + maxLength = Math.tan( Math.max( halfFovV, halfFovH ) ) * distance * 2; + tick = maxLength / 20; + + size = maxLength * multiplier; + divisions = size / tick; + + } + + if ( this._grid == null ) { + + this._grid = new GridHelper( size, divisions, color, color ); + this._grid.position.copy( this._gizmos.position ); + this._gridPosition.copy( this._grid.position ); + this._grid.quaternion.copy( this.camera.quaternion ); + this._grid.rotateX( Math.PI * 0.5 ); + + this.scene.add( this._grid ); + + } + + } + + }; + + /** + * Remove all listeners, stop animations and clean scene + */ + dispose = () => { + + if ( this._animationId != - 1 ) { + + window.cancelAnimationFrame( this._animationId ); + + } + + this.domElement.removeEventListener( 'pointerdown', this.onPointerDown ); + this.domElement.removeEventListener( 'pointercancel', this.onPointerCancel ); + this.domElement.removeEventListener( 'wheel', this.onWheel ); + this.domElement.removeEventListener( 'contextmenu', this.onContextMenu ); + + window.removeEventListener( 'pointermove', this.onPointerMove ); + window.removeEventListener( 'pointerup', this.onPointerUp ); + + window.removeEventListener( 'resize', this.onWindowResize ); + + if ( this.scene !== null ) this.scene.remove( this._gizmos ); + this.disposeGrid(); + + }; + + /** + * remove the grid from the scene + */ + disposeGrid = () => { + + if ( this._grid != null && this.scene != null ) { + + this.scene.remove( this._grid ); + this._grid = null; + + } + + }; + + /** + * Compute the easing out cubic function for ease out effect in animation + * @param {Number} t The absolute progress of the animation in the bound of 0 (beginning of the) and 1 (ending of animation) + * @returns {Number} Result of easing out cubic at time t + */ + easeOutCubic = ( t ) => { + + return 1 - Math.pow( 1 - t, 3 ); + + }; + + /** + * Make rotation gizmos more or less visible + * @param {Boolean} isActive If true, make gizmos more visible + */ + activateGizmos = ( isActive ) => { + + const gizmoX = this._gizmos.children[ 0 ]; + const gizmoY = this._gizmos.children[ 1 ]; + const gizmoZ = this._gizmos.children[ 2 ]; + + if ( isActive ) { + + gizmoX.material.setValues( { opacity: 1 } ); + gizmoY.material.setValues( { opacity: 1 } ); + gizmoZ.material.setValues( { opacity: 1 } ); + + } else { + + gizmoX.material.setValues( { opacity: 0.6 } ); + gizmoY.material.setValues( { opacity: 0.6 } ); + gizmoZ.material.setValues( { opacity: 0.6 } ); + + } + + }; + + /** + * Calculate the cursor position in NDC + * @param {number} x Cursor horizontal coordinate within the canvas + * @param {number} y Cursor vertical coordinate within the canvas + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @returns {Vector2} Cursor normalized position inside the canvas + */ + getCursorNDC = ( cursorX, cursorY, canvas ) => { + + const canvasRect = canvas.getBoundingClientRect(); + this._v2_1.setX( ( ( cursorX - canvasRect.left ) / canvasRect.width ) * 2 - 1 ); + this._v2_1.setY( ( ( canvasRect.bottom - cursorY ) / canvasRect.height ) * 2 - 1 ); + return this._v2_1.clone(); + + }; + + /** + * Calculate the cursor position inside the canvas x/y coordinates with the origin being in the center of the canvas + * @param {Number} x Cursor horizontal coordinate within the canvas + * @param {Number} y Cursor vertical coordinate within the canvas + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @returns {Vector2} Cursor position inside the canvas + */ + getCursorPosition = ( cursorX, cursorY, canvas ) => { + + this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); + this._v2_1.x *= ( this.camera.right - this.camera.left ) * 0.5; + this._v2_1.y *= ( this.camera.top - this.camera.bottom ) * 0.5; + return this._v2_1.clone(); + + }; + + /** + * Set the camera to be controlled + * @param {Camera} camera The virtual camera to be controlled + */ + setCamera = ( camera ) => { + + camera.lookAt( this.target ); + camera.updateMatrix(); + + //setting state + if ( camera.type == 'PerspectiveCamera' ) { + + this._fov0 = camera.fov; + this._fovState = camera.fov; + + } + + this._cameraMatrixState0.copy( camera.matrix ); + this._cameraMatrixState.copy( this._cameraMatrixState0 ); + this._cameraProjectionState.copy( camera.projectionMatrix ); + this._zoom0 = camera.zoom; + this._zoomState = this._zoom0; + + this._initialNear = camera.near; + this._nearPos0 = camera.position.distanceTo( this.target ) - camera.near; + this._nearPos = this._initialNear; + + this._initialFar = camera.far; + this._farPos0 = camera.position.distanceTo( this.target ) - camera.far; + this._farPos = this._initialFar; + + this._up0.copy( camera.up ); + this._upState.copy( camera.up ); + + this.camera = camera; + this.camera.updateProjectionMatrix(); + + //making gizmos + this._tbRadius = this.calculateTbRadius( camera ); + this.makeGizmos( this.target, this._tbRadius ); + + }; + + /** + * Set gizmos visibility + * @param {Boolean} value Value of gizmos visibility + */ + setGizmosVisible( value ) { + + this._gizmos.visible = value; + this.dispatchEvent( _changeEvent ); + + } + + /** + * Set gizmos radius factor and redraws gizmos + * @param {Float} value Value of radius factor + */ + setTbRadius( value ) { + + this.radiusFactor = value; + this._tbRadius = this.calculateTbRadius( this.camera ); + + const curve = new EllipseCurve( 0, 0, this._tbRadius, this._tbRadius ); + const points = curve.getPoints( this._curvePts ); + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + + for ( const gizmo in this._gizmos.children ) { + + this._gizmos.children[ gizmo ].geometry = curveGeometry; + + } + + this.dispatchEvent( _changeEvent ); + + } + + /** + * Creates the rotation gizmos matching trackball center and radius + * @param {Vector3} tbCenter The trackball center + * @param {number} tbRadius The trackball radius + */ + makeGizmos = ( tbCenter, tbRadius ) => { + + const curve = new EllipseCurve( 0, 0, tbRadius, tbRadius ); + const points = curve.getPoints( this._curvePts ); + + //geometry + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + //material + const curveMaterialX = new LineBasicMaterial( { color: 0xff8080, fog: false, transparent: true, opacity: 0.6 } ); + const curveMaterialY = new LineBasicMaterial( { color: 0x80ff80, fog: false, transparent: true, opacity: 0.6 } ); + const curveMaterialZ = new LineBasicMaterial( { color: 0x8080ff, fog: false, transparent: true, opacity: 0.6 } ); + + //line + const gizmoX = new Line( curveGeometry, curveMaterialX ); + const gizmoY = new Line( curveGeometry, curveMaterialY ); + const gizmoZ = new Line( curveGeometry, curveMaterialZ ); + + const rotation = Math.PI * 0.5; + gizmoX.rotation.x = rotation; + gizmoY.rotation.y = rotation; + + + //setting state + this._gizmoMatrixState0.identity().setPosition( tbCenter ); + this._gizmoMatrixState.copy( this._gizmoMatrixState0 ); + + if ( this.camera.zoom != 1 ) { + + //adapt gizmos size to camera zoom + const size = 1 / this.camera.zoom; + this._scaleMatrix.makeScale( size, size, size ); + this._translationMatrix.makeTranslation( - tbCenter.x, - tbCenter.y, - tbCenter.z ); + + this._gizmoMatrixState.premultiply( this._translationMatrix ).premultiply( this._scaleMatrix ); + this._translationMatrix.makeTranslation( tbCenter.x, tbCenter.y, tbCenter.z ); + this._gizmoMatrixState.premultiply( this._translationMatrix ); + + } + + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + this._gizmos.clear(); + + this._gizmos.add( gizmoX ); + this._gizmos.add( gizmoY ); + this._gizmos.add( gizmoZ ); + + }; + + /** + * Perform animation for focus operation + * @param {Number} time Instant in which this function is called as performance.now() + * @param {Vector3} point Point of interest for focus operation + * @param {Matrix4} cameraMatrix Camera matrix + * @param {Matrix4} gizmoMatrix Gizmos matrix + */ + onFocusAnim = ( time, point, cameraMatrix, gizmoMatrix ) => { + + if ( this._timeStart == - 1 ) { + + //animation start + this._timeStart = time; + + } + + if ( this._state == STATE.ANIMATION_FOCUS ) { + + const deltaTime = time - this._timeStart; + const animTime = deltaTime / this.focusAnimationTime; + + this._gizmoMatrixState.copy( gizmoMatrix ); + + if ( animTime >= 1 ) { + + //animation end + + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + this.focus( point, this.scaleFactor ); + + this._timeStart = - 1; + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + + this.dispatchEvent( _changeEvent ); + + } else { + + const amount = this.easeOutCubic( animTime ); + const size = ( ( 1 - amount ) + ( this.scaleFactor * amount ) ); + + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + this.focus( point, size, amount ); + + this.dispatchEvent( _changeEvent ); + const self = this; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.onFocusAnim( t, point, cameraMatrix, gizmoMatrix.clone() ); + + } ); + + } + + } else { + + //interrupt animation + + this._animationId = - 1; + this._timeStart = - 1; + + } + + }; + + /** + * Perform animation for rotation operation + * @param {Number} time Instant in which this function is called as performance.now() + * @param {Vector3} rotationAxis Rotation axis + * @param {number} w0 Initial angular velocity + */ + onRotationAnim = ( time, rotationAxis, w0 ) => { + + if ( this._timeStart == - 1 ) { + + //animation start + this._anglePrev = 0; + this._angleCurrent = 0; + this._timeStart = time; + + } + + if ( this._state == STATE.ANIMATION_ROTATE ) { + + //w = w0 + alpha * t + const deltaTime = ( time - this._timeStart ) / 1000; + const w = w0 + ( ( - this.dampingFactor ) * deltaTime ); + + if ( w > 0 ) { + + //tetha = 0.5 * alpha * t^2 + w0 * t + tetha0 + this._angleCurrent = 0.5 * ( - this.dampingFactor ) * Math.pow( deltaTime, 2 ) + w0 * deltaTime + 0; + this.applyTransformMatrix( this.rotate( rotationAxis, this._angleCurrent ) ); + this.dispatchEvent( _changeEvent ); + const self = this; + this._animationId = window.requestAnimationFrame( function ( t ) { + + self.onRotationAnim( t, rotationAxis, w0 ); + + } ); + + } else { + + this._animationId = - 1; + this._timeStart = - 1; + + this.updateTbState( STATE.IDLE, false ); + this.activateGizmos( false ); + + this.dispatchEvent( _changeEvent ); + + } + + } else { + + //interrupt animation + + this._animationId = - 1; + this._timeStart = - 1; + + if ( this._state != STATE.ROTATE ) { + + this.activateGizmos( false ); + this.dispatchEvent( _changeEvent ); + + } + + } + + }; + + + /** + * Perform pan operation moving camera between two points + * @param {Vector3} p0 Initial point + * @param {Vector3} p1 Ending point + * @param {Boolean} adjust If movement should be adjusted considering camera distance (Perspective only) + */ + pan = ( p0, p1, adjust = false ) => { + + const movement = p0.clone().sub( p1 ); + + if ( this.camera.isOrthographicCamera ) { + + //adjust movement amount + movement.multiplyScalar( 1 / this.camera.zoom ); + + } else if ( this.camera.isPerspectiveCamera && adjust ) { + + //adjust movement amount + this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ); //camera's initial position + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ); //gizmo's initial position + const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.camera.position.distanceTo( this._gizmos.position ); + movement.multiplyScalar( 1 / distanceFactor ); + + } + + this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.camera.quaternion ); + + this._m4_1.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ); + + this.setTransformationMatrices( this._m4_1, this._m4_1 ); + return _transformation; + + }; + + /** + * Reset trackball + */ + reset = () => { + + this.camera.zoom = this._zoom0; + + if ( this.camera.isPerspectiveCamera ) { + + this.camera.fov = this._fov0; + + } + + this.camera.near = this._nearPos; + this.camera.far = this._farPos; + this._cameraMatrixState.copy( this._cameraMatrixState0 ); + this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + this.camera.up.copy( this._up0 ); + + this.camera.updateMatrix(); + this.camera.updateProjectionMatrix(); + + this._gizmoMatrixState.copy( this._gizmoMatrixState0 ); + this._gizmoMatrixState0.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + this._gizmos.updateMatrix(); + + this._tbRadius = this.calculateTbRadius( this.camera ); + this.makeGizmos( this._gizmos.position, this._tbRadius ); + + this.camera.lookAt( this._gizmos.position ); + + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + + }; + + /** + * Rotate the camera around an axis passing by trackball's center + * @param {Vector3} axis Rotation axis + * @param {number} angle Angle in radians + * @returns {Object} Object with 'camera' field containing transformation matrix resulting from the operation to be applied to the camera + */ + rotate = ( axis, angle ) => { + + const point = this._gizmos.position; //rotation center + this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z ); + this._rotationMatrix.makeRotationAxis( axis, - angle ); + + //rotate camera + this._m4_1.makeTranslation( point.x, point.y, point.z ); + this._m4_1.multiply( this._rotationMatrix ); + this._m4_1.multiply( this._translationMatrix ); + + this.setTransformationMatrices( this._m4_1 ); + + return _transformation; + + }; + + copyState = () => { + + let state; + if ( this.camera.isOrthographicCamera ) { + + state = JSON.stringify( { arcballState: { + + cameraFar: this.camera.far, + cameraMatrix: this.camera.matrix, + cameraNear: this.camera.near, + cameraUp: this.camera.up, + cameraZoom: this.camera.zoom, + gizmoMatrix: this._gizmos.matrix + + } } ); + + } else if ( this.camera.isPerspectiveCamera ) { + + state = JSON.stringify( { arcballState: { + cameraFar: this.camera.far, + cameraFov: this.camera.fov, + cameraMatrix: this.camera.matrix, + cameraNear: this.camera.near, + cameraUp: this.camera.up, + cameraZoom: this.camera.zoom, + gizmoMatrix: this._gizmos.matrix + + } } ); + + } + + navigator.clipboard.writeText( state ); + + }; + + pasteState = () => { + + const self = this; + navigator.clipboard.readText().then( function resolved( value ) { + + self.setStateFromJSON( value ); + + } ); + + }; + + /** + * Save the current state of the control. This can later be recover with .reset + */ + saveState = () => { + + this._cameraMatrixState0.copy( this.camera.matrix ); + this._gizmoMatrixState0.copy( this._gizmos.matrix ); + this._nearPos = this.camera.near; + this._farPos = this.camera.far; + this._zoom0 = this.camera.zoom; + this._up0.copy( this.camera.up ); + + if ( this.camera.isPerspectiveCamera ) { + + this._fov0 = this.camera.fov; + + } + + }; + + /** + * Perform uniform scale operation around a given point + * @param {Number} size Scale factor + * @param {Vector3} point Point around which scale + * @param {Boolean} scaleGizmos If gizmos should be scaled (Perspective only) + * @returns {Object} Object with 'camera' and 'gizmo' fields containing transformation matrices resulting from the operation to be applied to the camera and gizmos + */ + scale = ( size, point, scaleGizmos = true ) => { + + _scalePointTemp.copy( point ); + let sizeInverse = 1 / size; + + if ( this.camera.isOrthographicCamera ) { + + //camera zoom + this.camera.zoom = this._zoomState; + this.camera.zoom *= size; + + //check min and max zoom + if ( this.camera.zoom > this.maxZoom ) { + + this.camera.zoom = this.maxZoom; + sizeInverse = this._zoomState / this.maxZoom; + + } else if ( this.camera.zoom < this.minZoom ) { + + this.camera.zoom = this.minZoom; + sizeInverse = this._zoomState / this.minZoom; + + } + + this.camera.updateProjectionMatrix(); + + this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ); //gizmos position + + //scale gizmos so they appear in the same spot having the same dimension + this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse ); + this._translationMatrix.makeTranslation( - this._v3_1.x, - this._v3_1.y, - this._v3_1.z ); + + this._m4_2.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ).multiply( this._scaleMatrix ); + this._m4_2.multiply( this._translationMatrix ); + + + //move camera and gizmos to obtain pinch effect + _scalePointTemp.sub( this._v3_1 ); + + const amount = _scalePointTemp.clone().multiplyScalar( sizeInverse ); + _scalePointTemp.sub( amount ); + + this._m4_1.makeTranslation( _scalePointTemp.x, _scalePointTemp.y, _scalePointTemp.z ); + this._m4_2.premultiply( this._m4_1 ); + + this.setTransformationMatrices( this._m4_1, this._m4_2 ); + return _transformation; + + } else if ( this.camera.isPerspectiveCamera ) { + + this._v3_1.setFromMatrixPosition( this._cameraMatrixState ); + this._v3_2.setFromMatrixPosition( this._gizmoMatrixState ); + + //move camera + let distance = this._v3_1.distanceTo( _scalePointTemp ); + let amount = distance - ( distance * sizeInverse ); + + //check min and max distance + const newDistance = distance - amount; + if ( newDistance < this.minDistance ) { + + sizeInverse = this.minDistance / distance; + amount = distance - ( distance * sizeInverse ); + + } else if ( newDistance > this.maxDistance ) { + + sizeInverse = this.maxDistance / distance; + amount = distance - ( distance * sizeInverse ); + + } + + _offset.copy( _scalePointTemp ).sub( this._v3_1 ).normalize().multiplyScalar( amount ); + + this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z ); + + + if ( scaleGizmos ) { + + //scale gizmos so they appear in the same spot having the same dimension + const pos = this._v3_2; + + distance = pos.distanceTo( _scalePointTemp ); + amount = distance - ( distance * sizeInverse ); + _offset.copy( _scalePointTemp ).sub( this._v3_2 ).normalize().multiplyScalar( amount ); + + this._translationMatrix.makeTranslation( pos.x, pos.y, pos.z ); + this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse ); + + this._m4_2.makeTranslation( _offset.x, _offset.y, _offset.z ).multiply( this._translationMatrix ); + this._m4_2.multiply( this._scaleMatrix ); + + this._translationMatrix.makeTranslation( - pos.x, - pos.y, - pos.z ); + + this._m4_2.multiply( this._translationMatrix ); + this.setTransformationMatrices( this._m4_1, this._m4_2 ); + + + } else { + + this.setTransformationMatrices( this._m4_1 ); + + } + + return _transformation; + + } + + }; + + /** + * Set camera fov + * @param {Number} value fov to be setted + */ + setFov = ( value ) => { + + if ( this.camera.isPerspectiveCamera ) { + + this.camera.fov = MathUtils.clamp( value, this.minFov, this.maxFov ); + this.camera.updateProjectionMatrix(); + + } + + }; + + /** + * Set values in transformation object + * @param {Matrix4} camera Transformation to be applied to the camera + * @param {Matrix4} gizmos Transformation to be applied to gizmos + */ + setTransformationMatrices( camera = null, gizmos = null ) { + + if ( camera != null ) { + + if ( _transformation.camera != null ) { + + _transformation.camera.copy( camera ); + + } else { + + _transformation.camera = camera.clone(); + + } + + } else { + + _transformation.camera = null; + + } + + if ( gizmos != null ) { + + if ( _transformation.gizmos != null ) { + + _transformation.gizmos.copy( gizmos ); + + } else { + + _transformation.gizmos = gizmos.clone(); + + } + + } else { + + _transformation.gizmos = null; + + } + + } + + /** + * Rotate camera around its direction axis passing by a given point by a given angle + * @param {Vector3} point The point where the rotation axis is passing trough + * @param {Number} angle Angle in radians + * @returns The computed transormation matix + */ + zRotate = ( point, angle ) => { + + this._rotationMatrix.makeRotationAxis( this._rotationAxis, angle ); + this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z ); + + this._m4_1.makeTranslation( point.x, point.y, point.z ); + this._m4_1.multiply( this._rotationMatrix ); + this._m4_1.multiply( this._translationMatrix ); + + this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ).sub( point ); //vector from rotation center to gizmos position + this._v3_2.copy( this._v3_1 ).applyAxisAngle( this._rotationAxis, angle ); //apply rotation + this._v3_2.sub( this._v3_1 ); + + this._m4_2.makeTranslation( this._v3_2.x, this._v3_2.y, this._v3_2.z ); + + this.setTransformationMatrices( this._m4_1, this._m4_2 ); + return _transformation; + + }; + + + getRaycaster() { + + return _raycaster; + + } + + + /** + * Unproject the cursor on the 3D object surface + * @param {Vector2} cursor Cursor coordinates in NDC + * @param {Camera} camera Virtual camera + * @returns {Vector3} The point of intersection with the model, if exist, null otherwise + */ + unprojectOnObj = ( cursor, camera ) => { + + const raycaster = this.getRaycaster(); + raycaster.near = camera.near; + raycaster.far = camera.far; + raycaster.setFromCamera( cursor, camera ); + + const intersect = raycaster.intersectObjects( this.scene.children, true ); + + for ( let i = 0; i < intersect.length; i ++ ) { + + if ( intersect[ i ].object.uuid != this._gizmos.uuid && intersect[ i ].face != null ) { + + return intersect[ i ].point.clone(); + + } + + } + + return null; + + }; + + /** + * Unproject the cursor on the trackball surface + * @param {Camera} camera The virtual camera + * @param {Number} cursorX Cursor horizontal coordinate on screen + * @param {Number} cursorY Cursor vertical coordinate on screen + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @param {number} tbRadius The trackball radius + * @returns {Vector3} The unprojected point on the trackball surface + */ + unprojectOnTbSurface = ( camera, cursorX, cursorY, canvas, tbRadius ) => { + + if ( camera.type == 'OrthographicCamera' ) { + + this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) ); + this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 ); + + const x2 = Math.pow( this._v2_1.x, 2 ); + const y2 = Math.pow( this._v2_1.y, 2 ); + const r2 = Math.pow( this._tbRadius, 2 ); + + if ( x2 + y2 <= r2 * 0.5 ) { + + //intersection with sphere + this._v3_1.setZ( Math.sqrt( r2 - ( x2 + y2 ) ) ); + + } else { + + //intersection with hyperboloid + this._v3_1.setZ( ( r2 * 0.5 ) / ( Math.sqrt( x2 + y2 ) ) ); + + } + + return this._v3_1; + + } else if ( camera.type == 'PerspectiveCamera' ) { + + //unproject cursor on the near plane + this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); + + this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 ); + this._v3_1.applyMatrix4( camera.projectionMatrixInverse ); + + const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction + const cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position ); + const radius2 = Math.pow( tbRadius, 2 ); + + // camera + // |\ + // | \ + // | \ + // h | \ + // | \ + // | \ + // _ _ | _ _ _\ _ _ near plane + // l + + const h = this._v3_1.z; + const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) ); + + if ( l == 0 ) { + + //ray aligned with camera + rayDir.set( this._v3_1.x, this._v3_1.y, tbRadius ); + return rayDir; + + } + + const m = h / l; + const q = cameraGizmoDistance; + + /* + * calculate intersection point between unprojected ray and trackball surface + *|y = m * x + q + *|x^2 + y^2 = r^2 + * + * (m^2 + 1) * x^2 + (2 * m * q) * x + q^2 - r^2 = 0 + */ + let a = Math.pow( m, 2 ) + 1; + let b = 2 * m * q; + let c = Math.pow( q, 2 ) - radius2; + let delta = Math.pow( b, 2 ) - ( 4 * a * c ); + + if ( delta >= 0 ) { + + //intersection with sphere + this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) ); + this._v2_1.setY( m * this._v2_1.x + q ); + + const angle = MathUtils.RAD2DEG * this._v2_1.angle(); + + if ( angle >= 45 ) { + + //if angle between intersection point and X' axis is >= 45°, return that point + //otherwise, calculate intersection point with hyperboloid + + const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) ); + rayDir.multiplyScalar( rayLength ); + rayDir.z += cameraGizmoDistance; + return rayDir; + + } + + } + + //intersection with hyperboloid + /* + *|y = m * x + q + *|y = (1 / x) * (r^2 / 2) + * + * m * x^2 + q * x - r^2 / 2 = 0 + */ + + a = m; + b = q; + c = - radius2 * 0.5; + delta = Math.pow( b, 2 ) - ( 4 * a * c ); + this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) ); + this._v2_1.setY( m * this._v2_1.x + q ); + + const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) ); + + rayDir.multiplyScalar( rayLength ); + rayDir.z += cameraGizmoDistance; + return rayDir; + + } + + }; + + + /** + * Unproject the cursor on the plane passing through the center of the trackball orthogonal to the camera + * @param {Camera} camera The virtual camera + * @param {Number} cursorX Cursor horizontal coordinate on screen + * @param {Number} cursorY Cursor vertical coordinate on screen + * @param {HTMLElement} canvas The canvas where the renderer draws its output + * @param {Boolean} initialDistance If initial distance between camera and gizmos should be used for calculations instead of current (Perspective only) + * @returns {Vector3} The unprojected point on the trackball plane + */ + unprojectOnTbPlane = ( camera, cursorX, cursorY, canvas, initialDistance = false ) => { + + if ( camera.type == '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(); + + } else if ( camera.type == 'PerspectiveCamera' ) { + + this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) ); + + //unproject cursor on the near plane + this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 ); + this._v3_1.applyMatrix4( camera.projectionMatrixInverse ); + + const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction + + // camera + // |\ + // | \ + // | \ + // h | \ + // | \ + // | \ + // _ _ | _ _ _\ _ _ near plane + // l + + const h = this._v3_1.z; + const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) ); + let cameraGizmoDistance; + + if ( initialDistance ) { + + cameraGizmoDistance = this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ).distanceTo( this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ) ); + + } else { + + cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position ); + + } + + /* + * calculate intersection point between unprojected ray and the plane + *|y = mx + q + *|y = 0 + * + * x = -q/m + */ + if ( l == 0 ) { + + //ray aligned with camera + rayDir.set( 0, 0, 0 ); + return rayDir; + + } + + const m = h / l; + const q = cameraGizmoDistance; + const x = - q / m; + + const rayLength = Math.sqrt( Math.pow( q, 2 ) + Math.pow( x, 2 ) ); + rayDir.multiplyScalar( rayLength ); + rayDir.z = 0; + return rayDir; + + } + + }; + + /** + * Update camera and gizmos state + */ + updateMatrixState = () => { + + //update camera and gizmos state + this._cameraMatrixState.copy( this.camera.matrix ); + this._gizmoMatrixState.copy( this._gizmos.matrix ); + + if ( this.camera.isOrthographicCamera ) { + + this._cameraProjectionState.copy( this.camera.projectionMatrix ); + this.camera.updateProjectionMatrix(); + this._zoomState = this.camera.zoom; + + } else if ( this.camera.isPerspectiveCamera ) { + + this._fovState = this.camera.fov; + + } + + }; + + /** + * Update the trackball FSA + * @param {STATE} newState New state of the FSA + * @param {Boolean} updateMatrices If matriices state should be updated + */ + updateTbState = ( newState, updateMatrices ) => { + + this._state = newState; + if ( updateMatrices ) { + + this.updateMatrixState(); + + } + + }; + + update = () => { + + const EPS = 0.000001; + + if ( this.target.equals( this._currentTarget ) === false ) { + + this._gizmos.position.copy( this.target ); //for correct radius calculation + this._tbRadius = this.calculateTbRadius( this.camera ); + this.makeGizmos( this.target, this._tbRadius ); + this._currentTarget.copy( this.target ); + + } + + //check min/max parameters + if ( this.camera.isOrthographicCamera ) { + + //check zoom + if ( this.camera.zoom > this.maxZoom || this.camera.zoom < this.minZoom ) { + + const newZoom = MathUtils.clamp( this.camera.zoom, this.minZoom, this.maxZoom ); + this.applyTransformMatrix( this.scale( newZoom / this.camera.zoom, this._gizmos.position, true ) ); + + } + + } else if ( this.camera.isPerspectiveCamera ) { + + //check distance + const distance = this.camera.position.distanceTo( this._gizmos.position ); + + if ( distance > this.maxDistance + EPS || distance < this.minDistance - EPS ) { + + const newDistance = MathUtils.clamp( distance, this.minDistance, this.maxDistance ); + this.applyTransformMatrix( this.scale( newDistance / distance, this._gizmos.position ) ); + this.updateMatrixState(); + + } + + //check fov + if ( this.camera.fov < this.minFov || this.camera.fov > this.maxFov ) { + + this.camera.fov = MathUtils.clamp( this.camera.fov, this.minFov, this.maxFov ); + this.camera.updateProjectionMatrix(); + + } + + const oldRadius = this._tbRadius; + this._tbRadius = this.calculateTbRadius( this.camera ); + + 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; + const curve = new EllipseCurve( 0, 0, newRadius, newRadius ); + const points = curve.getPoints( this._curvePts ); + const curveGeometry = new BufferGeometry().setFromPoints( points ); + + for ( const gizmo in this._gizmos.children ) { + + this._gizmos.children[ gizmo ].geometry = curveGeometry; + + } + + } + + } + + this.camera.lookAt( this._gizmos.position ); + + }; + + setStateFromJSON = ( json ) => { + + const state = JSON.parse( json ); + + if ( state.arcballState != undefined ) { + + this._cameraMatrixState.fromArray( state.arcballState.cameraMatrix.elements ); + this._cameraMatrixState.decompose( this.camera.position, this.camera.quaternion, this.camera.scale ); + + this.camera.up.copy( state.arcballState.cameraUp ); + this.camera.near = state.arcballState.cameraNear; + this.camera.far = state.arcballState.cameraFar; + + this.camera.zoom = state.arcballState.cameraZoom; + + if ( this.camera.isPerspectiveCamera ) { + + this.camera.fov = state.arcballState.cameraFov; + + } + + this._gizmoMatrixState.fromArray( state.arcballState.gizmoMatrix.elements ); + this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale ); + + this.camera.updateMatrix(); + this.camera.updateProjectionMatrix(); + + this._gizmos.updateMatrix(); + + this._tbRadius = this.calculateTbRadius( this.camera ); + const gizmoTmp = new Matrix4().copy( this._gizmoMatrixState0 ); + this.makeGizmos( this._gizmos.position, this._tbRadius ); + this._gizmoMatrixState0.copy( gizmoTmp ); + + this.camera.lookAt( this._gizmos.position ); + this.updateTbState( STATE.IDLE, false ); + + this.dispatchEvent( _changeEvent ); + + } + + }; + +} + +export { ArcballControls }; diff --git a/jsm/controls/DragControls.js b/jsm/controls/DragControls.js new file mode 100644 index 0000000..4db4813 --- /dev/null +++ b/jsm/controls/DragControls.js @@ -0,0 +1,220 @@ +import { + EventDispatcher, + Matrix4, + Plane, + Raycaster, + Vector2, + Vector3 +} from 'three'; + +const _plane = new Plane(); +const _raycaster = new Raycaster(); + +const _pointer = new Vector2(); +const _offset = new Vector3(); +const _intersection = new Vector3(); +const _worldPosition = new Vector3(); +const _inverseMatrix = new Matrix4(); + +class DragControls extends EventDispatcher { + + constructor( _objects, _camera, _domElement ) { + + super(); + + _domElement.style.touchAction = 'none'; // disable touch scroll + + let _selected = null, _hovered = null; + + const _intersections = []; + + // + + const scope = this; + + function activate() { + + _domElement.addEventListener( 'pointermove', onPointerMove ); + _domElement.addEventListener( 'pointerdown', onPointerDown ); + _domElement.addEventListener( 'pointerup', onPointerCancel ); + _domElement.addEventListener( 'pointerleave', onPointerCancel ); + + } + + function deactivate() { + + _domElement.removeEventListener( 'pointermove', onPointerMove ); + _domElement.removeEventListener( 'pointerdown', onPointerDown ); + _domElement.removeEventListener( 'pointerup', onPointerCancel ); + _domElement.removeEventListener( 'pointerleave', onPointerCancel ); + + _domElement.style.cursor = ''; + + } + + function dispose() { + + deactivate(); + + } + + function getObjects() { + + return _objects; + + } + + function getRaycaster() { + + return _raycaster; + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + updatePointer( event ); + + _raycaster.setFromCamera( _pointer, _camera ); + + if ( _selected ) { + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) ); + + } + + scope.dispatchEvent( { type: 'drag', object: _selected } ); + + return; + + } + + // hover support + + if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) { + + _intersections.length = 0; + + _raycaster.setFromCamera( _pointer, _camera ); + _raycaster.intersectObjects( _objects, true, _intersections ); + + if ( _intersections.length > 0 ) { + + const object = _intersections[ 0 ].object; + + _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) ); + + if ( _hovered !== object && _hovered !== null ) { + + scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + + _domElement.style.cursor = 'auto'; + _hovered = null; + + } + + if ( _hovered !== object ) { + + scope.dispatchEvent( { type: 'hoveron', object: object } ); + + _domElement.style.cursor = 'pointer'; + _hovered = object; + + } + + } else { + + if ( _hovered !== null ) { + + scope.dispatchEvent( { type: 'hoveroff', object: _hovered } ); + + _domElement.style.cursor = 'auto'; + _hovered = null; + + } + + } + + } + + } + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + updatePointer( event ); + + _intersections.length = 0; + + _raycaster.setFromCamera( _pointer, _camera ); + _raycaster.intersectObjects( _objects, true, _intersections ); + + if ( _intersections.length > 0 ) { + + _selected = ( scope.transformGroup === true ) ? _objects[ 0 ] : _intersections[ 0 ].object; + + _plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); + + if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) { + + _inverseMatrix.copy( _selected.parent.matrixWorld ).invert(); + _offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) ); + + } + + _domElement.style.cursor = 'move'; + + scope.dispatchEvent( { type: 'dragstart', object: _selected } ); + + } + + + } + + function onPointerCancel() { + + if ( scope.enabled === false ) return; + + if ( _selected ) { + + scope.dispatchEvent( { type: 'dragend', object: _selected } ); + + _selected = null; + + } + + _domElement.style.cursor = _hovered ? 'pointer' : 'auto'; + + } + + function updatePointer( event ) { + + const rect = _domElement.getBoundingClientRect(); + + _pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1; + _pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1; + + } + + activate(); + + // API + + this.enabled = true; + this.transformGroup = false; + + this.activate = activate; + this.deactivate = deactivate; + this.dispose = dispose; + this.getObjects = getObjects; + this.getRaycaster = getRaycaster; + + } + +} + +export { DragControls }; diff --git a/jsm/controls/FirstPersonControls.js b/jsm/controls/FirstPersonControls.js new file mode 100644 index 0000000..143e139 --- /dev/null +++ b/jsm/controls/FirstPersonControls.js @@ -0,0 +1,332 @@ +import { + MathUtils, + Spherical, + Vector3 +} from 'three'; + +const _lookDirection = new Vector3(); +const _spherical = new Spherical(); +const _target = new Vector3(); + +class FirstPersonControls { + + constructor( object, domElement ) { + + if ( domElement === undefined ) { + + console.warn( 'THREE.FirstPersonControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.object = object; + this.domElement = domElement; + + // API + + this.enabled = true; + + this.movementSpeed = 1.0; + this.lookSpeed = 0.005; + + this.lookVertical = true; + this.autoForward = false; + + this.activeLook = true; + + this.heightSpeed = false; + this.heightCoef = 1.0; + this.heightMin = 0.0; + this.heightMax = 1.0; + + this.constrainVertical = false; + this.verticalMin = 0; + this.verticalMax = Math.PI; + + this.mouseDragOn = false; + + // internals + + this.autoSpeedFactor = 0.0; + + this.mouseX = 0; + this.mouseY = 0; + + this.moveForward = false; + this.moveBackward = false; + this.moveLeft = false; + this.moveRight = false; + + this.viewHalfX = 0; + this.viewHalfY = 0; + + // private variables + + let lat = 0; + let lon = 0; + + // + + this.handleResize = function () { + + if ( this.domElement === document ) { + + this.viewHalfX = window.innerWidth / 2; + this.viewHalfY = window.innerHeight / 2; + + } else { + + this.viewHalfX = this.domElement.offsetWidth / 2; + this.viewHalfY = this.domElement.offsetHeight / 2; + + } + + }; + + this.onMouseDown = function ( event ) { + + if ( this.domElement !== document ) { + + this.domElement.focus(); + + } + + if ( this.activeLook ) { + + switch ( event.button ) { + + case 0: this.moveForward = true; break; + case 2: this.moveBackward = true; break; + + } + + } + + this.mouseDragOn = true; + + }; + + this.onMouseUp = function ( event ) { + + if ( this.activeLook ) { + + switch ( event.button ) { + + case 0: this.moveForward = false; break; + case 2: this.moveBackward = false; break; + + } + + } + + this.mouseDragOn = false; + + }; + + this.onMouseMove = function ( event ) { + + if ( this.domElement === document ) { + + this.mouseX = event.pageX - this.viewHalfX; + this.mouseY = event.pageY - this.viewHalfY; + + } else { + + this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX; + this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY; + + } + + }; + + this.onKeyDown = function ( event ) { + + switch ( event.code ) { + + case 'ArrowUp': + case 'KeyW': this.moveForward = true; break; + + case 'ArrowLeft': + case 'KeyA': this.moveLeft = true; break; + + case 'ArrowDown': + case 'KeyS': this.moveBackward = true; break; + + case 'ArrowRight': + case 'KeyD': this.moveRight = true; break; + + case 'KeyR': this.moveUp = true; break; + case 'KeyF': this.moveDown = true; break; + + } + + }; + + this.onKeyUp = function ( event ) { + + switch ( event.code ) { + + case 'ArrowUp': + case 'KeyW': this.moveForward = false; break; + + case 'ArrowLeft': + case 'KeyA': this.moveLeft = false; break; + + case 'ArrowDown': + case 'KeyS': this.moveBackward = false; break; + + case 'ArrowRight': + case 'KeyD': this.moveRight = false; break; + + case 'KeyR': this.moveUp = false; break; + case 'KeyF': this.moveDown = false; break; + + } + + }; + + this.lookAt = function ( x, y, z ) { + + if ( x.isVector3 ) { + + _target.copy( x ); + + } else { + + _target.set( x, y, z ); + + } + + this.object.lookAt( _target ); + + setOrientation( this ); + + return this; + + }; + + this.update = function () { + + const targetPosition = new Vector3(); + + return function update( delta ) { + + if ( this.enabled === false ) return; + + if ( this.heightSpeed ) { + + const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax ); + const heightDelta = y - this.heightMin; + + this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef ); + + } else { + + this.autoSpeedFactor = 0.0; + + } + + const actualMoveSpeed = delta * this.movementSpeed; + + if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) ); + if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed ); + + if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed ); + if ( this.moveRight ) this.object.translateX( actualMoveSpeed ); + + if ( this.moveUp ) this.object.translateY( actualMoveSpeed ); + if ( this.moveDown ) this.object.translateY( - actualMoveSpeed ); + + let actualLookSpeed = delta * this.lookSpeed; + + if ( ! this.activeLook ) { + + actualLookSpeed = 0; + + } + + let verticalLookRatio = 1; + + if ( this.constrainVertical ) { + + verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin ); + + } + + lon -= this.mouseX * actualLookSpeed; + if ( this.lookVertical ) lat -= this.mouseY * actualLookSpeed * verticalLookRatio; + + lat = Math.max( - 85, Math.min( 85, lat ) ); + + let phi = MathUtils.degToRad( 90 - lat ); + const theta = MathUtils.degToRad( lon ); + + if ( this.constrainVertical ) { + + phi = MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax ); + + } + + const position = this.object.position; + + targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position ); + + this.object.lookAt( targetPosition ); + + }; + + }(); + + this.dispose = function () { + + this.domElement.removeEventListener( 'contextmenu', contextmenu ); + this.domElement.removeEventListener( 'mousedown', _onMouseDown ); + this.domElement.removeEventListener( 'mousemove', _onMouseMove ); + this.domElement.removeEventListener( 'mouseup', _onMouseUp ); + + window.removeEventListener( 'keydown', _onKeyDown ); + window.removeEventListener( 'keyup', _onKeyUp ); + + }; + + const _onMouseMove = this.onMouseMove.bind( this ); + const _onMouseDown = this.onMouseDown.bind( this ); + const _onMouseUp = this.onMouseUp.bind( this ); + const _onKeyDown = this.onKeyDown.bind( this ); + const _onKeyUp = this.onKeyUp.bind( this ); + + this.domElement.addEventListener( 'contextmenu', contextmenu ); + this.domElement.addEventListener( 'mousemove', _onMouseMove ); + this.domElement.addEventListener( 'mousedown', _onMouseDown ); + this.domElement.addEventListener( 'mouseup', _onMouseUp ); + + window.addEventListener( 'keydown', _onKeyDown ); + window.addEventListener( 'keyup', _onKeyUp ); + + function setOrientation( controls ) { + + const quaternion = controls.object.quaternion; + + _lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion ); + _spherical.setFromVector3( _lookDirection ); + + lat = 90 - MathUtils.radToDeg( _spherical.phi ); + lon = MathUtils.radToDeg( _spherical.theta ); + + } + + this.handleResize(); + + setOrientation( this ); + + } + +} + +function contextmenu( event ) { + + event.preventDefault(); + +} + +export { FirstPersonControls }; diff --git a/jsm/controls/FlyControls.js b/jsm/controls/FlyControls.js new file mode 100644 index 0000000..69a29a2 --- /dev/null +++ b/jsm/controls/FlyControls.js @@ -0,0 +1,292 @@ +import { + EventDispatcher, + Quaternion, + Vector3 +} from 'three'; + +const _changeEvent = { type: 'change' }; + +class FlyControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.FlyControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.object = object; + this.domElement = domElement; + + // API + + this.movementSpeed = 1.0; + this.rollSpeed = 0.005; + + this.dragToLook = false; + this.autoForward = false; + + // disable default target object behavior + + // internals + + const scope = this; + + const EPS = 0.000001; + + const lastQuaternion = new Quaternion(); + const lastPosition = new Vector3(); + + this.tmpQuaternion = new Quaternion(); + + this.mouseStatus = 0; + + this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 }; + this.moveVector = new Vector3( 0, 0, 0 ); + this.rotationVector = new Vector3( 0, 0, 0 ); + + this.keydown = function ( event ) { + + if ( event.altKey ) { + + return; + + } + + switch ( event.code ) { + + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = .1; break; + + case 'KeyW': this.moveState.forward = 1; break; + case 'KeyS': this.moveState.back = 1; break; + + case 'KeyA': this.moveState.left = 1; break; + case 'KeyD': this.moveState.right = 1; break; + + case 'KeyR': this.moveState.up = 1; break; + case 'KeyF': this.moveState.down = 1; break; + + case 'ArrowUp': this.moveState.pitchUp = 1; break; + case 'ArrowDown': this.moveState.pitchDown = 1; break; + + case 'ArrowLeft': this.moveState.yawLeft = 1; break; + case 'ArrowRight': this.moveState.yawRight = 1; break; + + case 'KeyQ': this.moveState.rollLeft = 1; break; + case 'KeyE': this.moveState.rollRight = 1; break; + + } + + this.updateMovementVector(); + this.updateRotationVector(); + + }; + + this.keyup = function ( event ) { + + switch ( event.code ) { + + case 'ShiftLeft': + case 'ShiftRight': this.movementSpeedMultiplier = 1; break; + + case 'KeyW': this.moveState.forward = 0; break; + case 'KeyS': this.moveState.back = 0; break; + + case 'KeyA': this.moveState.left = 0; break; + case 'KeyD': this.moveState.right = 0; break; + + case 'KeyR': this.moveState.up = 0; break; + case 'KeyF': this.moveState.down = 0; break; + + case 'ArrowUp': this.moveState.pitchUp = 0; break; + case 'ArrowDown': this.moveState.pitchDown = 0; break; + + case 'ArrowLeft': this.moveState.yawLeft = 0; break; + case 'ArrowRight': this.moveState.yawRight = 0; break; + + case 'KeyQ': this.moveState.rollLeft = 0; break; + case 'KeyE': this.moveState.rollRight = 0; break; + + } + + this.updateMovementVector(); + this.updateRotationVector(); + + }; + + this.mousedown = function ( event ) { + + if ( this.dragToLook ) { + + this.mouseStatus ++; + + } else { + + switch ( event.button ) { + + case 0: this.moveState.forward = 1; break; + case 2: this.moveState.back = 1; break; + + } + + this.updateMovementVector(); + + } + + }; + + this.mousemove = function ( event ) { + + if ( ! this.dragToLook || this.mouseStatus > 0 ) { + + const container = this.getContainerDimensions(); + const halfWidth = container.size[ 0 ] / 2; + const halfHeight = container.size[ 1 ] / 2; + + this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth; + this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight; + + this.updateRotationVector(); + + } + + }; + + this.mouseup = function ( event ) { + + if ( this.dragToLook ) { + + this.mouseStatus --; + + this.moveState.yawLeft = this.moveState.pitchDown = 0; + + } else { + + switch ( event.button ) { + + case 0: this.moveState.forward = 0; break; + case 2: this.moveState.back = 0; break; + + } + + this.updateMovementVector(); + + } + + this.updateRotationVector(); + + }; + + this.update = function ( delta ) { + + const moveMult = delta * scope.movementSpeed; + const rotMult = delta * scope.rollSpeed; + + scope.object.translateX( scope.moveVector.x * moveMult ); + scope.object.translateY( scope.moveVector.y * moveMult ); + scope.object.translateZ( scope.moveVector.z * moveMult ); + + scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize(); + scope.object.quaternion.multiply( scope.tmpQuaternion ); + + if ( + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS + ) { + + scope.dispatchEvent( _changeEvent ); + lastQuaternion.copy( scope.object.quaternion ); + lastPosition.copy( scope.object.position ); + + } + + }; + + this.updateMovementVector = function () { + + const forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0; + + this.moveVector.x = ( - this.moveState.left + this.moveState.right ); + this.moveVector.y = ( - this.moveState.down + this.moveState.up ); + this.moveVector.z = ( - forward + this.moveState.back ); + + //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] ); + + }; + + this.updateRotationVector = function () { + + this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp ); + this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft ); + this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft ); + + //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] ); + + }; + + this.getContainerDimensions = function () { + + if ( this.domElement != document ) { + + return { + size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ], + offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ] + }; + + } else { + + return { + size: [ window.innerWidth, window.innerHeight ], + offset: [ 0, 0 ] + }; + + } + + }; + + this.dispose = function () { + + this.domElement.removeEventListener( 'contextmenu', contextmenu ); + this.domElement.removeEventListener( 'mousedown', _mousedown ); + this.domElement.removeEventListener( 'mousemove', _mousemove ); + this.domElement.removeEventListener( 'mouseup', _mouseup ); + + window.removeEventListener( 'keydown', _keydown ); + window.removeEventListener( 'keyup', _keyup ); + + }; + + const _mousemove = this.mousemove.bind( this ); + const _mousedown = this.mousedown.bind( this ); + const _mouseup = this.mouseup.bind( this ); + const _keydown = this.keydown.bind( this ); + const _keyup = this.keyup.bind( this ); + + this.domElement.addEventListener( 'contextmenu', contextmenu ); + + this.domElement.addEventListener( 'mousemove', _mousemove ); + this.domElement.addEventListener( 'mousedown', _mousedown ); + this.domElement.addEventListener( 'mouseup', _mouseup ); + + window.addEventListener( 'keydown', _keydown ); + window.addEventListener( 'keyup', _keyup ); + + this.updateMovementVector(); + this.updateRotationVector(); + + } + +} + +function contextmenu( event ) { + + event.preventDefault(); + +} + +export { FlyControls }; diff --git a/jsm/controls/OrbitControls.js b/jsm/controls/OrbitControls.js new file mode 100644 index 0000000..6281fb9 --- /dev/null +++ b/jsm/controls/OrbitControls.js @@ -0,0 +1,1252 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from '../../src/Three.js'; + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +class OrbitControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3(); + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI ) + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option actually enables dollying in and out; left as "zoom" for backwards compatibility. + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60 + + // The four arrow keys + this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' }; + + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.zoom0 = this.object.zoom; + + // the target DOM element for key events + this._domElementKeyEvents = null; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.getDistance = function () { + + return this.object.position.distanceTo( this.target ); + + }; + + this.listenToKeyEvents = function ( domElement ) { + + domElement.addEventListener( 'keydown', onKeyDown ); + this._domElementKeyEvents = domElement; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( _changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + const offset = new Vector3(); + + // so camera.up is the orbit axis + const quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + const quatInverse = quat.clone().invert(); + + const lastPosition = new Vector3(); + const lastQuaternion = new Quaternion(); + + const twoPI = 2 * Math.PI; + + return function update() { + + const position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + // angle from z-axis around y-axis + spherical.setFromVector3( offset ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + + let min = scope.minAzimuthAngle; + let max = scope.maxAzimuthAngle; + + if ( isFinite( min ) && isFinite( max ) ) { + + if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI; + + if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI; + + if ( min <= max ) { + + spherical.theta = Math.max( min, Math.min( max, spherical.theta ) ); + + } else { + + spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ? + Math.max( min, spherical.theta ) : + Math.min( max, spherical.theta ); + + } + + } + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + position.copy( scope.target ).add( offset ); + + scope.object.lookAt( scope.target ); + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + + if ( scope._domElementKeyEvents !== null ) { + + scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown ); + + } + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + const scope = this; + + const STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + let state = STATE.NONE; + + const EPS = 0.000001; + + // current position in spherical coordinates + const spherical = new Spherical(); + const sphericalDelta = new Spherical(); + + let scale = 1; + const panOffset = new Vector3(); + let zoomChanged = false; + + const rotateStart = new Vector2(); + const rotateEnd = new Vector2(); + const rotateDelta = new Vector2(); + + const panStart = new Vector2(); + const panEnd = new Vector2(); + const panDelta = new Vector2(); + + const dollyStart = new Vector2(); + const dollyEnd = new Vector2(); + const dollyDelta = new Vector2(); + + const pointers = []; + const pointerPositions = {}; + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + const panLeft = function () { + + const v = new Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + const panUp = function () { + + const v = new Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + const pan = function () { + + const offset = new Vector3(); + + return function pan( deltaX, deltaY ) { + + const element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + const position = scope.object.position; + offset.copy( position ).sub( scope.target ); + let targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyIn( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyOut( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + let needsUpdate = false; + + switch ( event.code ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate() { + + if ( pointers.length === 1 ) { + + rotateStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan() { + + if ( pointers.length === 1 ) { + + panStart.set( pointers[ 0 ].pageX, pointers[ 0 ].pageY ); + + } else { + + const x = 0.5 * ( pointers[ 0 ].pageX + pointers[ 1 ].pageX ); + const y = 0.5 * ( pointers[ 0 ].pageY + pointers[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly() { + + const dx = pointers[ 0 ].pageX - pointers[ 1 ].pageX; + const dy = pointers[ 0 ].pageY - pointers[ 1 ].pageY; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enablePan ) handleTouchStartPan(); + + } + + function handleTouchStartDollyRotate() { + + if ( scope.enableZoom ) handleTouchStartDolly(); + + if ( scope.enableRotate ) handleTouchStartRotate(); + + } + + function handleTouchMoveRotate( event ) { + + if ( pointers.length == 1 ) { + + rotateEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + const element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( pointers.length === 1 ) { + + panEnd.set( event.pageX, event.pageY ); + + } else { + + const position = getSecondPointerPosition( event ); + + const x = 0.5 * ( event.pageX + position.x ); + const y = 0.5 * ( event.pageY + position.y ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + const position = getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + + const distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyOut( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + if ( pointers.length === 0 ) { + + scope.domElement.setPointerCapture( event.pointerId ); + + scope.domElement.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.addEventListener( 'pointerup', onPointerUp ); + + } + + // + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { + + onMouseDown( event ); + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchMove( event ); + + } else { + + onMouseMove( event ); + + } + + } + + function onPointerUp( event ) { + + removePointer( event ); + + if ( pointers.length === 0 ) { + + scope.domElement.releasePointerCapture( event.pointerId ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + } + + scope.dispatchEvent( _endEvent ); + + state = STATE.NONE; + + } + + function onPointerCancel( event ) { + + removePointer( event ); + + } + + function onMouseDown( event ) { + + let mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE ) return; + + event.preventDefault(); + + scope.dispatchEvent( _startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( _endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + trackPointer( event ); + + switch ( pointers.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate(); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan(); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan(); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate(); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( _startEvent ); + + } + + } + + function onTouchMove( event ) { + + trackPointer( event ); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + function addPointer( event ) { + + pointers.push( event ); + + } + + function removePointer( event ) { + + delete pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < pointers.length; i ++ ) { + + if ( pointers[ i ].pointerId == event.pointerId ) { + + pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === pointers[ 0 ].pointerId ) ? pointers[ 1 ] : pointers[ 0 ]; + + return pointerPositions[ pointer.pointerId ]; + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu ); + + scope.domElement.addEventListener( 'pointerdown', onPointerDown ); + scope.domElement.addEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + + // force an update at start + + this.update(); + + } + +} + + +// This set of controls performs orbiting, dollying (zooming), and panning. +// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default). +// This is very similar to OrbitControls, another set of touch behavior +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or arrow keys / touch: one-finger move + +class MapControls extends OrbitControls { + + constructor( object, domElement ) { + + super( object, domElement ); + + this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + + } + +} + +export { OrbitControls, MapControls }; diff --git a/jsm/controls/PointerLockControls.js b/jsm/controls/PointerLockControls.js new file mode 100644 index 0000000..884a69c --- /dev/null +++ b/jsm/controls/PointerLockControls.js @@ -0,0 +1,164 @@ +import { + Euler, + EventDispatcher, + Vector3 +} from 'three'; + +const _euler = new Euler( 0, 0, 0, 'YXZ' ); +const _vector = new Vector3(); + +const _changeEvent = { type: 'change' }; +const _lockEvent = { type: 'lock' }; +const _unlockEvent = { type: 'unlock' }; + +const _PI_2 = Math.PI / 2; + +class PointerLockControls extends EventDispatcher { + + constructor( camera, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' ); + domElement = document.body; + + } + + this.domElement = domElement; + this.isLocked = false; + + // Set to constrain the pitch of the camera + // Range is 0 to Math.PI radians + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + this.pointerSpeed = 1.0; + + const scope = this; + + function onMouseMove( event ) { + + if ( scope.isLocked === false ) return; + + const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; + const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; + + _euler.setFromQuaternion( camera.quaternion ); + + _euler.y -= movementX * 0.002 * scope.pointerSpeed; + _euler.x -= movementY * 0.002 * scope.pointerSpeed; + + _euler.x = Math.max( _PI_2 - scope.maxPolarAngle, Math.min( _PI_2 - scope.minPolarAngle, _euler.x ) ); + + camera.quaternion.setFromEuler( _euler ); + + scope.dispatchEvent( _changeEvent ); + + } + + function onPointerlockChange() { + + if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) { + + scope.dispatchEvent( _lockEvent ); + + scope.isLocked = true; + + } else { + + scope.dispatchEvent( _unlockEvent ); + + scope.isLocked = false; + + } + + } + + function onPointerlockError() { + + console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' ); + + } + + this.connect = function () { + + scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove ); + scope.domElement.ownerDocument.addEventListener( 'pointerlockchange', onPointerlockChange ); + scope.domElement.ownerDocument.addEventListener( 'pointerlockerror', onPointerlockError ); + + }; + + this.disconnect = function () { + + scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove ); + scope.domElement.ownerDocument.removeEventListener( 'pointerlockchange', onPointerlockChange ); + scope.domElement.ownerDocument.removeEventListener( 'pointerlockerror', onPointerlockError ); + + }; + + this.dispose = function () { + + this.disconnect(); + + }; + + this.getObject = function () { // retaining this method for backward compatibility + + return camera; + + }; + + this.getDirection = function () { + + const direction = new Vector3( 0, 0, - 1 ); + + return function ( v ) { + + return v.copy( direction ).applyQuaternion( camera.quaternion ); + + }; + + }(); + + this.moveForward = function ( distance ) { + + // move forward parallel to the xz-plane + // assumes camera.up is y-up + + _vector.setFromMatrixColumn( camera.matrix, 0 ); + + _vector.crossVectors( camera.up, _vector ); + + camera.position.addScaledVector( _vector, distance ); + + }; + + this.moveRight = function ( distance ) { + + _vector.setFromMatrixColumn( camera.matrix, 0 ); + + camera.position.addScaledVector( _vector, distance ); + + }; + + this.lock = function () { + + this.domElement.requestPointerLock(); + + }; + + this.unlock = function () { + + scope.domElement.ownerDocument.exitPointerLock(); + + }; + + this.connect(); + + } + +} + +export { PointerLockControls }; diff --git a/jsm/controls/TrackballControls.js b/jsm/controls/TrackballControls.js new file mode 100644 index 0000000..2710f36 --- /dev/null +++ b/jsm/controls/TrackballControls.js @@ -0,0 +1,805 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Vector2, + Vector3 +} from 'three'; + +const _changeEvent = { type: 'change' }; +const _startEvent = { type: 'start' }; +const _endEvent = { type: 'end' }; + +class TrackballControls extends EventDispatcher { + + constructor( object, domElement ) { + + super(); + + if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + const scope = this; + const STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 }; + + this.object = object; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + // API + + this.enabled = true; + + this.screen = { left: 0, top: 0, width: 0, height: 0 }; + + this.rotateSpeed = 1.0; + this.zoomSpeed = 1.2; + this.panSpeed = 0.3; + + this.noRotate = false; + this.noZoom = false; + this.noPan = false; + + this.staticMoving = false; + this.dynamicDampingFactor = 0.2; + + this.minDistance = 0; + this.maxDistance = Infinity; + + this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ]; + + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // internals + + this.target = new Vector3(); + + const EPS = 0.000001; + + const lastPosition = new Vector3(); + let lastZoom = 1; + + let _state = STATE.NONE, + _keyState = STATE.NONE, + + _touchZoomDistanceStart = 0, + _touchZoomDistanceEnd = 0, + + _lastAngle = 0; + + const _eye = new Vector3(), + + _movePrev = new Vector2(), + _moveCurr = new Vector2(), + + _lastAxis = new Vector3(), + + _zoomStart = new Vector2(), + _zoomEnd = new Vector2(), + + _panStart = new Vector2(), + _panEnd = new Vector2(), + + _pointers = [], + _pointerPositions = {}; + + // for reset + + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.up0 = this.object.up.clone(); + this.zoom0 = this.object.zoom; + + // methods + + this.handleResize = function () { + + const box = scope.domElement.getBoundingClientRect(); + // adjustments come from similar code in the jquery offset() function + const d = scope.domElement.ownerDocument.documentElement; + scope.screen.left = box.left + window.pageXOffset - d.clientLeft; + scope.screen.top = box.top + window.pageYOffset - d.clientTop; + scope.screen.width = box.width; + scope.screen.height = box.height; + + }; + + const getMouseOnScreen = ( function () { + + const vector = new Vector2(); + + return function getMouseOnScreen( pageX, pageY ) { + + vector.set( + ( pageX - scope.screen.left ) / scope.screen.width, + ( pageY - scope.screen.top ) / scope.screen.height + ); + + return vector; + + }; + + }() ); + + const getMouseOnCircle = ( function () { + + const vector = new Vector2(); + + return function getMouseOnCircle( pageX, pageY ) { + + vector.set( + ( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ), + ( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional + ); + + return vector; + + }; + + }() ); + + this.rotateCamera = ( function () { + + const axis = new Vector3(), + quaternion = new Quaternion(), + eyeDirection = new Vector3(), + objectUpDirection = new Vector3(), + objectSidewaysDirection = new Vector3(), + moveDirection = new Vector3(); + + return function rotateCamera() { + + moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 ); + let angle = moveDirection.length(); + + if ( angle ) { + + _eye.copy( scope.object.position ).sub( scope.target ); + + eyeDirection.copy( _eye ).normalize(); + objectUpDirection.copy( scope.object.up ).normalize(); + objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize(); + + objectUpDirection.setLength( _moveCurr.y - _movePrev.y ); + objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x ); + + moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) ); + + axis.crossVectors( moveDirection, _eye ).normalize(); + + angle *= scope.rotateSpeed; + quaternion.setFromAxisAngle( axis, angle ); + + _eye.applyQuaternion( quaternion ); + scope.object.up.applyQuaternion( quaternion ); + + _lastAxis.copy( axis ); + _lastAngle = angle; + + } else if ( ! scope.staticMoving && _lastAngle ) { + + _lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor ); + _eye.copy( scope.object.position ).sub( scope.target ); + quaternion.setFromAxisAngle( _lastAxis, _lastAngle ); + _eye.applyQuaternion( quaternion ); + scope.object.up.applyQuaternion( quaternion ); + + } + + _movePrev.copy( _moveCurr ); + + }; + + }() ); + + + this.zoomCamera = function () { + + let factor; + + if ( _state === STATE.TOUCH_ZOOM_PAN ) { + + factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; + _touchZoomDistanceStart = _touchZoomDistanceEnd; + + if ( scope.object.isPerspectiveCamera ) { + + _eye.multiplyScalar( factor ); + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom /= factor; + scope.object.updateProjectionMatrix(); + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + } else { + + factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed; + + if ( factor !== 1.0 && factor > 0.0 ) { + + if ( scope.object.isPerspectiveCamera ) { + + _eye.multiplyScalar( factor ); + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom /= factor; + scope.object.updateProjectionMatrix(); + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + } + + if ( scope.staticMoving ) { + + _zoomStart.copy( _zoomEnd ); + + } else { + + _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; + + } + + } + + }; + + this.panCamera = ( function () { + + const mouseChange = new Vector2(), + objectUp = new Vector3(), + pan = new Vector3(); + + return function panCamera() { + + mouseChange.copy( _panEnd ).sub( _panStart ); + + if ( mouseChange.lengthSq() ) { + + if ( scope.object.isOrthographicCamera ) { + + const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth; + const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth; + + mouseChange.x *= scale_x; + mouseChange.y *= scale_y; + + } + + mouseChange.multiplyScalar( _eye.length() * scope.panSpeed ); + + pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x ); + pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) ); + + scope.object.position.add( pan ); + scope.target.add( pan ); + + if ( scope.staticMoving ) { + + _panStart.copy( _panEnd ); + + } else { + + _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) ); + + } + + } + + }; + + }() ); + + this.checkDistances = function () { + + if ( ! scope.noZoom || ! scope.noPan ) { + + if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) { + + scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) ); + _zoomStart.copy( _zoomEnd ); + + } + + if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) { + + scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) ); + _zoomStart.copy( _zoomEnd ); + + } + + } + + }; + + this.update = function () { + + _eye.subVectors( scope.object.position, scope.target ); + + if ( ! scope.noRotate ) { + + scope.rotateCamera(); + + } + + if ( ! scope.noZoom ) { + + scope.zoomCamera(); + + } + + if ( ! scope.noPan ) { + + scope.panCamera(); + + } + + scope.object.position.addVectors( scope.target, _eye ); + + if ( scope.object.isPerspectiveCamera ) { + + scope.checkDistances(); + + scope.object.lookAt( scope.target ); + + if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + + } + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.lookAt( scope.target ); + + if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) { + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastZoom = scope.object.zoom; + + } + + } else { + + console.warn( 'THREE.TrackballControls: Unsupported camera type' ); + + } + + }; + + this.reset = function () { + + _state = STATE.NONE; + _keyState = STATE.NONE; + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.up.copy( scope.up0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + + _eye.subVectors( scope.object.position, scope.target ); + + scope.object.lookAt( scope.target ); + + scope.dispatchEvent( _changeEvent ); + + lastPosition.copy( scope.object.position ); + lastZoom = scope.object.zoom; + + }; + + // listeners + + function onPointerDown( event ) { + + if ( scope.enabled === false ) return; + + if ( _pointers.length === 0 ) { + + scope.domElement.setPointerCapture( event.pointerId ); + + scope.domElement.addEventListener( 'pointermove', onPointerMove ); + scope.domElement.addEventListener( 'pointerup', onPointerUp ); + + } + + // + + addPointer( event ); + + if ( event.pointerType === 'touch' ) { + + onTouchStart( event ); + + } else { + + onMouseDown( event ); + + } + + } + + function onPointerMove( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchMove( event ); + + } else { + + onMouseMove( event ); + + } + + } + + function onPointerUp( event ) { + + if ( scope.enabled === false ) return; + + if ( event.pointerType === 'touch' ) { + + onTouchEnd( event ); + + } else { + + onMouseUp(); + + } + + // + + removePointer( event ); + + if ( _pointers.length === 0 ) { + + scope.domElement.releasePointerCapture( event.pointerId ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + } + + + } + + function onPointerCancel( event ) { + + removePointer( event ); + + } + + function keydown( event ) { + + if ( scope.enabled === false ) return; + + window.removeEventListener( 'keydown', keydown ); + + if ( _keyState !== STATE.NONE ) { + + return; + + } else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) { + + _keyState = STATE.ROTATE; + + } else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) { + + _keyState = STATE.ZOOM; + + } else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) { + + _keyState = STATE.PAN; + + } + + } + + function keyup() { + + if ( scope.enabled === false ) return; + + _keyState = STATE.NONE; + + window.addEventListener( 'keydown', keydown ); + + } + + function onMouseDown( event ) { + + if ( _state === STATE.NONE ) { + + switch ( event.button ) { + + case scope.mouseButtons.LEFT: + _state = STATE.ROTATE; + break; + + case scope.mouseButtons.MIDDLE: + _state = STATE.ZOOM; + break; + + case scope.mouseButtons.RIGHT: + _state = STATE.PAN; + break; + + } + + } + + const state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + + if ( state === STATE.ROTATE && ! scope.noRotate ) { + + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + _movePrev.copy( _moveCurr ); + + } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + + _zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + _zoomEnd.copy( _zoomStart ); + + } else if ( state === STATE.PAN && ! scope.noPan ) { + + _panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + _panEnd.copy( _panStart ); + + } + + scope.dispatchEvent( _startEvent ); + + } + + function onMouseMove( event ) { + + const state = ( _keyState !== STATE.NONE ) ? _keyState : _state; + + if ( state === STATE.ROTATE && ! scope.noRotate ) { + + _movePrev.copy( _moveCurr ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + + } else if ( state === STATE.ZOOM && ! scope.noZoom ) { + + _zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + + } else if ( state === STATE.PAN && ! scope.noPan ) { + + _panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) ); + + } + + } + + function onMouseUp() { + + _state = STATE.NONE; + + scope.dispatchEvent( _endEvent ); + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false ) return; + + if ( scope.noZoom === true ) return; + + event.preventDefault(); + + switch ( event.deltaMode ) { + + case 2: + // Zoom in pages + _zoomStart.y -= event.deltaY * 0.025; + break; + + case 1: + // Zoom in lines + _zoomStart.y -= event.deltaY * 0.01; + break; + + default: + // undefined, 0, assume pixels + _zoomStart.y -= event.deltaY * 0.00025; + break; + + } + + scope.dispatchEvent( _startEvent ); + scope.dispatchEvent( _endEvent ); + + } + + function onTouchStart( event ) { + + trackPointer( event ); + + switch ( _pointers.length ) { + + case 1: + _state = STATE.TOUCH_ROTATE; + _moveCurr.copy( getMouseOnCircle( _pointers[ 0 ].pageX, _pointers[ 0 ].pageY ) ); + _movePrev.copy( _moveCurr ); + break; + + default: // 2 or more + _state = STATE.TOUCH_ZOOM_PAN; + const dx = _pointers[ 0 ].pageX - _pointers[ 1 ].pageX; + const dy = _pointers[ 0 ].pageY - _pointers[ 1 ].pageY; + _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); + + const x = ( _pointers[ 0 ].pageX + _pointers[ 1 ].pageX ) / 2; + const y = ( _pointers[ 0 ].pageY + _pointers[ 1 ].pageY ) / 2; + _panStart.copy( getMouseOnScreen( x, y ) ); + _panEnd.copy( _panStart ); + break; + + } + + scope.dispatchEvent( _startEvent ); + + } + + function onTouchMove( event ) { + + trackPointer( event ); + + switch ( _pointers.length ) { + + case 1: + _movePrev.copy( _moveCurr ); + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + break; + + default: // 2 or more + + const position = getSecondPointerPosition( event ); + + const dx = event.pageX - position.x; + const dy = event.pageY - position.y; + _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ); + + const x = ( event.pageX + position.x ) / 2; + const y = ( event.pageY + position.y ) / 2; + _panEnd.copy( getMouseOnScreen( x, y ) ); + break; + + } + + } + + function onTouchEnd( event ) { + + switch ( _pointers.length ) { + + case 0: + _state = STATE.NONE; + break; + + case 1: + _state = STATE.TOUCH_ROTATE; + _moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) ); + _movePrev.copy( _moveCurr ); + break; + + case 2: + _state = STATE.TOUCH_ZOOM_PAN; + _moveCurr.copy( getMouseOnCircle( event.pageX - _movePrev.x, event.pageY - _movePrev.y ) ); + _movePrev.copy( _moveCurr ); + break; + + } + + scope.dispatchEvent( _endEvent ); + + } + + function contextmenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + function addPointer( event ) { + + _pointers.push( event ); + + } + + function removePointer( event ) { + + delete _pointerPositions[ event.pointerId ]; + + for ( let i = 0; i < _pointers.length; i ++ ) { + + if ( _pointers[ i ].pointerId == event.pointerId ) { + + _pointers.splice( i, 1 ); + return; + + } + + } + + } + + function trackPointer( event ) { + + let position = _pointerPositions[ event.pointerId ]; + + if ( position === undefined ) { + + position = new Vector2(); + _pointerPositions[ event.pointerId ] = position; + + } + + position.set( event.pageX, event.pageY ); + + } + + function getSecondPointerPosition( event ) { + + const pointer = ( event.pointerId === _pointers[ 0 ].pointerId ) ? _pointers[ 1 ] : _pointers[ 0 ]; + + return _pointerPositions[ pointer.pointerId ]; + + } + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', contextmenu ); + + scope.domElement.removeEventListener( 'pointerdown', onPointerDown ); + scope.domElement.removeEventListener( 'pointercancel', onPointerCancel ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel ); + + scope.domElement.removeEventListener( 'pointermove', onPointerMove ); + scope.domElement.removeEventListener( 'pointerup', onPointerUp ); + + window.removeEventListener( 'keydown', keydown ); + window.removeEventListener( 'keyup', keyup ); + + }; + + this.domElement.addEventListener( 'contextmenu', contextmenu ); + + this.domElement.addEventListener( 'pointerdown', onPointerDown ); + this.domElement.addEventListener( 'pointercancel', onPointerCancel ); + this.domElement.addEventListener( 'wheel', onMouseWheel, { passive: false } ); + + + window.addEventListener( 'keydown', keydown ); + window.addEventListener( 'keyup', keyup ); + + this.handleResize(); + + // force an update at start + this.update(); + + } + +} + +export { TrackballControls }; diff --git a/jsm/controls/TransformControls.js b/jsm/controls/TransformControls.js new file mode 100644 index 0000000..16fe1c1 --- /dev/null +++ b/jsm/controls/TransformControls.js @@ -0,0 +1,1556 @@ +import { + BoxGeometry, + BufferGeometry, + CylinderGeometry, + DoubleSide, + Euler, + Float32BufferAttribute, + Line, + LineBasicMaterial, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + OctahedronGeometry, + PlaneGeometry, + Quaternion, + Raycaster, + SphereGeometry, + TorusGeometry, + Vector3 +} from 'three'; + +const _raycaster = new Raycaster(); + +const _tempVector = new Vector3(); +const _tempVector2 = new Vector3(); +const _tempQuaternion = new Quaternion(); +const _unit = { + X: new Vector3( 1, 0, 0 ), + Y: new Vector3( 0, 1, 0 ), + Z: new Vector3( 0, 0, 1 ) +}; + +const _changeEvent = { type: 'change' }; +const _mouseDownEvent = { type: 'mouseDown' }; +const _mouseUpEvent = { type: 'mouseUp', mode: null }; +const _objectChangeEvent = { type: 'objectChange' }; + +class TransformControls extends Object3D { + + constructor( camera, domElement ) { + + super(); + + if ( domElement === undefined ) { + + console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' ); + domElement = document; + + } + + this.visible = false; + this.domElement = domElement; + this.domElement.style.touchAction = 'none'; // disable touch scroll + + const _gizmo = new TransformControlsGizmo(); + this._gizmo = _gizmo; + this.add( _gizmo ); + + const _plane = new TransformControlsPlane(); + this._plane = _plane; + this.add( _plane ); + + const scope = this; + + // Defined getter, setter and store for a property + function defineProperty( propName, defaultValue ) { + + let propValue = defaultValue; + + Object.defineProperty( scope, propName, { + + get: function () { + + return propValue !== undefined ? propValue : defaultValue; + + }, + + set: function ( value ) { + + if ( propValue !== value ) { + + propValue = value; + _plane[ propName ] = value; + _gizmo[ propName ] = value; + + scope.dispatchEvent( { type: propName + '-changed', value: value } ); + scope.dispatchEvent( _changeEvent ); + + } + + } + + } ); + + scope[ propName ] = defaultValue; + _plane[ propName ] = defaultValue; + _gizmo[ propName ] = defaultValue; + + } + + // Define properties with getters/setter + // Setting the defined property will automatically trigger change event + // Defined properties are passed down to gizmo and plane + + defineProperty( 'camera', camera ); + defineProperty( 'object', undefined ); + defineProperty( 'enabled', true ); + defineProperty( 'axis', null ); + defineProperty( 'mode', 'translate' ); + defineProperty( 'translationSnap', null ); + defineProperty( 'rotationSnap', null ); + defineProperty( 'scaleSnap', null ); + defineProperty( 'space', 'world' ); + defineProperty( 'size', 1 ); + defineProperty( 'dragging', false ); + defineProperty( 'showX', true ); + defineProperty( 'showY', true ); + defineProperty( 'showZ', true ); + + // Reusable utility variables + + const worldPosition = new Vector3(); + const worldPositionStart = new Vector3(); + const worldQuaternion = new Quaternion(); + const worldQuaternionStart = new Quaternion(); + const cameraPosition = new Vector3(); + const cameraQuaternion = new Quaternion(); + const pointStart = new Vector3(); + const pointEnd = new Vector3(); + const rotationAxis = new Vector3(); + const rotationAngle = 0; + const eye = new Vector3(); + + // TODO: remove properties unused in plane and gizmo + + defineProperty( 'worldPosition', worldPosition ); + defineProperty( 'worldPositionStart', worldPositionStart ); + defineProperty( 'worldQuaternion', worldQuaternion ); + defineProperty( 'worldQuaternionStart', worldQuaternionStart ); + defineProperty( 'cameraPosition', cameraPosition ); + defineProperty( 'cameraQuaternion', cameraQuaternion ); + defineProperty( 'pointStart', pointStart ); + defineProperty( 'pointEnd', pointEnd ); + defineProperty( 'rotationAxis', rotationAxis ); + defineProperty( 'rotationAngle', rotationAngle ); + defineProperty( 'eye', eye ); + + this._offset = new Vector3(); + this._startNorm = new Vector3(); + this._endNorm = new Vector3(); + this._cameraScale = new Vector3(); + + this._parentPosition = new Vector3(); + this._parentQuaternion = new Quaternion(); + this._parentQuaternionInv = new Quaternion(); + this._parentScale = new Vector3(); + + this._worldScaleStart = new Vector3(); + this._worldQuaternionInv = new Quaternion(); + this._worldScale = new Vector3(); + + this._positionStart = new Vector3(); + this._quaternionStart = new Quaternion(); + this._scaleStart = new Vector3(); + + this._getPointer = getPointer.bind( this ); + this._onPointerDown = onPointerDown.bind( this ); + this._onPointerHover = onPointerHover.bind( this ); + this._onPointerMove = onPointerMove.bind( this ); + this._onPointerUp = onPointerUp.bind( this ); + + this.domElement.addEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.addEventListener( 'pointermove', this._onPointerHover ); + this.domElement.addEventListener( 'pointerup', this._onPointerUp ); + + } + + // updateMatrixWorld updates key transformation variables + updateMatrixWorld() { + + if ( this.object !== undefined ) { + + this.object.updateMatrixWorld(); + + if ( this.object.parent === null ) { + + console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' ); + + } else { + + this.object.parent.matrixWorld.decompose( this._parentPosition, this._parentQuaternion, this._parentScale ); + + } + + this.object.matrixWorld.decompose( this.worldPosition, this.worldQuaternion, this._worldScale ); + + this._parentQuaternionInv.copy( this._parentQuaternion ).invert(); + this._worldQuaternionInv.copy( this.worldQuaternion ).invert(); + + } + + this.camera.updateMatrixWorld(); + this.camera.matrixWorld.decompose( this.cameraPosition, this.cameraQuaternion, this._cameraScale ); + + this.eye.copy( this.cameraPosition ).sub( this.worldPosition ).normalize(); + + super.updateMatrixWorld( this ); + + } + + pointerHover( pointer ) { + + if ( this.object === undefined || this.dragging === true ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster ); + + if ( intersect ) { + + this.axis = intersect.object.name; + + } else { + + this.axis = null; + + } + + } + + pointerDown( pointer ) { + + if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return; + + if ( this.axis !== null ) { + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + + if ( planeIntersect ) { + + this.object.updateMatrixWorld(); + this.object.parent.updateMatrixWorld(); + + this._positionStart.copy( this.object.position ); + this._quaternionStart.copy( this.object.quaternion ); + this._scaleStart.copy( this.object.scale ); + + this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart ); + + this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + } + + this.dragging = true; + _mouseDownEvent.mode = this.mode; + this.dispatchEvent( _mouseDownEvent ); + + } + + } + + pointerMove( pointer ) { + + const axis = this.axis; + const mode = this.mode; + const object = this.object; + let space = this.space; + + if ( mode === 'scale' ) { + + space = 'local'; + + } else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) { + + space = 'world'; + + } + + if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return; + + _raycaster.setFromCamera( pointer, this.camera ); + + const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true ); + + if ( ! planeIntersect ) return; + + this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart ); + + if ( mode === 'translate' ) { + + // Apply translate + + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._worldQuaternionInv ); + + } + + if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0; + if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0; + if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0; + + if ( space === 'local' && axis !== 'XYZ' ) { + + this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale ); + + } else { + + this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale ); + + } + + object.position.copy( this._offset ).add( this._positionStart ); + + // Apply translation snap + + if ( this.translationSnap ) { + + if ( space === 'local' ) { + + object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() ); + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + object.position.applyQuaternion( this._quaternionStart ); + + } + + if ( space === 'world' ) { + + if ( object.parent ) { + + object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + if ( axis.search( 'X' ) !== - 1 ) { + + object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap; + + } + + if ( object.parent ) { + + object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) ); + + } + + } + + } + + } else if ( mode === 'scale' ) { + + if ( axis.search( 'XYZ' ) !== - 1 ) { + + let d = this.pointEnd.length() / this.pointStart.length(); + + if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1; + + _tempVector2.set( d, d, d ); + + } else { + + _tempVector.copy( this.pointStart ); + _tempVector2.copy( this.pointEnd ); + + _tempVector.applyQuaternion( this._worldQuaternionInv ); + _tempVector2.applyQuaternion( this._worldQuaternionInv ); + + _tempVector2.divide( _tempVector ); + + if ( axis.search( 'X' ) === - 1 ) { + + _tempVector2.x = 1; + + } + + if ( axis.search( 'Y' ) === - 1 ) { + + _tempVector2.y = 1; + + } + + if ( axis.search( 'Z' ) === - 1 ) { + + _tempVector2.z = 1; + + } + + } + + // Apply scale + + object.scale.copy( this._scaleStart ).multiply( _tempVector2 ); + + if ( this.scaleSnap ) { + + if ( axis.search( 'X' ) !== - 1 ) { + + object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Y' ) !== - 1 ) { + + object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + if ( axis.search( 'Z' ) !== - 1 ) { + + object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap; + + } + + } + + } else if ( mode === 'rotate' ) { + + this._offset.copy( this.pointEnd ).sub( this.pointStart ); + + const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) ); + + if ( axis === 'E' ) { + + this.rotationAxis.copy( this.eye ); + this.rotationAngle = this.pointEnd.angleTo( this.pointStart ); + + this._startNorm.copy( this.pointStart ).normalize(); + this._endNorm.copy( this.pointEnd ).normalize(); + + this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 ); + + } else if ( axis === 'XYZE' ) { + + this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize(); + this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED; + + } else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) { + + this.rotationAxis.copy( _unit[ axis ] ); + + _tempVector.copy( _unit[ axis ] ); + + if ( space === 'local' ) { + + _tempVector.applyQuaternion( this.worldQuaternion ); + + } + + this.rotationAngle = this._offset.dot( _tempVector.cross( this.eye ).normalize() ) * ROTATION_SPEED; + + } + + // Apply rotation snap + + if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap; + + // Apply rotate + if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) { + + object.quaternion.copy( this._quaternionStart ); + object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize(); + + } else { + + this.rotationAxis.applyQuaternion( this._parentQuaternionInv ); + object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ); + object.quaternion.multiply( this._quaternionStart ).normalize(); + + } + + } + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _objectChangeEvent ); + + } + + pointerUp( pointer ) { + + if ( pointer.button !== 0 ) return; + + if ( this.dragging && ( this.axis !== null ) ) { + + _mouseUpEvent.mode = this.mode; + this.dispatchEvent( _mouseUpEvent ); + + } + + this.dragging = false; + this.axis = null; + + } + + dispose() { + + this.domElement.removeEventListener( 'pointerdown', this._onPointerDown ); + this.domElement.removeEventListener( 'pointermove', this._onPointerHover ); + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + this.domElement.removeEventListener( 'pointerup', this._onPointerUp ); + + this.traverse( function ( child ) { + + if ( child.geometry ) child.geometry.dispose(); + if ( child.material ) child.material.dispose(); + + } ); + + } + + // Set current object + attach( object ) { + + this.object = object; + this.visible = true; + + return this; + + } + + // Detatch from object + detach() { + + this.object = undefined; + this.visible = false; + this.axis = null; + + return this; + + } + + reset() { + + if ( ! this.enabled ) return; + + if ( this.dragging ) { + + this.object.position.copy( this._positionStart ); + this.object.quaternion.copy( this._quaternionStart ); + this.object.scale.copy( this._scaleStart ); + + this.dispatchEvent( _changeEvent ); + this.dispatchEvent( _objectChangeEvent ); + + this.pointStart.copy( this.pointEnd ); + + } + + } + + getRaycaster() { + + return _raycaster; + + } + + // TODO: deprecate + + getMode() { + + return this.mode; + + } + + setMode( mode ) { + + this.mode = mode; + + } + + setTranslationSnap( translationSnap ) { + + this.translationSnap = translationSnap; + + } + + setRotationSnap( rotationSnap ) { + + this.rotationSnap = rotationSnap; + + } + + setScaleSnap( scaleSnap ) { + + this.scaleSnap = scaleSnap; + + } + + setSize( size ) { + + this.size = size; + + } + + setSpace( space ) { + + this.space = space; + + } + + update() { + + console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' ); + + } + +} + +TransformControls.prototype.isTransformControls = true; + +// mouse / touch event handlers + +function getPointer( event ) { + + if ( this.domElement.ownerDocument.pointerLockElement ) { + + return { + x: 0, + y: 0, + button: event.button + }; + + } else { + + const rect = this.domElement.getBoundingClientRect(); + + return { + x: ( event.clientX - rect.left ) / rect.width * 2 - 1, + y: - ( event.clientY - rect.top ) / rect.height * 2 + 1, + button: event.button + }; + + } + +} + +function onPointerHover( event ) { + + if ( ! this.enabled ) return; + + switch ( event.pointerType ) { + + case 'mouse': + case 'pen': + this.pointerHover( this._getPointer( event ) ); + break; + + } + +} + +function onPointerDown( event ) { + + if ( ! this.enabled ) return; + + if ( ! document.pointerLockElement ) { + + this.domElement.setPointerCapture( event.pointerId ); + + } + + this.domElement.addEventListener( 'pointermove', this._onPointerMove ); + + this.pointerHover( this._getPointer( event ) ); + this.pointerDown( this._getPointer( event ) ); + +} + +function onPointerMove( event ) { + + if ( ! this.enabled ) return; + + this.pointerMove( this._getPointer( event ) ); + +} + +function onPointerUp( event ) { + + if ( ! this.enabled ) return; + + this.domElement.releasePointerCapture( event.pointerId ); + + this.domElement.removeEventListener( 'pointermove', this._onPointerMove ); + + this.pointerUp( this._getPointer( event ) ); + +} + +function intersectObjectWithRay( object, raycaster, includeInvisible ) { + + const allIntersections = raycaster.intersectObject( object, true ); + + for ( let i = 0; i < allIntersections.length; i ++ ) { + + if ( allIntersections[ i ].object.visible || includeInvisible ) { + + return allIntersections[ i ]; + + } + + } + + return false; + +} + +// + +// Reusable utility variables + +const _tempEuler = new Euler(); +const _alignVector = new Vector3( 0, 1, 0 ); +const _zeroVector = new Vector3( 0, 0, 0 ); +const _lookAtMatrix = new Matrix4(); +const _tempQuaternion2 = new Quaternion(); +const _identityQuaternion = new Quaternion(); +const _dirVector = new Vector3(); +const _tempMatrix = new Matrix4(); + +const _unitX = new Vector3( 1, 0, 0 ); +const _unitY = new Vector3( 0, 1, 0 ); +const _unitZ = new Vector3( 0, 0, 1 ); + +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _v3 = new Vector3(); + +class TransformControlsGizmo extends Object3D { + + constructor() { + + super(); + + this.type = 'TransformControlsGizmo'; + + // shared materials + + const gizmoMaterial = new MeshBasicMaterial( { + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true + } ); + + const gizmoLineMaterial = new LineBasicMaterial( { + depthTest: false, + depthWrite: false, + fog: false, + toneMapped: false, + transparent: true + } ); + + // Make unique material for each axis/color + + const matInvisible = gizmoMaterial.clone(); + matInvisible.opacity = 0.15; + + const matHelper = gizmoLineMaterial.clone(); + matHelper.opacity = 0.5; + + const matRed = gizmoMaterial.clone(); + matRed.color.setHex( 0xff0000 ); + + const matGreen = gizmoMaterial.clone(); + matGreen.color.setHex( 0x00ff00 ); + + const matBlue = gizmoMaterial.clone(); + matBlue.color.setHex( 0x0000ff ); + + const matRedTransparent = gizmoMaterial.clone(); + matRedTransparent.color.setHex( 0xff0000 ); + matRedTransparent.opacity = 0.5; + + const matGreenTransparent = gizmoMaterial.clone(); + matGreenTransparent.color.setHex( 0x00ff00 ); + matGreenTransparent.opacity = 0.5; + + const matBlueTransparent = gizmoMaterial.clone(); + matBlueTransparent.color.setHex( 0x0000ff ); + matBlueTransparent.opacity = 0.5; + + const matWhiteTransparent = gizmoMaterial.clone(); + matWhiteTransparent.opacity = 0.25; + + const matYellowTransparent = gizmoMaterial.clone(); + matYellowTransparent.color.setHex( 0xffff00 ); + matYellowTransparent.opacity = 0.25; + + const matYellow = gizmoMaterial.clone(); + matYellow.color.setHex( 0xffff00 ); + + const matGray = gizmoMaterial.clone(); + matGray.color.setHex( 0x787878 ); + + // reusable geometry + + const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 ); + arrowGeometry.translate( 0, 0.05, 0 ); + + const scaleHandleGeometry = new BoxGeometry( 0.08, 0.08, 0.08 ); + scaleHandleGeometry.translate( 0, 0.04, 0 ); + + const lineGeometry = new BufferGeometry(); + lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) ); + + const lineGeometry2 = new CylinderGeometry( 0.0075, 0.0075, 0.5, 3 ); + lineGeometry2.translate( 0, 0.25, 0 ); + + function CircleGeometry( radius, arc ) { + + const geometry = new TorusGeometry( radius, 0.0075, 3, 64, arc * Math.PI * 2 ); + geometry.rotateY( Math.PI / 2 ); + geometry.rotateX( Math.PI / 2 ); + return geometry; + + } + + // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position + + function TranslateHelperGeometry() { + + const geometry = new BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) ); + + return geometry; + + } + + // Gizmo definitions - custom hierarchy definitions for setupGizmo() function + + const gizmoTranslate = { + X: [ + [ new Mesh( arrowGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( arrowGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], + [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( arrowGeometry, matGreen ), [ 0, 0.5, 0 ]], + [ new Mesh( arrowGeometry, matGreen ), [ 0, - 0.5, 0 ], [ Math.PI, 0, 0 ]], + [ new Mesh( lineGeometry2, matGreen ) ] + ], + Z: [ + [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( arrowGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]], + [ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ] + }; + + const pickerTranslate = { + X: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] + ], + Z: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new OctahedronGeometry( 0.2, 0 ), matInvisible ) ] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ] + }; + + const helperTranslate = { + START: [ + [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] + ], + END: [ + [ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ] + ], + DELTA: [ + [ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ] + ], + X: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ], + Y: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + ], + Z: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + const gizmoRotate = { + XYZE: [ + [ new Mesh( CircleGeometry( 0.5, 1 ), matGray ), null, [ 0, Math.PI / 2, 0 ]] + ], + X: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matRed ) ] + ], + Y: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matGreen ), null, [ 0, 0, - Math.PI / 2 ]] + ], + Z: [ + [ new Mesh( CircleGeometry( 0.5, 0.5 ), matBlue ), null, [ 0, Math.PI / 2, 0 ]] + ], + E: [ + [ new Mesh( CircleGeometry( 0.75, 1 ), matYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]] + ] + }; + + const helperRotate = { + AXIS: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + const pickerRotate = { + XYZE: [ + [ new Mesh( new SphereGeometry( 0.25, 10, 8 ), matInvisible ) ] + ], + X: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]], + ], + Y: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], + ], + Z: [ + [ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + ], + E: [ + [ new Mesh( new TorusGeometry( 0.75, 0.1, 2, 24 ), matInvisible ) ] + ] + }; + + const gizmoScale = { + X: [ + [ new Mesh( scaleHandleGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( scaleHandleGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]], + ], + Y: [ + [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.5, 0 ]], + [ new Mesh( lineGeometry2, matGreen ) ], + [ new Mesh( scaleHandleGeometry, matGreen ), [ 0, - 0.5, 0 ], [ 0, 0, Math.PI ]], + ], + Z: [ + [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( lineGeometry2, matBlue ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]] + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]] + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]] + ], + XYZ: [ + [ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ], + ] + }; + + const pickerScale = { + X: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]] + ], + Y: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]] + ], + Z: [ + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]], + [ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]] + ], + XY: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]], + ], + YZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]], + ], + XZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]], + ], + XYZ: [ + [ new Mesh( new BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 0 ]], + ] + }; + + const helperScale = { + X: [ + [ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ] + ], + Y: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ] + ], + Z: [ + [ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ] + ] + }; + + // Creates an Object3D with gizmos described in custom hierarchy definition. + + function setupGizmo( gizmoMap ) { + + const gizmo = new Object3D(); + + for ( const name in gizmoMap ) { + + for ( let i = gizmoMap[ name ].length; i --; ) { + + const object = gizmoMap[ name ][ i ][ 0 ].clone(); + const position = gizmoMap[ name ][ i ][ 1 ]; + const rotation = gizmoMap[ name ][ i ][ 2 ]; + const scale = gizmoMap[ name ][ i ][ 3 ]; + const tag = gizmoMap[ name ][ i ][ 4 ]; + + // name and tag properties are essential for picking and updating logic. + object.name = name; + object.tag = tag; + + if ( position ) { + + object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] ); + + } + + if ( rotation ) { + + object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] ); + + } + + if ( scale ) { + + object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] ); + + } + + object.updateMatrix(); + + const tempGeometry = object.geometry.clone(); + tempGeometry.applyMatrix4( object.matrix ); + object.geometry = tempGeometry; + object.renderOrder = Infinity; + + object.position.set( 0, 0, 0 ); + object.rotation.set( 0, 0, 0 ); + object.scale.set( 1, 1, 1 ); + + gizmo.add( object ); + + } + + } + + return gizmo; + + } + + // Gizmo creation + + this.gizmo = {}; + this.picker = {}; + this.helper = {}; + + this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) ); + this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) ); + this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) ); + this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) ); + this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) ); + this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) ); + this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) ); + this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) ); + this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) ); + + // Pickers should be hidden always + + this.picker[ 'translate' ].visible = false; + this.picker[ 'rotate' ].visible = false; + this.picker[ 'scale' ].visible = false; + + } + + // updateMatrixWorld will update transformations and appearance of individual handles + + updateMatrixWorld( force ) { + + const space = ( this.mode === 'scale' ) ? 'local' : this.space; // scale always oriented to local rotation + + const quaternion = ( space === 'local' ) ? this.worldQuaternion : _identityQuaternion; + + // Show only gizmos for current transform mode + + this.gizmo[ 'translate' ].visible = this.mode === 'translate'; + this.gizmo[ 'rotate' ].visible = this.mode === 'rotate'; + this.gizmo[ 'scale' ].visible = this.mode === 'scale'; + + this.helper[ 'translate' ].visible = this.mode === 'translate'; + this.helper[ 'rotate' ].visible = this.mode === 'rotate'; + this.helper[ 'scale' ].visible = this.mode === 'scale'; + + + let handles = []; + handles = handles.concat( this.picker[ this.mode ].children ); + handles = handles.concat( this.gizmo[ this.mode ].children ); + handles = handles.concat( this.helper[ this.mode ].children ); + + for ( let i = 0; i < handles.length; i ++ ) { + + const handle = handles[ i ]; + + // hide aligned to camera + + handle.visible = true; + handle.rotation.set( 0, 0, 0 ); + handle.position.copy( this.worldPosition ); + + let factor; + + if ( this.camera.isOrthographicCamera ) { + + factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom; + + } else { + + factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 ); + + } + + handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 4 ); + + // TODO: simplify helpers and consider decoupling from gizmo + + if ( handle.tag === 'helper' ) { + + handle.visible = false; + + if ( handle.name === 'AXIS' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = !! this.axis; + + if ( this.axis === 'X' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'Y' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, Math.PI / 2 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'Z' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); + handle.quaternion.copy( quaternion ).multiply( _tempQuaternion ); + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) { + + handle.visible = false; + + } + + } + + if ( this.axis === 'XYZE' ) { + + _tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) ); + _alignVector.copy( this.rotationAxis ); + handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) ); + handle.quaternion.multiply( _tempQuaternion ); + handle.visible = this.dragging; + + } + + if ( this.axis === 'E' ) { + + handle.visible = false; + + } + + + } else if ( handle.name === 'START' ) { + + handle.position.copy( this.worldPositionStart ); + handle.visible = this.dragging; + + } else if ( handle.name === 'END' ) { + + handle.position.copy( this.worldPosition ); + handle.visible = this.dragging; + + } else if ( handle.name === 'DELTA' ) { + + handle.position.copy( this.worldPositionStart ); + handle.quaternion.copy( this.worldQuaternionStart ); + _tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 ); + _tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() ); + handle.scale.copy( _tempVector ); + handle.visible = this.dragging; + + } else { + + handle.quaternion.copy( quaternion ); + + if ( this.dragging ) { + + handle.position.copy( this.worldPositionStart ); + + } else { + + handle.position.copy( this.worldPosition ); + + } + + if ( this.axis ) { + + handle.visible = this.axis.search( handle.name ) !== - 1; + + } + + } + + // If updating helper, skip rest of the loop + continue; + + } + + // Align handles to current local or world rotation + + handle.quaternion.copy( quaternion ); + + if ( this.mode === 'translate' || this.mode === 'scale' ) { + + // Hide translate and scale axis facing the camera + + const AXIS_HIDE_TRESHOLD = 0.99; + const PLANE_HIDE_TRESHOLD = 0.2; + + if ( handle.name === 'X' ) { + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Y' ) { + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'Z' ) { + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'XY' ) { + + if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'YZ' ) { + + if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + if ( handle.name === 'XZ' ) { + + if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) { + + handle.scale.set( 1e-10, 1e-10, 1e-10 ); + handle.visible = false; + + } + + } + + } else if ( this.mode === 'rotate' ) { + + // Align handles to current local or world rotation + + _tempQuaternion2.copy( quaternion ); + _alignVector.copy( this.eye ).applyQuaternion( _tempQuaternion.copy( quaternion ).invert() ); + + if ( handle.name.search( 'E' ) !== - 1 ) { + + handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) ); + + } + + if ( handle.name === 'X' ) { + + _tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Y' ) { + + _tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + if ( handle.name === 'Z' ) { + + _tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) ); + _tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion ); + handle.quaternion.copy( _tempQuaternion ); + + } + + } + + // Hide disabled axes + handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX ); + handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY ); + handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ ); + handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) ); + + // highlight selected axis + + handle.material._color = handle.material._color || handle.material.color.clone(); + handle.material._opacity = handle.material._opacity || handle.material.opacity; + + handle.material.color.copy( handle.material._color ); + handle.material.opacity = handle.material._opacity; + + if ( this.enabled && this.axis ) { + + if ( handle.name === this.axis ) { + + handle.material.color.setHex( 0xffff00 ); + handle.material.opacity = 1.0; + + } else if ( this.axis.split( '' ).some( function ( a ) { + + return handle.name === a; + + } ) ) { + + handle.material.color.setHex( 0xffff00 ); + handle.material.opacity = 1.0; + + } + + } + + } + + super.updateMatrixWorld( force ); + + } + +} + +TransformControlsGizmo.prototype.isTransformControlsGizmo = true; + +// + +class TransformControlsPlane extends Mesh { + + constructor() { + + super( + new PlaneGeometry( 100000, 100000, 2, 2 ), + new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } ) + ); + + this.type = 'TransformControlsPlane'; + + } + + updateMatrixWorld( force ) { + + let space = this.space; + + this.position.copy( this.worldPosition ); + + if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation + + _v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + _v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + _v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion ); + + // Align the plane for current transform mode, axis and space. + + _alignVector.copy( _v2 ); + + switch ( this.mode ) { + + case 'translate': + case 'scale': + switch ( this.axis ) { + + case 'X': + _alignVector.copy( this.eye ).cross( _v1 ); + _dirVector.copy( _v1 ).cross( _alignVector ); + break; + case 'Y': + _alignVector.copy( this.eye ).cross( _v2 ); + _dirVector.copy( _v2 ).cross( _alignVector ); + break; + case 'Z': + _alignVector.copy( this.eye ).cross( _v3 ); + _dirVector.copy( _v3 ).cross( _alignVector ); + break; + case 'XY': + _dirVector.copy( _v3 ); + break; + case 'YZ': + _dirVector.copy( _v1 ); + break; + case 'XZ': + _alignVector.copy( _v3 ); + _dirVector.copy( _v2 ); + break; + case 'XYZ': + case 'E': + _dirVector.set( 0, 0, 0 ); + break; + + } + + break; + case 'rotate': + default: + // special case for rotate + _dirVector.set( 0, 0, 0 ); + + } + + if ( _dirVector.length() === 0 ) { + + // If in rotate mode, make the plane parallel to camera + this.quaternion.copy( this.cameraQuaternion ); + + } else { + + _tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector ); + + this.quaternion.setFromRotationMatrix( _tempMatrix ); + + } + + super.updateMatrixWorld( force ); + + } + +} + +TransformControlsPlane.prototype.isTransformControlsPlane = true; + +export { TransformControls, TransformControlsGizmo, TransformControlsPlane }; diff --git a/jsm/controls/experimental/CameraControls.js b/jsm/controls/experimental/CameraControls.js new file mode 100644 index 0000000..25375af --- /dev/null +++ b/jsm/controls/experimental/CameraControls.js @@ -0,0 +1,1248 @@ +import { + EventDispatcher, + MOUSE, + Quaternion, + Spherical, + TOUCH, + Vector2, + Vector3 +} from 'three'; + +var CameraControls = function ( object, domElement ) { + + if ( domElement === undefined ) console.warn( 'THREE.CameraControls: The second parameter "domElement" is now mandatory.' ); + if ( domElement === document ) console.error( 'THREE.CameraControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' ); + + this.object = object; + this.domElement = domElement; + + // Set to false to disable this control + this.enabled = true; + + // "target" sets the location of focus, where the object orbits around + this.target = new Vector3(); + + // Set to true to enable trackball behavior + this.trackball = false; + + // How far you can dolly in and out ( PerspectiveCamera only ) + this.minDistance = 0; + this.maxDistance = Infinity; + + // How far you can zoom in and out ( OrthographicCamera only ) + this.minZoom = 0; + this.maxZoom = Infinity; + + // How far you can orbit vertically, upper and lower limits. + // Range is 0 to Math.PI radians. + this.minPolarAngle = 0; // radians + this.maxPolarAngle = Math.PI; // radians + + // How far you can orbit horizontally, upper and lower limits. + // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ]. + this.minAzimuthAngle = - Infinity; // radians + this.maxAzimuthAngle = Infinity; // radians + + // Set to true to enable damping (inertia) + // If damping is enabled, you must call controls.update() in your animation loop + this.enableDamping = false; + this.dampingFactor = 0.05; + + // This option enables dollying in and out; property named as "zoom" for backwards compatibility + // Set to false to disable zooming + this.enableZoom = true; + this.zoomSpeed = 1.0; + + // Set to false to disable rotating + this.enableRotate = true; + this.rotateSpeed = 1.0; + + // Set to false to disable panning + this.enablePan = true; + this.panSpeed = 1.0; + this.screenSpacePanning = false; // if true, pan in screen-space + this.keyPanSpeed = 7.0; // pixels moved per arrow key push + + // Set to true to automatically rotate around the target + // If auto-rotate is enabled, you must call controls.update() in your animation loop + // auto-rotate is not supported for trackball behavior + this.autoRotate = false; + this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60 + + // Set to false to disable use of the keys + this.enableKeys = true; + + // The four arrow keys + this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 }; + + // Mouse buttons + this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; + + // Touch fingers + this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN }; + + // for reset + this.target0 = this.target.clone(); + this.position0 = this.object.position.clone(); + this.quaternion0 = this.object.quaternion.clone(); + this.zoom0 = this.object.zoom; + + // + // public methods + // + + this.getPolarAngle = function () { + + return spherical.phi; + + }; + + this.getAzimuthalAngle = function () { + + return spherical.theta; + + }; + + this.saveState = function () { + + scope.target0.copy( scope.target ); + scope.position0.copy( scope.object.position ); + scope.quaternion0.copy( scope.object.quaternion ); + scope.zoom0 = scope.object.zoom; + + }; + + this.reset = function () { + + scope.target.copy( scope.target0 ); + scope.object.position.copy( scope.position0 ); + scope.object.quaternion.copy( scope.quaternion0 ); + scope.object.zoom = scope.zoom0; + + scope.object.updateProjectionMatrix(); + scope.dispatchEvent( changeEvent ); + + scope.update(); + + state = STATE.NONE; + + }; + + // this method is exposed, but perhaps it would be better if we can make it private... + this.update = function () { + + var offset = new Vector3(); + + // so camera.up is the orbit axis + var quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) ); + var quatInverse = quat.clone().invert(); + + var lastPosition = new Vector3(); + var lastQuaternion = new Quaternion(); + + var q = new Quaternion(); + var vec = new Vector3(); + + return function update() { + + var position = scope.object.position; + + offset.copy( position ).sub( scope.target ); + + if ( scope.trackball ) { + + // rotate around screen-space y-axis + + if ( sphericalDelta.theta ) { + + vec.set( 0, 1, 0 ).applyQuaternion( scope.object.quaternion ); + + const factor = ( scope.enableDamping ) ? scope.dampingFactor : 1; + + q.setFromAxisAngle( vec, sphericalDelta.theta * factor ); + + scope.object.quaternion.premultiply( q ); + offset.applyQuaternion( q ); + + } + + // rotate around screen-space x-axis + + if ( sphericalDelta.phi ) { + + vec.set( 1, 0, 0 ).applyQuaternion( scope.object.quaternion ); + + const factor = ( scope.enableDamping ) ? scope.dampingFactor : 1; + + q.setFromAxisAngle( vec, sphericalDelta.phi * factor ); + + scope.object.quaternion.premultiply( q ); + offset.applyQuaternion( q ); + + } + + offset.multiplyScalar( scale ); + offset.clampLength( scope.minDistance, scope.maxDistance ); + + } else { + + // rotate offset to "y-axis-is-up" space + offset.applyQuaternion( quat ); + + if ( scope.autoRotate && state === STATE.NONE ) { + + rotateLeft( getAutoRotationAngle() ); + + } + + spherical.setFromVector3( offset ); + + if ( scope.enableDamping ) { + + spherical.theta += sphericalDelta.theta * scope.dampingFactor; + spherical.phi += sphericalDelta.phi * scope.dampingFactor; + + } else { + + spherical.theta += sphericalDelta.theta; + spherical.phi += sphericalDelta.phi; + + } + + // restrict theta to be between desired limits + spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); + + // restrict phi to be between desired limits + spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) ); + + spherical.makeSafe(); + + spherical.radius *= scale; + + // restrict radius to be between desired limits + spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); + + offset.setFromSpherical( spherical ); + + // rotate offset back to "camera-up-vector-is-up" space + offset.applyQuaternion( quatInverse ); + + } + + // move target to panned location + + if ( scope.enableDamping === true ) { + + scope.target.addScaledVector( panOffset, scope.dampingFactor ); + + } else { + + scope.target.add( panOffset ); + + } + + position.copy( scope.target ).add( offset ); + + if ( scope.trackball === false ) { + + scope.object.lookAt( scope.target ); + + } + + if ( scope.enableDamping === true ) { + + sphericalDelta.theta *= ( 1 - scope.dampingFactor ); + sphericalDelta.phi *= ( 1 - scope.dampingFactor ); + + panOffset.multiplyScalar( 1 - scope.dampingFactor ); + + } else { + + sphericalDelta.set( 0, 0, 0 ); + + panOffset.set( 0, 0, 0 ); + + } + + scale = 1; + + // update condition is: + // min(camera displacement, camera rotation in radians)^2 > EPS + // using small-angle approximation cos(x/2) = 1 - x^2 / 8 + + if ( zoomChanged || + lastPosition.distanceToSquared( scope.object.position ) > EPS || + 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) { + + scope.dispatchEvent( changeEvent ); + + lastPosition.copy( scope.object.position ); + lastQuaternion.copy( scope.object.quaternion ); + zoomChanged = false; + + return true; + + } + + return false; + + }; + + }(); + + this.dispose = function () { + + scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false ); + scope.domElement.removeEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.removeEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.removeEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.removeEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.removeEventListener( 'touchmove', onTouchMove, false ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); + + //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here? + + }; + + // + // internals + // + + var scope = this; + + var changeEvent = { type: 'change' }; + var startEvent = { type: 'start' }; + var endEvent = { type: 'end' }; + + var STATE = { + NONE: - 1, + ROTATE: 0, + DOLLY: 1, + PAN: 2, + TOUCH_ROTATE: 3, + TOUCH_PAN: 4, + TOUCH_DOLLY_PAN: 5, + TOUCH_DOLLY_ROTATE: 6 + }; + + var state = STATE.NONE; + + var EPS = 0.000001; + + // current position in spherical coordinates + var spherical = new Spherical(); + var sphericalDelta = new Spherical(); + + var scale = 1; + var panOffset = new Vector3(); + var zoomChanged = false; + + var rotateStart = new Vector2(); + var rotateEnd = new Vector2(); + var rotateDelta = new Vector2(); + + var panStart = new Vector2(); + var panEnd = new Vector2(); + var panDelta = new Vector2(); + + var dollyStart = new Vector2(); + var dollyEnd = new Vector2(); + var dollyDelta = new Vector2(); + + function getAutoRotationAngle() { + + return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; + + } + + function getZoomScale() { + + return Math.pow( 0.95, scope.zoomSpeed ); + + } + + function rotateLeft( angle ) { + + sphericalDelta.theta -= angle; + + } + + function rotateUp( angle ) { + + sphericalDelta.phi -= angle; + + } + + var panLeft = function () { + + var v = new Vector3(); + + return function panLeft( distance, objectMatrix ) { + + v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix + v.multiplyScalar( - distance ); + + panOffset.add( v ); + + }; + + }(); + + var panUp = function () { + + var v = new Vector3(); + + return function panUp( distance, objectMatrix ) { + + if ( scope.screenSpacePanning === true ) { + + v.setFromMatrixColumn( objectMatrix, 1 ); + + } else { + + v.setFromMatrixColumn( objectMatrix, 0 ); + v.crossVectors( scope.object.up, v ); + + } + + v.multiplyScalar( distance ); + + panOffset.add( v ); + + }; + + }(); + + // deltaX and deltaY are in pixels; right and down are positive + var pan = function () { + + var offset = new Vector3(); + + return function pan( deltaX, deltaY ) { + + var element = scope.domElement; + + if ( scope.object.isPerspectiveCamera ) { + + // perspective + var position = scope.object.position; + offset.copy( position ).sub( scope.target ); + var targetDistance = offset.length(); + + // half of the fov is center to top of screen + targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 ); + + // we use only clientHeight here so aspect ratio does not distort speed + panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix ); + panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix ); + + } else if ( scope.object.isOrthographicCamera ) { + + // orthographic + panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix ); + panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix ); + + } else { + + // camera neither orthographic nor perspective + console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - pan disabled.' ); + scope.enablePan = false; + + } + + }; + + }(); + + function dollyIn( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale /= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + function dollyOut( dollyScale ) { + + if ( scope.object.isPerspectiveCamera ) { + + scale *= dollyScale; + + } else if ( scope.object.isOrthographicCamera ) { + + scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) ); + scope.object.updateProjectionMatrix(); + zoomChanged = true; + + } else { + + console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' ); + scope.enableZoom = false; + + } + + } + + // + // event callbacks - update the object state + // + + function handleMouseDownRotate( event ) { + + rotateStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownDolly( event ) { + + dollyStart.set( event.clientX, event.clientY ); + + } + + function handleMouseDownPan( event ) { + + panStart.set( event.clientX, event.clientY ); + + } + + function handleMouseMoveRotate( event ) { + + rotateEnd.set( event.clientX, event.clientY ); + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + scope.update(); + + } + + function handleMouseMoveDolly( event ) { + + dollyEnd.set( event.clientX, event.clientY ); + + dollyDelta.subVectors( dollyEnd, dollyStart ); + + if ( dollyDelta.y > 0 ) { + + dollyIn( getZoomScale() ); + + } else if ( dollyDelta.y < 0 ) { + + dollyOut( getZoomScale() ); + + } + + dollyStart.copy( dollyEnd ); + + scope.update(); + + } + + function handleMouseMovePan( event ) { + + panEnd.set( event.clientX, event.clientY ); + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + scope.update(); + + } + + function handleMouseUp( /*event*/ ) { + + // no-op + + } + + function handleMouseWheel( event ) { + + if ( event.deltaY < 0 ) { + + dollyOut( getZoomScale() ); + + } else if ( event.deltaY > 0 ) { + + dollyIn( getZoomScale() ); + + } + + scope.update(); + + } + + function handleKeyDown( event ) { + + var needsUpdate = false; + + switch ( event.keyCode ) { + + case scope.keys.UP: + pan( 0, scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.BOTTOM: + pan( 0, - scope.keyPanSpeed ); + needsUpdate = true; + break; + + case scope.keys.LEFT: + pan( scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + case scope.keys.RIGHT: + pan( - scope.keyPanSpeed, 0 ); + needsUpdate = true; + break; + + } + + if ( needsUpdate ) { + + // prevent the browser from scrolling on cursor keys + event.preventDefault(); + + scope.update(); + + } + + + } + + function handleTouchStartRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateStart.set( x, y ); + + } + + } + + function handleTouchStartPan( event ) { + + if ( event.touches.length == 1 ) { + + panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panStart.set( x, y ); + + } + + } + + function handleTouchStartDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyStart.set( 0, distance ); + + } + + function handleTouchStartDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enablePan ) handleTouchStartPan( event ); + + } + + function handleTouchStartDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchStartDolly( event ); + + if ( scope.enableRotate ) handleTouchStartRotate( event ); + + } + + function handleTouchMoveRotate( event ) { + + if ( event.touches.length == 1 ) { + + rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + rotateEnd.set( x, y ); + + } + + rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed ); + + var element = scope.domElement; + + rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height + + rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight ); + + rotateStart.copy( rotateEnd ); + + } + + function handleTouchMovePan( event ) { + + if ( event.touches.length == 1 ) { + + panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); + + } else { + + var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ); + var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ); + + panEnd.set( x, y ); + + } + + panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed ); + + pan( panDelta.x, panDelta.y ); + + panStart.copy( panEnd ); + + } + + function handleTouchMoveDolly( event ) { + + var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; + var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; + + var distance = Math.sqrt( dx * dx + dy * dy ); + + dollyEnd.set( 0, distance ); + + dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) ); + + dollyIn( dollyDelta.y ); + + dollyStart.copy( dollyEnd ); + + } + + function handleTouchMoveDollyPan( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enablePan ) handleTouchMovePan( event ); + + } + + function handleTouchMoveDollyRotate( event ) { + + if ( scope.enableZoom ) handleTouchMoveDolly( event ); + + if ( scope.enableRotate ) handleTouchMoveRotate( event ); + + } + + function handleTouchEnd( /*event*/ ) { + + // no-op + + } + + // + // event handlers - FSM: listen for events and reset state + // + + function onMouseDown( event ) { + + if ( scope.enabled === false ) return; + + // Prevent the browser from scrolling. + + event.preventDefault(); + + // Manually set the focus since calling preventDefault above + // prevents the browser from setting it automatically. + + scope.domElement.focus ? scope.domElement.focus() : window.focus(); + + var mouseAction; + + switch ( event.button ) { + + case 0: + + mouseAction = scope.mouseButtons.LEFT; + break; + + case 1: + + mouseAction = scope.mouseButtons.MIDDLE; + break; + + case 2: + + mouseAction = scope.mouseButtons.RIGHT; + break; + + default: + + mouseAction = - 1; + + } + + switch ( mouseAction ) { + + case MOUSE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseDownDolly( event ); + + state = STATE.DOLLY; + + break; + + case MOUSE.ROTATE: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } else { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } + + break; + + case MOUSE.PAN: + + if ( event.ctrlKey || event.metaKey || event.shiftKey ) { + + if ( scope.enableRotate === false ) return; + + handleMouseDownRotate( event ); + + state = STATE.ROTATE; + + } else { + + if ( scope.enablePan === false ) return; + + handleMouseDownPan( event ); + + state = STATE.PAN; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + document.addEventListener( 'mousemove', onMouseMove, false ); + document.addEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( startEvent ); + + } + + } + + function onMouseMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( state ) { + + case STATE.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleMouseMoveRotate( event ); + + break; + + case STATE.DOLLY: + + if ( scope.enableZoom === false ) return; + + handleMouseMoveDolly( event ); + + break; + + case STATE.PAN: + + if ( scope.enablePan === false ) return; + + handleMouseMovePan( event ); + + break; + + } + + } + + function onMouseUp( event ) { + + if ( scope.enabled === false ) return; + + handleMouseUp( event ); + + document.removeEventListener( 'mousemove', onMouseMove, false ); + document.removeEventListener( 'mouseup', onMouseUp, false ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onMouseWheel( event ) { + + if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return; + + event.preventDefault(); + event.stopPropagation(); + + scope.dispatchEvent( startEvent ); + + handleMouseWheel( event ); + + scope.dispatchEvent( endEvent ); + + } + + function onKeyDown( event ) { + + if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return; + + handleKeyDown( event ); + + } + + function onTouchStart( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + switch ( event.touches.length ) { + + case 1: + + switch ( scope.touches.ONE ) { + + case TOUCH.ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchStartRotate( event ); + + state = STATE.TOUCH_ROTATE; + + break; + + case TOUCH.PAN: + + if ( scope.enablePan === false ) return; + + handleTouchStartPan( event ); + + state = STATE.TOUCH_PAN; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + case 2: + + switch ( scope.touches.TWO ) { + + case TOUCH.DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchStartDollyPan( event ); + + state = STATE.TOUCH_DOLLY_PAN; + + break; + + case TOUCH.DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchStartDollyRotate( event ); + + state = STATE.TOUCH_DOLLY_ROTATE; + + break; + + default: + + state = STATE.NONE; + + } + + break; + + default: + + state = STATE.NONE; + + } + + if ( state !== STATE.NONE ) { + + scope.dispatchEvent( startEvent ); + + } + + } + + function onTouchMove( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + event.stopPropagation(); + + switch ( state ) { + + case STATE.TOUCH_ROTATE: + + if ( scope.enableRotate === false ) return; + + handleTouchMoveRotate( event ); + + scope.update(); + + break; + + case STATE.TOUCH_PAN: + + if ( scope.enablePan === false ) return; + + handleTouchMovePan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_PAN: + + if ( scope.enableZoom === false && scope.enablePan === false ) return; + + handleTouchMoveDollyPan( event ); + + scope.update(); + + break; + + case STATE.TOUCH_DOLLY_ROTATE: + + if ( scope.enableZoom === false && scope.enableRotate === false ) return; + + handleTouchMoveDollyRotate( event ); + + scope.update(); + + break; + + default: + + state = STATE.NONE; + + } + + } + + function onTouchEnd( event ) { + + if ( scope.enabled === false ) return; + + handleTouchEnd( event ); + + scope.dispatchEvent( endEvent ); + + state = STATE.NONE; + + } + + function onContextMenu( event ) { + + if ( scope.enabled === false ) return; + + event.preventDefault(); + + } + + // + + scope.domElement.addEventListener( 'contextmenu', onContextMenu, false ); + + scope.domElement.addEventListener( 'mousedown', onMouseDown, false ); + scope.domElement.addEventListener( 'wheel', onMouseWheel, false ); + + scope.domElement.addEventListener( 'touchstart', onTouchStart, false ); + scope.domElement.addEventListener( 'touchend', onTouchEnd, false ); + scope.domElement.addEventListener( 'touchmove', onTouchMove, false ); + + scope.domElement.addEventListener( 'keydown', onKeyDown, false ); + + // make sure element can receive keys. + + if ( scope.domElement.tabIndex === - 1 ) { + + scope.domElement.tabIndex = 0; + + } + + // force an update at start + + this.object.lookAt( scope.target ); + this.update(); + this.saveState(); + +}; + +CameraControls.prototype = Object.create( EventDispatcher.prototype ); +CameraControls.prototype.constructor = CameraControls; + + +// OrbitControls maintains the "up" direction, camera.up (+Y by default). +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +var OrbitControls = function ( object, domElement ) { + + CameraControls.call( this, object, domElement ); + + this.mouseButtons.LEFT = MOUSE.ROTATE; + this.mouseButtons.RIGHT = MOUSE.PAN; + + this.touches.ONE = TOUCH.ROTATE; + this.touches.TWO = TOUCH.DOLLY_PAN; + +}; + +OrbitControls.prototype = Object.create( EventDispatcher.prototype ); +OrbitControls.prototype.constructor = OrbitControls; + + +// MapControls maintains the "up" direction, camera.up (+Y by default) +// +// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - left mouse, or left right + ctrl/meta/shiftKey, or arrow keys / touch: one-finger move + + +var MapControls = function ( object, domElement ) { + + CameraControls.call( this, object, domElement ); + + this.mouseButtons.LEFT = MOUSE.PAN; + this.mouseButtons.RIGHT = MOUSE.ROTATE; + + this.touches.ONE = TOUCH.PAN; + this.touches.TWO = TOUCH.DOLLY_ROTATE; + +}; + +MapControls.prototype = Object.create( EventDispatcher.prototype ); +MapControls.prototype.constructor = MapControls; + + +// TrackballControls allows the camera to rotate over the polls and does not maintain camera.up +// +// Orbit - left mouse / touch: one-finger move +// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish +// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move + +var TrackballControls = function ( object, domElement ) { + + CameraControls.call( this, object, domElement ); + + this.trackball = true; + this.screenSpacePanning = true; + this.autoRotate = false; + + this.mouseButtons.LEFT = MOUSE.ROTATE; + this.mouseButtons.RIGHT = MOUSE.PAN; + + this.touches.ONE = TOUCH.ROTATE; + this.touches.TWO = TOUCH.DOLLY_PAN; + +}; + +TrackballControls.prototype = Object.create( EventDispatcher.prototype ); +TrackballControls.prototype.constructor = TrackballControls; + + +export { CameraControls, OrbitControls, MapControls, TrackballControls }; diff --git a/jsm/csm/CSM.js b/jsm/csm/CSM.js new file mode 100644 index 0000000..c96e627 --- /dev/null +++ b/jsm/csm/CSM.js @@ -0,0 +1,377 @@ +import { + Vector2, + Vector3, + DirectionalLight, + MathUtils, + ShaderChunk, + Matrix4, + Box3 +} from 'three'; +import { CSMFrustum } from './CSMFrustum.js'; +import { CSMShader } from './CSMShader.js'; + +const _cameraToLightMatrix = new Matrix4(); +const _lightSpaceFrustum = new CSMFrustum(); +const _center = new Vector3(); +const _bbox = new Box3(); +const _uniformArray = []; +const _logArray = []; + +export class CSM { + + constructor( data ) { + + data = data || {}; + + this.camera = data.camera; + this.parent = data.parent; + this.cascades = data.cascades || 3; + this.maxFar = data.maxFar || 100000; + this.mode = data.mode || 'practical'; + this.shadowMapSize = data.shadowMapSize || 2048; + this.shadowBias = data.shadowBias || 0.000001; + this.lightDirection = data.lightDirection || new Vector3( 1, - 1, 1 ).normalize(); + this.lightIntensity = data.lightIntensity || 1; + this.lightNear = data.lightNear || 1; + this.lightFar = data.lightFar || 2000; + this.lightMargin = data.lightMargin || 200; + this.customSplitsCallback = data.customSplitsCallback; + this.fade = false; + this.mainFrustum = new CSMFrustum(); + this.frustums = []; + this.breaks = []; + + this.lights = []; + this.shaders = new Map(); + + this.createLights(); + this.updateFrustums(); + this.injectInclude(); + + } + + createLights() { + + for ( let i = 0; i < this.cascades; i ++ ) { + + const light = new DirectionalLight( 0xffffff, this.lightIntensity ); + light.castShadow = true; + light.shadow.mapSize.width = this.shadowMapSize; + light.shadow.mapSize.height = this.shadowMapSize; + + light.shadow.camera.near = this.lightNear; + light.shadow.camera.far = this.lightFar; + light.shadow.bias = this.shadowBias; + + this.parent.add( light ); + this.parent.add( light.target ); + this.lights.push( light ); + + } + + } + + initCascades() { + + const camera = this.camera; + camera.updateProjectionMatrix(); + this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar ); + this.mainFrustum.split( this.breaks, this.frustums ); + + } + + updateShadowBounds() { + + const frustums = this.frustums; + for ( let i = 0; i < frustums.length; i ++ ) { + + const light = this.lights[ i ]; + const shadowCam = light.shadow.camera; + const frustum = this.frustums[ i ]; + + // Get the two points that represent that furthest points on the frustum assuming + // that's either the diagonal across the far plane or the diagonal across the whole + // frustum itself. + const nearVerts = frustum.vertices.near; + const farVerts = frustum.vertices.far; + const point1 = farVerts[ 0 ]; + let point2; + if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) { + + point2 = farVerts[ 2 ]; + + } else { + + point2 = nearVerts[ 2 ]; + + } + + let squaredBBWidth = point1.distanceTo( point2 ); + if ( this.fade ) { + + // expand the shadow extents by the fade margin if fade is enabled. + const camera = this.camera; + const far = Math.max( camera.far, this.maxFar ); + const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near ); + const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near ); + + squaredBBWidth += margin; + + } + + shadowCam.left = - squaredBBWidth / 2; + shadowCam.right = squaredBBWidth / 2; + shadowCam.top = squaredBBWidth / 2; + shadowCam.bottom = - squaredBBWidth / 2; + shadowCam.updateProjectionMatrix(); + + } + + } + + getBreaks() { + + const camera = this.camera; + const far = Math.min( camera.far, this.maxFar ); + this.breaks.length = 0; + + switch ( this.mode ) { + + case 'uniform': + uniformSplit( this.cascades, camera.near, far, this.breaks ); + break; + case 'logarithmic': + logarithmicSplit( this.cascades, camera.near, far, this.breaks ); + break; + case 'practical': + practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks ); + break; + case 'custom': + if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' ); + this.customSplitsCallback( this.cascades, camera.near, far, this.breaks ); + break; + + } + + function uniformSplit( amount, near, far, target ) { + + for ( let i = 1; i < amount; i ++ ) { + + target.push( ( near + ( far - near ) * i / amount ) / far ); + + } + + target.push( 1 ); + + } + + function logarithmicSplit( amount, near, far, target ) { + + for ( let i = 1; i < amount; i ++ ) { + + target.push( ( near * ( far / near ) ** ( i / amount ) ) / far ); + + } + + target.push( 1 ); + + } + + function practicalSplit( amount, near, far, lambda, target ) { + + _uniformArray.length = 0; + _logArray.length = 0; + logarithmicSplit( amount, near, far, _logArray ); + uniformSplit( amount, near, far, _uniformArray ); + + for ( let i = 1; i < amount; i ++ ) { + + target.push( MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) ); + + } + + target.push( 1 ); + + } + + } + + update() { + + const camera = this.camera; + const frustums = this.frustums; + for ( let i = 0; i < frustums.length; i ++ ) { + + const light = this.lights[ i ]; + const shadowCam = light.shadow.camera; + const texelWidth = ( shadowCam.right - shadowCam.left ) / this.shadowMapSize; + const texelHeight = ( shadowCam.top - shadowCam.bottom ) / this.shadowMapSize; + light.shadow.camera.updateMatrixWorld( true ); + _cameraToLightMatrix.multiplyMatrices( light.shadow.camera.matrixWorldInverse, camera.matrixWorld ); + frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum ); + + const nearVerts = _lightSpaceFrustum.vertices.near; + const farVerts = _lightSpaceFrustum.vertices.far; + _bbox.makeEmpty(); + for ( let j = 0; j < 4; j ++ ) { + + _bbox.expandByPoint( nearVerts[ j ] ); + _bbox.expandByPoint( farVerts[ j ] ); + + } + + _bbox.getCenter( _center ); + _center.z = _bbox.max.z + this.lightMargin; + _center.x = Math.floor( _center.x / texelWidth ) * texelWidth; + _center.y = Math.floor( _center.y / texelHeight ) * texelHeight; + _center.applyMatrix4( light.shadow.camera.matrixWorld ); + + light.position.copy( _center ); + light.target.position.copy( _center ); + + light.target.position.x += this.lightDirection.x; + light.target.position.y += this.lightDirection.y; + light.target.position.z += this.lightDirection.z; + + } + + } + + injectInclude() { + + ShaderChunk.lights_fragment_begin = CSMShader.lights_fragment_begin; + ShaderChunk.lights_pars_begin = CSMShader.lights_pars_begin; + + } + + setupMaterial( material ) { + + material.defines = material.defines || {}; + material.defines.USE_CSM = 1; + material.defines.CSM_CASCADES = this.cascades; + + if ( this.fade ) { + + material.defines.CSM_FADE = ''; + + } + + const breaksVec2 = []; + const scope = this; + const shaders = this.shaders; + + material.onBeforeCompile = function ( shader ) { + + const far = Math.min( scope.camera.far, scope.maxFar ); + scope.getExtendedBreaks( breaksVec2 ); + + shader.uniforms.CSM_cascades = { value: breaksVec2 }; + shader.uniforms.cameraNear = { value: scope.camera.near }; + shader.uniforms.shadowFar = { value: far }; + + shaders.set( material, shader ); + + }; + + shaders.set( material, null ); + + } + + updateUniforms() { + + const far = Math.min( this.camera.far, this.maxFar ); + const shaders = this.shaders; + + shaders.forEach( function ( shader, material ) { + + if ( shader !== null ) { + + const uniforms = shader.uniforms; + this.getExtendedBreaks( uniforms.CSM_cascades.value ); + uniforms.cameraNear.value = this.camera.near; + uniforms.shadowFar.value = far; + + } + + if ( ! this.fade && 'CSM_FADE' in material.defines ) { + + delete material.defines.CSM_FADE; + material.needsUpdate = true; + + } else if ( this.fade && ! ( 'CSM_FADE' in material.defines ) ) { + + material.defines.CSM_FADE = ''; + material.needsUpdate = true; + + } + + }, this ); + + } + + getExtendedBreaks( target ) { + + while ( target.length < this.breaks.length ) { + + target.push( new Vector2() ); + + } + + target.length = this.breaks.length; + + for ( let i = 0; i < this.cascades; i ++ ) { + + const amount = this.breaks[ i ]; + const prev = this.breaks[ i - 1 ] || 0; + target[ i ].x = prev; + target[ i ].y = amount; + + } + + } + + updateFrustums() { + + this.getBreaks(); + this.initCascades(); + this.updateShadowBounds(); + this.updateUniforms(); + + } + + remove() { + + for ( let i = 0; i < this.lights.length; i ++ ) { + + this.parent.remove( this.lights[ i ] ); + + } + + } + + dispose() { + + const shaders = this.shaders; + shaders.forEach( function ( shader, material ) { + + delete material.onBeforeCompile; + delete material.defines.USE_CSM; + delete material.defines.CSM_CASCADES; + delete material.defines.CSM_FADE; + + if ( shader !== null ) { + + delete shader.uniforms.CSM_cascades; + delete shader.uniforms.cameraNear; + delete shader.uniforms.shadowFar; + + } + + material.needsUpdate = true; + + } ); + shaders.clear(); + + } + +} diff --git a/jsm/csm/CSMFrustum.js b/jsm/csm/CSMFrustum.js new file mode 100644 index 0000000..2d968be --- /dev/null +++ b/jsm/csm/CSMFrustum.js @@ -0,0 +1,152 @@ +import { Vector3, Matrix4 } from 'three'; + +const inverseProjectionMatrix = new Matrix4(); + +class CSMFrustum { + + constructor( data ) { + + data = data || {}; + + this.vertices = { + near: [ + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3() + ], + far: [ + new Vector3(), + new Vector3(), + new Vector3(), + new Vector3() + ] + }; + + if ( data.projectionMatrix !== undefined ) { + + this.setFromProjectionMatrix( data.projectionMatrix, data.maxFar || 10000 ); + + } + + } + + setFromProjectionMatrix( projectionMatrix, maxFar ) { + + const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0; + + inverseProjectionMatrix.copy( projectionMatrix ).invert(); + + // 3 --- 0 vertices.near/far order + // | | + // 2 --- 1 + // clip space spans from [-1, 1] + + this.vertices.near[ 0 ].set( 1, 1, - 1 ); + this.vertices.near[ 1 ].set( 1, - 1, - 1 ); + this.vertices.near[ 2 ].set( - 1, - 1, - 1 ); + this.vertices.near[ 3 ].set( - 1, 1, - 1 ); + this.vertices.near.forEach( function ( v ) { + + v.applyMatrix4( inverseProjectionMatrix ); + + } ); + + this.vertices.far[ 0 ].set( 1, 1, 1 ); + this.vertices.far[ 1 ].set( 1, - 1, 1 ); + this.vertices.far[ 2 ].set( - 1, - 1, 1 ); + this.vertices.far[ 3 ].set( - 1, 1, 1 ); + this.vertices.far.forEach( function ( v ) { + + v.applyMatrix4( inverseProjectionMatrix ); + + const absZ = Math.abs( v.z ); + if ( isOrthographic ) { + + v.z *= Math.min( maxFar / absZ, 1.0 ); + + } else { + + v.multiplyScalar( Math.min( maxFar / absZ, 1.0 ) ); + + } + + } ); + + return this.vertices; + + } + + split( breaks, target ) { + + while ( breaks.length > target.length ) { + + target.push( new CSMFrustum() ); + + } + + target.length = breaks.length; + + for ( let i = 0; i < breaks.length; i ++ ) { + + const cascade = target[ i ]; + + if ( i === 0 ) { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.near[ j ].copy( this.vertices.near[ j ] ); + + } + + } else { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.near[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i - 1 ] ); + + } + + } + + if ( i === breaks.length - 1 ) { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.far[ j ].copy( this.vertices.far[ j ] ); + + } + + } else { + + for ( let j = 0; j < 4; j ++ ) { + + cascade.vertices.far[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i ] ); + + } + + } + + } + + } + + toSpace( cameraMatrix, target ) { + + for ( let i = 0; i < 4; i ++ ) { + + target.vertices.near[ i ] + .copy( this.vertices.near[ i ] ) + .applyMatrix4( cameraMatrix ); + + target.vertices.far[ i ] + .copy( this.vertices.far[ i ] ) + .applyMatrix4( cameraMatrix ); + + } + + } + +} + +export { CSMFrustum }; diff --git a/jsm/csm/CSMHelper.js b/jsm/csm/CSMHelper.js new file mode 100644 index 0000000..7ea0a81 --- /dev/null +++ b/jsm/csm/CSMHelper.js @@ -0,0 +1,163 @@ +import { + Group, + Mesh, + LineSegments, + BufferGeometry, + LineBasicMaterial, + Box3Helper, + Box3, + PlaneGeometry, + MeshBasicMaterial, + BufferAttribute, + DoubleSide +} from 'three'; + +class CSMHelper extends Group { + + constructor( csm ) { + + super(); + this.csm = csm; + this.displayFrustum = true; + this.displayPlanes = true; + this.displayShadowBounds = true; + + const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] ); + const positions = new Float32Array( 24 ); + const frustumGeometry = new BufferGeometry(); + frustumGeometry.setIndex( new BufferAttribute( indices, 1 ) ); + frustumGeometry.setAttribute( 'position', new BufferAttribute( positions, 3, false ) ); + const frustumLines = new LineSegments( frustumGeometry, new LineBasicMaterial() ); + this.add( frustumLines ); + + this.frustumLines = frustumLines; + this.cascadeLines = []; + this.cascadePlanes = []; + this.shadowLines = []; + + } + + updateVisibility() { + + const displayFrustum = this.displayFrustum; + const displayPlanes = this.displayPlanes; + const displayShadowBounds = this.displayShadowBounds; + + const frustumLines = this.frustumLines; + const cascadeLines = this.cascadeLines; + const cascadePlanes = this.cascadePlanes; + const shadowLines = this.shadowLines; + for ( let i = 0, l = cascadeLines.length; i < l; i ++ ) { + + const cascadeLine = cascadeLines[ i ]; + const cascadePlane = cascadePlanes[ i ]; + const shadowLineGroup = shadowLines[ i ]; + + cascadeLine.visible = displayFrustum; + cascadePlane.visible = displayFrustum && displayPlanes; + shadowLineGroup.visible = displayShadowBounds; + + } + + frustumLines.visible = displayFrustum; + + } + + update() { + + const csm = this.csm; + const camera = csm.camera; + const cascades = csm.cascades; + const mainFrustum = csm.mainFrustum; + const frustums = csm.frustums; + const lights = csm.lights; + + const frustumLines = this.frustumLines; + const frustumLinePositions = frustumLines.geometry.getAttribute( 'position' ); + const cascadeLines = this.cascadeLines; + const cascadePlanes = this.cascadePlanes; + const shadowLines = this.shadowLines; + + this.position.copy( camera.position ); + this.quaternion.copy( camera.quaternion ); + this.scale.copy( camera.scale ); + this.updateMatrixWorld( true ); + + while ( cascadeLines.length > cascades ) { + + this.remove( cascadeLines.pop() ); + this.remove( cascadePlanes.pop() ); + this.remove( shadowLines.pop() ); + + } + + while ( cascadeLines.length < cascades ) { + + const cascadeLine = new Box3Helper( new Box3(), 0xffffff ); + const planeMat = new MeshBasicMaterial( { transparent: true, opacity: 0.1, depthWrite: false, side: DoubleSide } ); + const cascadePlane = new Mesh( new PlaneGeometry(), planeMat ); + const shadowLineGroup = new Group(); + const shadowLine = new Box3Helper( new Box3(), 0xffff00 ); + shadowLineGroup.add( shadowLine ); + + this.add( cascadeLine ); + this.add( cascadePlane ); + this.add( shadowLineGroup ); + + cascadeLines.push( cascadeLine ); + cascadePlanes.push( cascadePlane ); + shadowLines.push( shadowLineGroup ); + + } + + for ( let i = 0; i < cascades; i ++ ) { + + const frustum = frustums[ i ]; + const light = lights[ i ]; + const shadowCam = light.shadow.camera; + const farVerts = frustum.vertices.far; + + const cascadeLine = cascadeLines[ i ]; + const cascadePlane = cascadePlanes[ i ]; + const shadowLineGroup = shadowLines[ i ]; + const shadowLine = shadowLineGroup.children[ 0 ]; + + cascadeLine.box.min.copy( farVerts[ 2 ] ); + cascadeLine.box.max.copy( farVerts[ 0 ] ); + cascadeLine.box.max.z += 1e-4; + + cascadePlane.position.addVectors( farVerts[ 0 ], farVerts[ 2 ] ); + cascadePlane.position.multiplyScalar( 0.5 ); + cascadePlane.scale.subVectors( farVerts[ 0 ], farVerts[ 2 ] ); + cascadePlane.scale.z = 1e-4; + + this.remove( shadowLineGroup ); + shadowLineGroup.position.copy( shadowCam.position ); + shadowLineGroup.quaternion.copy( shadowCam.quaternion ); + shadowLineGroup.scale.copy( shadowCam.scale ); + shadowLineGroup.updateMatrixWorld( true ); + this.attach( shadowLineGroup ); + + shadowLine.box.min.set( shadowCam.bottom, shadowCam.left, - shadowCam.far ); + shadowLine.box.max.set( shadowCam.top, shadowCam.right, - shadowCam.near ); + + } + + const nearVerts = mainFrustum.vertices.near; + const farVerts = mainFrustum.vertices.far; + frustumLinePositions.setXYZ( 0, farVerts[ 0 ].x, farVerts[ 0 ].y, farVerts[ 0 ].z ); + frustumLinePositions.setXYZ( 1, farVerts[ 3 ].x, farVerts[ 3 ].y, farVerts[ 3 ].z ); + frustumLinePositions.setXYZ( 2, farVerts[ 2 ].x, farVerts[ 2 ].y, farVerts[ 2 ].z ); + frustumLinePositions.setXYZ( 3, farVerts[ 1 ].x, farVerts[ 1 ].y, farVerts[ 1 ].z ); + + frustumLinePositions.setXYZ( 4, nearVerts[ 0 ].x, nearVerts[ 0 ].y, nearVerts[ 0 ].z ); + frustumLinePositions.setXYZ( 5, nearVerts[ 3 ].x, nearVerts[ 3 ].y, nearVerts[ 3 ].z ); + frustumLinePositions.setXYZ( 6, nearVerts[ 2 ].x, nearVerts[ 2 ].y, nearVerts[ 2 ].z ); + frustumLinePositions.setXYZ( 7, nearVerts[ 1 ].x, nearVerts[ 1 ].y, nearVerts[ 1 ].z ); + frustumLinePositions.needsUpdate = true; + + } + +} + +export { CSMHelper }; diff --git a/jsm/csm/CSMShader.js b/jsm/csm/CSMShader.js new file mode 100644 index 0000000..316461b --- /dev/null +++ b/jsm/csm/CSMShader.js @@ -0,0 +1,251 @@ +import { ShaderChunk } from 'three'; + +const CSMShader = { + lights_fragment_begin: /* glsl */` +GeometricContext geometry; + +geometry.position = - vViewPosition; +geometry.normal = normal; +geometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition ); + +#ifdef CLEARCOAT + + geometry.clearcoatNormal = clearcoatNormal; + +#endif + +IncidentLight directLight; + +#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct ) + + PointLight pointLight; + #if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0 + PointLightShadow pointLightShadow; + #endif + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) { + + pointLight = pointLights[ i ]; + + getPointLightInfo( pointLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS ) + pointLightShadow = pointLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0; + #endif + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct ) + + SpotLight spotLight; + #if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0 + SpotLightShadow spotLightShadow; + #endif + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) { + + spotLight = spotLights[ i ]; + + getSpotLightInfo( spotLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS ) + spotLightShadow = spotLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0; + #endif + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if ( NUM_DIR_LIGHTS > 0) && defined( RE_Direct ) && defined( USE_CSM ) && defined( CSM_CASCADES ) + + DirectionalLight directionalLight; + float linearDepth = (vViewPosition.z) / (shadowFar - cameraNear); + #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0 + DirectionalLightShadow directionalLightShadow; + #endif + + #if defined( USE_SHADOWMAP ) && defined( CSM_FADE ) + vec2 cascade; + float cascadeCenter; + float closestEdge; + float margin; + float csmx; + float csmy; + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + // NOTE: Depth gets larger away from the camera. + // cascade.x is closer, cascade.y is further + cascade = CSM_cascades[ i ]; + cascadeCenter = ( cascade.x + cascade.y ) / 2.0; + closestEdge = linearDepth < cascadeCenter ? cascade.x : cascade.y; + margin = 0.25 * pow( closestEdge, 2.0 ); + csmx = cascade.x - margin / 2.0; + csmy = cascade.y + margin / 2.0; + if( linearDepth >= csmx && ( linearDepth < csmy || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 ) ) { + + float dist = min( linearDepth - csmx, csmy - linearDepth ); + float ratio = clamp( dist / margin, 0.0, 1.0 ); + + vec3 prevColor = directLight.color; + directionalLightShadow = directionalLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + + bool shouldFadeLastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth > cascadeCenter; + directLight.color = mix( prevColor, directLight.color, shouldFadeLastCascade ? ratio : 1.0 ); + + ReflectedLight prevLight = reflectedLight; + RE_Direct( directLight, geometry, material, reflectedLight ); + + bool shouldBlend = UNROLLED_LOOP_INDEX != CSM_CASCADES - 1 || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth < cascadeCenter; + float blendRatio = shouldBlend ? ratio : 1.0; + + reflectedLight.directDiffuse = mix( prevLight.directDiffuse, reflectedLight.directDiffuse, blendRatio ); + reflectedLight.directSpecular = mix( prevLight.directSpecular, reflectedLight.directSpecular, blendRatio ); + reflectedLight.indirectDiffuse = mix( prevLight.indirectDiffuse, reflectedLight.indirectDiffuse, blendRatio ); + reflectedLight.indirectSpecular = mix( prevLight.indirectSpecular, reflectedLight.indirectSpecular, blendRatio ); + + } + #endif + + } + #pragma unroll_loop_end + #else + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + + directionalLightShadow = directionalLightShadows[ i ]; + if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + + if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometry, material, reflectedLight ); + + #endif + + } + #pragma unroll_loop_end + + #endif + + #if ( NUM_DIR_LIGHTS > NUM_DIR_LIGHT_SHADOWS) + // compute the lights not casting shadows (if any) + + #pragma unroll_loop_start + for ( int i = NUM_DIR_LIGHT_SHADOWS; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + + #endif + +#endif + + +#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && !defined( USE_CSM ) && !defined( CSM_CASCADES ) + + DirectionalLight directionalLight; + #if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0 + DirectionalLightShadow directionalLightShadow; + #endif + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) { + + directionalLight = directionalLights[ i ]; + + getDirectionalLightInfo( directionalLight, geometry, directLight ); + + #if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) + directionalLightShadow = directionalLightShadows[ i ]; + directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0; + #endif + + RE_Direct( directLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea ) + + RectAreaLight rectAreaLight; + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) { + + rectAreaLight = rectAreaLights[ i ]; + RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight ); + + } + #pragma unroll_loop_end + +#endif + +#if defined( RE_IndirectDiffuse ) + + vec3 iblIrradiance = vec3( 0.0 ); + + vec3 irradiance = getAmbientLightIrradiance( ambientLightColor ); + + irradiance += getLightProbeIrradiance( lightProbe, geometry.normal ); + + #if ( NUM_HEMI_LIGHTS > 0 ) + + #pragma unroll_loop_start + for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) { + + irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry.normal ); + + } + #pragma unroll_loop_end + + #endif + +#endif + +#if defined( RE_IndirectSpecular ) + + vec3 radiance = vec3( 0.0 ); + vec3 clearcoatRadiance = vec3( 0.0 ); + +#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 +}; + +export { CSMShader }; diff --git a/jsm/curves/CurveExtras.js b/jsm/curves/CurveExtras.js new file mode 100644 index 0000000..51efb84 --- /dev/null +++ b/jsm/curves/CurveExtras.js @@ -0,0 +1,422 @@ +import { + Curve, + Vector3 +} from 'three'; + +/** + * A bunch of parametric curves + * + * Formulas collected from various sources + * http://mathworld.wolfram.com/HeartCurve.html + * http://en.wikipedia.org/wiki/Viviani%27s_curve + * http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf + * https://prideout.net/blog/old/blog/index.html@p=44.html + */ + +// GrannyKnot + +class GrannyKnot extends Curve { + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = 2 * Math.PI * t; + + const x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t ); + const y = - 0.1 * Math.cos( 2 * t ) - 0.27 * Math.sin( 2 * t ) + 0.38 * Math.cos( 4 * t ) + 0.46 * Math.sin( 4 * t ); + const z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t ); + + return point.set( x, y, z ).multiplyScalar( 20 ); + + } + +} + +// HeartCurve + +class HeartCurve extends Curve { + + constructor( scale = 5 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= 2 * Math.PI; + + const x = 16 * Math.pow( Math.sin( t ), 3 ); + const y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t ); + const z = 0; + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// Viviani's Curve + +class VivianiCurve extends Curve { + + constructor( scale = 70 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = t * 4 * Math.PI; // normalized to 0..1 + const a = this.scale / 2; + + const x = a * ( 1 + Math.cos( t ) ); + const y = a * Math.sin( t ); + const z = 2 * a * Math.sin( t / 2 ); + + return point.set( x, y, z ); + + } + +} + +// KnotCurve + +class KnotCurve extends Curve { + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= 2 * Math.PI; + + const R = 10; + const s = 50; + + const x = s * Math.sin( t ); + const y = Math.cos( t ) * ( R + s * Math.cos( t ) ); + const z = Math.sin( t ) * ( R + s * Math.cos( t ) ); + + return point.set( x, y, z ); + + } + +} + + +// HelixCurve + +class HelixCurve extends Curve { + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const a = 30; // radius + const b = 150; // height + + const t2 = 2 * Math.PI * t * b / 30; + + const x = Math.cos( t2 ) * a; + const y = Math.sin( t2 ) * a; + const z = b * t; + + return point.set( x, y, z ); + + } + +} + +// TrefoilKnot + +class TrefoilKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= Math.PI * 2; + + const x = ( 2 + Math.cos( 3 * t ) ) * Math.cos( 2 * t ); + const y = ( 2 + Math.cos( 3 * t ) ) * Math.sin( 2 * t ); + const z = Math.sin( 3 * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// TorusKnot + +class TorusKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const p = 3; + const q = 4; + + t *= Math.PI * 2; + + const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t ); + const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t ); + const z = Math.sin( q * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// CinquefoilKnot + +class CinquefoilKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const p = 2; + const q = 5; + + t *= Math.PI * 2; + + const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t ); + const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t ); + const z = Math.sin( q * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + + +// TrefoilPolynomialKnot + +class TrefoilPolynomialKnot extends Curve { + + constructor( scale = 10 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = t * 4 - 2; + + const x = Math.pow( t, 3 ) - 3 * t; + const y = Math.pow( t, 4 ) - 4 * t * t; + const z = 1 / 5 * Math.pow( t, 5 ) - 2 * t; + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +function scaleTo( x, y, t ) { + + const r = y - x; + return t * r + x; + +} + +// FigureEightPolynomialKnot + +class FigureEightPolynomialKnot extends Curve { + + constructor( scale = 1 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t = scaleTo( - 4, 4, t ); + + const x = 2 / 5 * t * ( t * t - 7 ) * ( t * t - 10 ); + const y = Math.pow( t, 4 ) - 13 * t * t; + const z = 1 / 10 * t * ( t * t - 4 ) * ( t * t - 9 ) * ( t * t - 12 ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// DecoratedTorusKnot4a + +class DecoratedTorusKnot4a extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + t *= Math.PI * 2; + + const x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) ); + const y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) ); + const z = 0.35 * Math.sin( 5 * t ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// DecoratedTorusKnot4b + +class DecoratedTorusKnot4b extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const fi = t * Math.PI * 2; + + const x = Math.cos( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) ); + const y = Math.sin( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) ); + const z = 0.2 * Math.sin( 9 * fi ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + + +// DecoratedTorusKnot5a + +class DecoratedTorusKnot5a extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const fi = t * Math.PI * 2; + + const x = Math.cos( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) ); + const y = Math.sin( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) ); + const z = 0.2 * Math.sin( 20 * fi ); + + return point.set( x, y, z ).multiplyScalar( this.scale ); + + } + +} + +// DecoratedTorusKnot5c + +class DecoratedTorusKnot5c extends Curve { + + constructor( scale = 40 ) { + + super(); + + this.scale = scale; + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const fi = t * Math.PI * 2; + + const x = Math.cos( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) ); + const y = Math.sin( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) ); + const z = 0.35 * Math.sin( 15 * fi ); + + 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/jsm/curves/NURBSCurve.js b/jsm/curves/NURBSCurve.js new file mode 100644 index 0000000..8be8dde --- /dev/null +++ b/jsm/curves/NURBSCurve.js @@ -0,0 +1,80 @@ +import { + Curve, + Vector3, + Vector4 +} from 'three'; +import * as NURBSUtils from '../curves/NURBSUtils.js'; + +/** + * NURBS curve object + * + * Derives from Curve, overriding getPoint and getTangent. + * + * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight. + * + **/ + +class NURBSCurve extends Curve { + + constructor( + degree, + knots /* array of reals */, + controlPoints /* array of Vector(2|3|4) */, + startKnot /* index in knots */, + endKnot /* index in knots */ + ) { + + super(); + + this.degree = degree; + this.knots = knots; + this.controlPoints = []; + // Used by periodic NURBS to remove hidden spans + this.startKnot = startKnot || 0; + this.endKnot = endKnot || ( this.knots.length - 1 ); + + for ( let i = 0; i < controlPoints.length; ++ i ) { + + // ensure Vector4 for control points + const point = controlPoints[ i ]; + this.controlPoints[ i ] = new Vector4( point.x, point.y, point.z, point.w ); + + } + + } + + getPoint( t, optionalTarget = new Vector3() ) { + + const point = optionalTarget; + + const u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u + + // following results in (wx, wy, wz, w) homogeneous point + const hpoint = NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u ); + + if ( hpoint.w !== 1.0 ) { + + // project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1) + hpoint.divideScalar( hpoint.w ); + + } + + return point.set( hpoint.x, hpoint.y, hpoint.z ); + + } + + getTangent( t, optionalTarget = new Vector3() ) { + + const tangent = optionalTarget; + + const u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] ); + const ders = NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 ); + tangent.copy( ders[ 1 ] ).normalize(); + + return tangent; + + } + +} + +export { NURBSCurve }; diff --git a/jsm/curves/NURBSSurface.js b/jsm/curves/NURBSSurface.js new file mode 100644 index 0000000..577ecfc --- /dev/null +++ b/jsm/curves/NURBSSurface.js @@ -0,0 +1,52 @@ +import { + Vector4 +} from 'three'; +import * as NURBSUtils from '../curves/NURBSUtils.js'; + +/** + * NURBS surface object + * + * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight. + **/ + +class NURBSSurface { + + constructor( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) { + + this.degree1 = degree1; + this.degree2 = degree2; + this.knots1 = knots1; + this.knots2 = knots2; + this.controlPoints = []; + + const len1 = knots1.length - degree1 - 1; + const len2 = knots2.length - degree2 - 1; + + // ensure Vector4 for control points + for ( let i = 0; i < len1; ++ i ) { + + this.controlPoints[ i ] = []; + + for ( let j = 0; j < len2; ++ j ) { + + const point = controlPoints[ i ][ j ]; + this.controlPoints[ i ][ j ] = new Vector4( point.x, point.y, point.z, point.w ); + + } + + } + + } + + getPoint( t1, t2, target ) { + + const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u + const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u + + NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target ); + + } + +} + +export { NURBSSurface }; diff --git a/jsm/curves/NURBSUtils.js b/jsm/curves/NURBSUtils.js new file mode 100644 index 0000000..fc77fdb --- /dev/null +++ b/jsm/curves/NURBSUtils.js @@ -0,0 +1,487 @@ +import { + Vector3, + Vector4 +} from 'three'; + +/** + * NURBS utils + * + * See NURBSCurve and NURBSSurface. + **/ + + +/************************************************************** + * NURBS Utils + **************************************************************/ + +/* +Finds knot vector span. + +p : degree +u : parametric value +U : knot vector + +returns the span +*/ +function findSpan( p, u, U ) { + + const n = U.length - p - 1; + + if ( u >= U[ n ] ) { + + return n - 1; + + } + + if ( u <= U[ p ] ) { + + return p; + + } + + let low = p; + let high = n; + let mid = Math.floor( ( low + high ) / 2 ); + + while ( u < U[ mid ] || u >= U[ mid + 1 ] ) { + + if ( u < U[ mid ] ) { + + high = mid; + + } else { + + low = mid; + + } + + mid = Math.floor( ( low + high ) / 2 ); + + } + + return mid; + +} + + +/* +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 + +returns array[p+1] with basis functions values. +*/ +function calcBasisFunctions( span, u, p, U ) { + + const N = []; + const left = []; + const right = []; + N[ 0 ] = 1.0; + + for ( let j = 1; j <= p; ++ j ) { + + left[ j ] = u - U[ span + 1 - j ]; + right[ j ] = U[ span + j ] - u; + + let saved = 0.0; + + for ( let r = 0; r < j; ++ r ) { + + const rv = right[ r + 1 ]; + const lv = left[ j - r ]; + const temp = N[ r ] / ( rv + lv ); + N[ r ] = saved + rv * temp; + saved = lv * temp; + + } + + N[ j ] = saved; + + } + + return N; + +} + + +/* +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 + +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 ); + + for ( let j = 0; j <= p; ++ j ) { + + const point = P[ span - p + j ]; + const Nj = N[ j ]; + const wNj = point.w * Nj; + C.x += point.x * wNj; + C.y += point.y * wNj; + C.z += point.z * wNj; + C.w += point.w * Nj; + + } + + return C; + +} + + +/* +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 + +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; + + const left = zeroArr.slice( 0 ); + const right = zeroArr.slice( 0 ); + + for ( let j = 1; j <= p; ++ j ) { + + left[ j ] = u - U[ span + 1 - j ]; + right[ j ] = U[ span + j ] - u; + + let saved = 0.0; + + for ( let r = 0; r < j; ++ r ) { + + const rv = right[ r + 1 ]; + const lv = left[ j - r ]; + ndu[ j ][ r ] = rv + lv; + + const temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ]; + ndu[ r ][ j ] = saved + rv * temp; + saved = lv * temp; + + } + + ndu[ j ][ j ] = saved; + + } + + for ( let j = 0; j <= p; ++ j ) { + + ders[ 0 ][ j ] = ndu[ j ][ p ]; + + } + + for ( let r = 0; r <= p; ++ r ) { + + let s1 = 0; + let s2 = 1; + + const a = []; + for ( let i = 0; i <= p; ++ i ) { + + a[ i ] = zeroArr.slice( 0 ); + + } + + a[ 0 ][ 0 ] = 1.0; + + for ( let k = 1; k <= n; ++ k ) { + + let d = 0.0; + const rk = r - k; + const pk = p - k; + + if ( r >= k ) { + + a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ]; + d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ]; + + } + + const j1 = ( rk >= - 1 ) ? 1 : - rk; + const j2 = ( r - 1 <= pk ) ? k - 1 : p - r; + + for ( let j = j1; j <= j2; ++ j ) { + + a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ]; + d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ]; + + } + + if ( r <= pk ) { + + a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ]; + d += a[ s2 ][ k ] * ndu[ r ][ pk ]; + + } + + ders[ k ][ r ] = d; + + const j = s1; + s1 = s2; + s2 = j; + + } + + } + + let r = p; + + for ( let k = 1; k <= n; ++ k ) { + + for ( let j = 0; j <= p; ++ j ) { + + ders[ k ][ j ] *= r; + + } + + r *= p - k; + + } + + return ders; + +} + + +/* + 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 + + 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 ); + const nders = calcBasisFunctionDerivatives( span, u, p, du, U ); + const Pw = []; + + for ( let i = 0; i < P.length; ++ i ) { + + const point = P[ i ].clone(); + const w = point.w; + + point.x *= w; + point.y *= w; + point.z *= w; + + Pw[ i ] = point; + + } + + for ( let k = 0; k <= du; ++ k ) { + + 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 ] ) ); + + } + + CK[ k ] = point; + + } + + for ( let k = du + 1; k <= nd + 1; ++ k ) { + + CK[ k ] = new Vector4( 0, 0, 0 ); + + } + + return CK; + +} + + +/* +Calculate "K over I" + +returns k!/(i!(k-i)!) +*/ +function calcKoverI( k, i ) { + + let nom = 1; + + for ( let j = 2; j <= k; ++ j ) { + + nom *= j; + + } + + let denom = 1; + + for ( let j = 2; j <= i; ++ j ) { + + denom *= j; + + } + + for ( let j = 2; j <= k - i; ++ j ) { + + denom *= j; + + } + + return nom / denom; + +} + + +/* +Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2. + +Pders : result of function calcBSplineDerivatives + +returns array with derivatives for rational curve. +*/ +function calcRationalCurveDerivatives( Pders ) { + + const nd = Pders.length; + const Aders = []; + const wders = []; + + for ( let i = 0; i < nd; ++ i ) { + + const point = Pders[ i ]; + Aders[ i ] = new Vector3( point.x, point.y, point.z ); + wders[ i ] = point.w; + + } + + const CK = []; + + for ( let k = 0; k < nd; ++ k ) { + + const v = Aders[ k ].clone(); + + for ( let i = 1; i <= k; ++ i ) { + + v.sub( CK[ k - i ].clone().multiplyScalar( calcKoverI( k, i ) * wders[ i ] ) ); + + } + + CK[ k ] = v.divideScalar( wders[ 0 ] ); + + } + + return CK; + +} + + +/* +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 + +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. + +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) +*/ +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 ); + const Nv = calcBasisFunctions( vspan, v, q, V ); + const temp = []; + + for ( let l = 0; l <= q; ++ l ) { + + temp[ l ] = new Vector4( 0, 0, 0, 0 ); + for ( let k = 0; k <= p; ++ k ) { + + const point = P[ uspan - p + k ][ vspan - q + l ].clone(); + const w = point.w; + point.x *= w; + point.y *= w; + point.z *= w; + temp[ l ].add( point.multiplyScalar( Nu[ k ] ) ); + + } + + } + + const Sw = new Vector4( 0, 0, 0, 0 ); + for ( let l = 0; l <= q; ++ l ) { + + Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) ); + + } + + 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/jsm/deprecated/Geometry.js b/jsm/deprecated/Geometry.js new file mode 100644 index 0000000..aae29e5 --- /dev/null +++ b/jsm/deprecated/Geometry.js @@ -0,0 +1,1869 @@ +import { + Box3, + BufferAttribute, + BufferGeometry, + Color, + EventDispatcher, + Float32BufferAttribute, + Matrix3, + Matrix4, + MathUtils, + Object3D, + Sphere, + Vector2, + Vector3 +} from 'three'; + +const _m1 = new Matrix4(); +const _obj = new Object3D(); +const _offset = new Vector3(); + +class Geometry extends EventDispatcher { + + constructor() { + + super(); + + this.uuid = MathUtils.generateUUID(); + + this.name = ''; + this.type = 'Geometry'; + + this.vertices = []; + this.colors = []; + this.faces = []; + this.faceVertexUvs = [[]]; + + this.morphTargets = []; + this.morphNormals = []; + + this.skinWeights = []; + this.skinIndices = []; + + this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + // update flags + + this.elementsNeedUpdate = false; + this.verticesNeedUpdate = false; + this.uvsNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.lineDistancesNeedUpdate = false; + this.groupsNeedUpdate = false; + + } + + applyMatrix4( matrix ) { + + const normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + for ( let i = 0, il = this.vertices.length; i < il; i ++ ) { + + const vertex = this.vertices[ i ]; + vertex.applyMatrix4( matrix ); + + } + + for ( let i = 0, il = this.faces.length; i < il; i ++ ) { + + const face = this.faces[ i ]; + face.normal.applyMatrix3( normalMatrix ).normalize(); + + for ( let j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) { + + face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize(); + + } + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + this.verticesNeedUpdate = true; + this.normalsNeedUpdate = true; + + return this; + + } + + rotateX( angle ) { + + // rotate geometry around world x-axis + + _m1.makeRotationX( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + rotateY( angle ) { + + // rotate geometry around world y-axis + + _m1.makeRotationY( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + rotateZ( angle ) { + + // rotate geometry around world z-axis + + _m1.makeRotationZ( angle ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + translate( x, y, z ) { + + // translate geometry + + _m1.makeTranslation( x, y, z ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + scale( x, y, z ) { + + // scale geometry + + _m1.makeScale( x, y, z ); + + this.applyMatrix4( _m1 ); + + return this; + + } + + lookAt( vector ) { + + _obj.lookAt( vector ); + + _obj.updateMatrix(); + + this.applyMatrix4( _obj.matrix ); + + return this; + + } + + fromBufferGeometry( geometry ) { + + const scope = this; + + const index = geometry.index !== null ? geometry.index : undefined; + const attributes = geometry.attributes; + + if ( attributes.position === undefined ) { + + console.error( 'THREE.Geometry.fromBufferGeometry(): Position attribute required for conversion.' ); + return this; + + } + + const position = attributes.position; + const normal = attributes.normal; + const color = attributes.color; + const uv = attributes.uv; + const uv2 = attributes.uv2; + + if ( uv2 !== undefined ) this.faceVertexUvs[ 1 ] = []; + + for ( let i = 0; i < position.count; i ++ ) { + + scope.vertices.push( new Vector3().fromBufferAttribute( position, i ) ); + + if ( color !== undefined ) { + + scope.colors.push( new Color().fromBufferAttribute( color, i ) ); + + } + + } + + function addFace( a, b, c, materialIndex ) { + + const vertexColors = ( color === undefined ) ? [] : [ + scope.colors[ a ].clone(), + scope.colors[ b ].clone(), + scope.colors[ c ].clone() + ]; + + const vertexNormals = ( normal === undefined ) ? [] : [ + new Vector3().fromBufferAttribute( normal, a ), + new Vector3().fromBufferAttribute( normal, b ), + new Vector3().fromBufferAttribute( normal, c ) + ]; + + const face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex ); + + scope.faces.push( face ); + + if ( uv !== undefined ) { + + scope.faceVertexUvs[ 0 ].push( [ + new Vector2().fromBufferAttribute( uv, a ), + new Vector2().fromBufferAttribute( uv, b ), + new Vector2().fromBufferAttribute( uv, c ) + ] ); + + } + + if ( uv2 !== undefined ) { + + scope.faceVertexUvs[ 1 ].push( [ + new Vector2().fromBufferAttribute( uv2, a ), + new Vector2().fromBufferAttribute( uv2, b ), + new Vector2().fromBufferAttribute( uv2, c ) + ] ); + + } + + } + + const groups = geometry.groups; + + if ( groups.length > 0 ) { + + for ( let i = 0; i < groups.length; i ++ ) { + + const group = groups[ i ]; + + const start = group.start; + const count = group.count; + + for ( let j = start, jl = start + count; j < jl; j += 3 ) { + + if ( index !== undefined ) { + + addFace( index.getX( j ), index.getX( j + 1 ), index.getX( j + 2 ), group.materialIndex ); + + } else { + + addFace( j, j + 1, j + 2, group.materialIndex ); + + } + + } + + } + + } else { + + if ( index !== undefined ) { + + for ( let i = 0; i < index.count; i += 3 ) { + + addFace( index.getX( i ), index.getX( i + 1 ), index.getX( i + 2 ) ); + + } + + } else { + + for ( let i = 0; i < position.count; i += 3 ) { + + addFace( i, i + 1, i + 2 ); + + } + + } + + } + + this.computeFaceNormals(); + + if ( geometry.boundingBox !== null ) { + + this.boundingBox = geometry.boundingBox.clone(); + + } + + if ( geometry.boundingSphere !== null ) { + + this.boundingSphere = geometry.boundingSphere.clone(); + + } + + return this; + + } + + center() { + + this.computeBoundingBox(); + + this.boundingBox.getCenter( _offset ).negate(); + + this.translate( _offset.x, _offset.y, _offset.z ); + + return this; + + } + + normalize() { + + this.computeBoundingSphere(); + + const center = this.boundingSphere.center; + const radius = this.boundingSphere.radius; + + const s = radius === 0 ? 1 : 1.0 / radius; + + const matrix = new Matrix4(); + matrix.set( + s, 0, 0, - s * center.x, + 0, s, 0, - s * center.y, + 0, 0, s, - s * center.z, + 0, 0, 0, 1 + ); + + this.applyMatrix4( matrix ); + + return this; + + } + + computeFaceNormals() { + + const cb = new Vector3(), ab = new Vector3(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vA = this.vertices[ face.a ]; + const vB = this.vertices[ face.b ]; + const vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + cb.normalize(); + + face.normal.copy( cb ); + + } + + } + + computeVertexNormals( areaWeighted = true ) { + + const vertices = new Array( this.vertices.length ); + + for ( let v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ] = new Vector3(); + + } + + if ( areaWeighted ) { + + // vertex normals weighted by triangle areas + // http://www.iquilezles.org/www/articles/normals/normals.htm + + const cb = new Vector3(), ab = new Vector3(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vA = this.vertices[ face.a ]; + const vB = this.vertices[ face.b ]; + const vC = this.vertices[ face.c ]; + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ); + + vertices[ face.a ].add( cb ); + vertices[ face.b ].add( cb ); + vertices[ face.c ].add( cb ); + + } + + } else { + + this.computeFaceNormals(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + vertices[ face.a ].add( face.normal ); + vertices[ face.b ].add( face.normal ); + vertices[ face.c ].add( face.normal ); + + } + + } + + for ( let v = 0, vl = this.vertices.length; v < vl; v ++ ) { + + vertices[ v ].normalize(); + + } + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + vertexNormals[ 0 ].copy( vertices[ face.a ] ); + vertexNormals[ 1 ].copy( vertices[ face.b ] ); + vertexNormals[ 2 ].copy( vertices[ face.c ] ); + + } else { + + vertexNormals[ 0 ] = vertices[ face.a ].clone(); + vertexNormals[ 1 ] = vertices[ face.b ].clone(); + vertexNormals[ 2 ] = vertices[ face.c ].clone(); + + } + + } + + if ( this.faces.length > 0 ) { + + this.normalsNeedUpdate = true; + + } + + } + + computeFlatVertexNormals() { + + this.computeFaceNormals(); + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + vertexNormals[ 0 ].copy( face.normal ); + vertexNormals[ 1 ].copy( face.normal ); + vertexNormals[ 2 ].copy( face.normal ); + + } else { + + vertexNormals[ 0 ] = face.normal.clone(); + vertexNormals[ 1 ] = face.normal.clone(); + vertexNormals[ 2 ] = face.normal.clone(); + + } + + } + + if ( this.faces.length > 0 ) { + + this.normalsNeedUpdate = true; + + } + + } + + computeMorphNormals() { + + // save original normals + // - create temp variables on first access + // otherwise just copy (for faster repeated calls) + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + if ( ! face.__originalFaceNormal ) { + + face.__originalFaceNormal = face.normal.clone(); + + } else { + + face.__originalFaceNormal.copy( face.normal ); + + } + + if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = []; + + for ( let i = 0, il = face.vertexNormals.length; i < il; i ++ ) { + + if ( ! face.__originalVertexNormals[ i ] ) { + + face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone(); + + } else { + + face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] ); + + } + + } + + } + + // use temp geometry to compute face and vertex normals for each morph + + const tmpGeo = new Geometry(); + tmpGeo.faces = this.faces; + + for ( let i = 0, il = this.morphTargets.length; i < il; i ++ ) { + + // create on first access + + if ( ! this.morphNormals[ i ] ) { + + this.morphNormals[ i ] = {}; + this.morphNormals[ i ].faceNormals = []; + this.morphNormals[ i ].vertexNormals = []; + + const dstNormalsFace = this.morphNormals[ i ].faceNormals; + const dstNormalsVertex = this.morphNormals[ i ].vertexNormals; + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const faceNormal = new Vector3(); + const vertexNormals = { a: new Vector3(), b: new Vector3(), c: new Vector3() }; + + dstNormalsFace.push( faceNormal ); + dstNormalsVertex.push( vertexNormals ); + + } + + } + + const morphNormals = this.morphNormals[ i ]; + + // set vertices to morph target + + tmpGeo.vertices = this.morphTargets[ i ].vertices; + + // compute morph normals + + tmpGeo.computeFaceNormals(); + tmpGeo.computeVertexNormals(); + + // store morph normals + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + const faceNormal = morphNormals.faceNormals[ f ]; + const vertexNormals = morphNormals.vertexNormals[ f ]; + + faceNormal.copy( face.normal ); + + vertexNormals.a.copy( face.vertexNormals[ 0 ] ); + vertexNormals.b.copy( face.vertexNormals[ 1 ] ); + vertexNormals.c.copy( face.vertexNormals[ 2 ] ); + + } + + } + + // restore original normals + + for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) { + + const face = this.faces[ f ]; + + face.normal = face.__originalFaceNormal; + face.vertexNormals = face.__originalVertexNormals; + + } + + } + + computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + this.boundingBox.setFromPoints( this.vertices ); + + } + + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + this.boundingSphere.setFromPoints( this.vertices ); + + } + + merge( geometry, matrix, materialIndexOffset = 0 ) { + + if ( ! ( geometry && geometry.isGeometry ) ) { + + console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry ); + return; + + } + + let normalMatrix; + const vertexOffset = this.vertices.length, + vertices1 = this.vertices, + vertices2 = geometry.vertices, + faces1 = this.faces, + faces2 = geometry.faces, + colors1 = this.colors, + colors2 = geometry.colors; + + if ( matrix !== undefined ) { + + normalMatrix = new Matrix3().getNormalMatrix( matrix ); + + } + + // vertices + + for ( let i = 0, il = vertices2.length; i < il; i ++ ) { + + const vertex = vertices2[ i ]; + + const vertexCopy = vertex.clone(); + + if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix ); + + vertices1.push( vertexCopy ); + + } + + // colors + + for ( let i = 0, il = colors2.length; i < il; i ++ ) { + + colors1.push( colors2[ i ].clone() ); + + } + + // faces + + for ( let i = 0, il = faces2.length; i < il; i ++ ) { + + const face = faces2[ i ]; + let normal, color; + const faceVertexNormals = face.vertexNormals, + faceVertexColors = face.vertexColors; + + const faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset ); + faceCopy.normal.copy( face.normal ); + + if ( normalMatrix !== undefined ) { + + faceCopy.normal.applyMatrix3( normalMatrix ).normalize(); + + } + + for ( let j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) { + + normal = faceVertexNormals[ j ].clone(); + + if ( normalMatrix !== undefined ) { + + normal.applyMatrix3( normalMatrix ).normalize(); + + } + + faceCopy.vertexNormals.push( normal ); + + } + + faceCopy.color.copy( face.color ); + + for ( let j = 0, jl = faceVertexColors.length; j < jl; j ++ ) { + + color = faceVertexColors[ j ]; + faceCopy.vertexColors.push( color.clone() ); + + } + + faceCopy.materialIndex = face.materialIndex + materialIndexOffset; + + faces1.push( faceCopy ); + + } + + // uvs + + for ( let i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) { + + const faceVertexUvs2 = geometry.faceVertexUvs[ i ]; + + if ( this.faceVertexUvs[ i ] === undefined ) this.faceVertexUvs[ i ] = []; + + for ( let j = 0, jl = faceVertexUvs2.length; j < jl; j ++ ) { + + const uvs2 = faceVertexUvs2[ j ], uvsCopy = []; + + for ( let k = 0, kl = uvs2.length; k < kl; k ++ ) { + + uvsCopy.push( uvs2[ k ].clone() ); + + } + + this.faceVertexUvs[ i ].push( uvsCopy ); + + } + + } + + } + + mergeMesh( mesh ) { + + if ( ! ( mesh && mesh.isMesh ) ) { + + console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh ); + return; + + } + + if ( mesh.matrixAutoUpdate ) mesh.updateMatrix(); + + this.merge( mesh.geometry, mesh.matrix ); + + } + + /* + * Checks for duplicate vertices with hashmap. + * Duplicated vertices are removed + * and faces' vertices are updated. + */ + + mergeVertices( precisionPoints = 4 ) { + + const verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique) + const unique = [], changes = []; + + const precision = Math.pow( 10, precisionPoints ); + + for ( let i = 0, il = this.vertices.length; i < il; i ++ ) { + + const v = this.vertices[ i ]; + const key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision ); + + if ( verticesMap[ key ] === undefined ) { + + verticesMap[ key ] = i; + unique.push( this.vertices[ i ] ); + changes[ i ] = unique.length - 1; + + } else { + + //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]); + changes[ i ] = changes[ verticesMap[ key ] ]; + + } + + } + + + // if faces are completely degenerate after merging vertices, we + // have to remove them from the geometry. + const faceIndicesToRemove = []; + + for ( let i = 0, il = this.faces.length; i < il; i ++ ) { + + const face = this.faces[ i ]; + + face.a = changes[ face.a ]; + face.b = changes[ face.b ]; + face.c = changes[ face.c ]; + + const indices = [ face.a, face.b, face.c ]; + + // if any duplicate vertices are found in a Face3 + // we have to remove the face as nothing can be saved + for ( let n = 0; n < 3; n ++ ) { + + if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) { + + faceIndicesToRemove.push( i ); + break; + + } + + } + + } + + for ( let i = faceIndicesToRemove.length - 1; i >= 0; i -- ) { + + const idx = faceIndicesToRemove[ i ]; + + this.faces.splice( idx, 1 ); + + for ( let j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) { + + this.faceVertexUvs[ j ].splice( idx, 1 ); + + } + + } + + // Use unique set of vertices + + const diff = this.vertices.length - unique.length; + this.vertices = unique; + return diff; + + } + + setFromPoints( points ) { + + this.vertices = []; + + for ( let i = 0, l = points.length; i < l; i ++ ) { + + const point = points[ i ]; + this.vertices.push( new Vector3( point.x, point.y, point.z || 0 ) ); + + } + + return this; + + } + + sortFacesByMaterialIndex() { + + const faces = this.faces; + const length = faces.length; + + // tag faces + + for ( let i = 0; i < length; i ++ ) { + + faces[ i ]._id = i; + + } + + // sort faces + + function materialIndexSort( a, b ) { + + return a.materialIndex - b.materialIndex; + + } + + faces.sort( materialIndexSort ); + + // sort uvs + + const uvs1 = this.faceVertexUvs[ 0 ]; + const uvs2 = this.faceVertexUvs[ 1 ]; + + let newUvs1, newUvs2; + + if ( uvs1 && uvs1.length === length ) newUvs1 = []; + if ( uvs2 && uvs2.length === length ) newUvs2 = []; + + for ( let i = 0; i < length; i ++ ) { + + const id = faces[ i ]._id; + + if ( newUvs1 ) newUvs1.push( uvs1[ id ] ); + if ( newUvs2 ) newUvs2.push( uvs2[ id ] ); + + } + + if ( newUvs1 ) this.faceVertexUvs[ 0 ] = newUvs1; + if ( newUvs2 ) this.faceVertexUvs[ 1 ] = newUvs2; + + } + + toJSON() { + + const data = { + metadata: { + version: 4.5, + type: 'Geometry', + generator: 'Geometry.toJSON' + } + }; + + // standard Geometry serialization + + data.uuid = this.uuid; + data.type = this.type; + if ( this.name !== '' ) data.name = this.name; + + if ( this.parameters !== undefined ) { + + const parameters = this.parameters; + + for ( const key in parameters ) { + + if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ]; + + } + + return data; + + } + + const vertices = []; + + for ( let i = 0; i < this.vertices.length; i ++ ) { + + const vertex = this.vertices[ i ]; + vertices.push( vertex.x, vertex.y, vertex.z ); + + } + + const faces = []; + const normals = []; + const normalsHash = {}; + const colors = []; + const colorsHash = {}; + const uvs = []; + const uvsHash = {}; + + for ( let i = 0; i < this.faces.length; i ++ ) { + + const face = this.faces[ i ]; + + const hasMaterial = true; + const hasFaceUv = false; // deprecated + const hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined; + const hasFaceNormal = face.normal.length() > 0; + const hasFaceVertexNormal = face.vertexNormals.length > 0; + const hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1; + const hasFaceVertexColor = face.vertexColors.length > 0; + + let faceType = 0; + + faceType = setBit( faceType, 0, 0 ); // isQuad + faceType = setBit( faceType, 1, hasMaterial ); + faceType = setBit( faceType, 2, hasFaceUv ); + faceType = setBit( faceType, 3, hasFaceVertexUv ); + faceType = setBit( faceType, 4, hasFaceNormal ); + faceType = setBit( faceType, 5, hasFaceVertexNormal ); + faceType = setBit( faceType, 6, hasFaceColor ); + faceType = setBit( faceType, 7, hasFaceVertexColor ); + + faces.push( faceType ); + faces.push( face.a, face.b, face.c ); + faces.push( face.materialIndex ); + + if ( hasFaceVertexUv ) { + + const faceVertexUvs = this.faceVertexUvs[ 0 ][ i ]; + + faces.push( + getUvIndex( faceVertexUvs[ 0 ] ), + getUvIndex( faceVertexUvs[ 1 ] ), + getUvIndex( faceVertexUvs[ 2 ] ) + ); + + } + + if ( hasFaceNormal ) { + + faces.push( getNormalIndex( face.normal ) ); + + } + + if ( hasFaceVertexNormal ) { + + const vertexNormals = face.vertexNormals; + + faces.push( + getNormalIndex( vertexNormals[ 0 ] ), + getNormalIndex( vertexNormals[ 1 ] ), + getNormalIndex( vertexNormals[ 2 ] ) + ); + + } + + if ( hasFaceColor ) { + + faces.push( getColorIndex( face.color ) ); + + } + + if ( hasFaceVertexColor ) { + + const vertexColors = face.vertexColors; + + faces.push( + getColorIndex( vertexColors[ 0 ] ), + getColorIndex( vertexColors[ 1 ] ), + getColorIndex( vertexColors[ 2 ] ) + ); + + } + + } + + function setBit( value, position, enabled ) { + + return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) ); + + } + + function getNormalIndex( normal ) { + + const hash = normal.x.toString() + normal.y.toString() + normal.z.toString(); + + if ( normalsHash[ hash ] !== undefined ) { + + return normalsHash[ hash ]; + + } + + normalsHash[ hash ] = normals.length / 3; + normals.push( normal.x, normal.y, normal.z ); + + return normalsHash[ hash ]; + + } + + function getColorIndex( color ) { + + const hash = color.r.toString() + color.g.toString() + color.b.toString(); + + if ( colorsHash[ hash ] !== undefined ) { + + return colorsHash[ hash ]; + + } + + colorsHash[ hash ] = colors.length; + colors.push( color.getHex() ); + + return colorsHash[ hash ]; + + } + + function getUvIndex( uv ) { + + const hash = uv.x.toString() + uv.y.toString(); + + if ( uvsHash[ hash ] !== undefined ) { + + return uvsHash[ hash ]; + + } + + uvsHash[ hash ] = uvs.length / 2; + uvs.push( uv.x, uv.y ); + + return uvsHash[ hash ]; + + } + + data.data = {}; + + data.data.vertices = vertices; + data.data.normals = normals; + if ( colors.length > 0 ) data.data.colors = colors; + if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility + data.data.faces = faces; + + return data; + + } + + clone() { + + /* + // Handle primitives + + const parameters = this.parameters; + + if ( parameters !== undefined ) { + + const values = []; + + for ( const key in parameters ) { + + values.push( parameters[ key ] ); + + } + + const geometry = Object.create( this.constructor.prototype ); + this.constructor.apply( geometry, values ); + return geometry; + + } + + return new this.constructor().copy( this ); + */ + + return new Geometry().copy( this ); + + } + + copy( source ) { + + // reset + + this.vertices = []; + this.colors = []; + this.faces = []; + this.faceVertexUvs = [[]]; + this.morphTargets = []; + this.morphNormals = []; + this.skinWeights = []; + this.skinIndices = []; + this.lineDistances = []; + this.boundingBox = null; + this.boundingSphere = null; + + // name + + this.name = source.name; + + // vertices + + const vertices = source.vertices; + + for ( let i = 0, il = vertices.length; i < il; i ++ ) { + + this.vertices.push( vertices[ i ].clone() ); + + } + + // colors + + const colors = source.colors; + + for ( let i = 0, il = colors.length; i < il; i ++ ) { + + this.colors.push( colors[ i ].clone() ); + + } + + // faces + + const faces = source.faces; + + for ( let i = 0, il = faces.length; i < il; i ++ ) { + + this.faces.push( faces[ i ].clone() ); + + } + + // face vertex uvs + + for ( let i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) { + + const faceVertexUvs = source.faceVertexUvs[ i ]; + + if ( this.faceVertexUvs[ i ] === undefined ) { + + this.faceVertexUvs[ i ] = []; + + } + + for ( let j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) { + + const uvs = faceVertexUvs[ j ], uvsCopy = []; + + for ( let k = 0, kl = uvs.length; k < kl; k ++ ) { + + const uv = uvs[ k ]; + + uvsCopy.push( uv.clone() ); + + } + + this.faceVertexUvs[ i ].push( uvsCopy ); + + } + + } + + // morph targets + + const morphTargets = source.morphTargets; + + for ( let i = 0, il = morphTargets.length; i < il; i ++ ) { + + const morphTarget = {}; + morphTarget.name = morphTargets[ i ].name; + + // vertices + + if ( morphTargets[ i ].vertices !== undefined ) { + + morphTarget.vertices = []; + + for ( let j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) { + + morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() ); + + } + + } + + // normals + + if ( morphTargets[ i ].normals !== undefined ) { + + morphTarget.normals = []; + + for ( let j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) { + + morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() ); + + } + + } + + this.morphTargets.push( morphTarget ); + + } + + // morph normals + + const morphNormals = source.morphNormals; + + for ( let i = 0, il = morphNormals.length; i < il; i ++ ) { + + const morphNormal = {}; + + // vertex normals + + if ( morphNormals[ i ].vertexNormals !== undefined ) { + + morphNormal.vertexNormals = []; + + for ( let j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) { + + const srcVertexNormal = morphNormals[ i ].vertexNormals[ j ]; + const destVertexNormal = {}; + + destVertexNormal.a = srcVertexNormal.a.clone(); + destVertexNormal.b = srcVertexNormal.b.clone(); + destVertexNormal.c = srcVertexNormal.c.clone(); + + morphNormal.vertexNormals.push( destVertexNormal ); + + } + + } + + // face normals + + if ( morphNormals[ i ].faceNormals !== undefined ) { + + morphNormal.faceNormals = []; + + for ( let j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) { + + morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() ); + + } + + } + + this.morphNormals.push( morphNormal ); + + } + + // skin weights + + const skinWeights = source.skinWeights; + + for ( let i = 0, il = skinWeights.length; i < il; i ++ ) { + + this.skinWeights.push( skinWeights[ i ].clone() ); + + } + + // skin indices + + const skinIndices = source.skinIndices; + + for ( let i = 0, il = skinIndices.length; i < il; i ++ ) { + + this.skinIndices.push( skinIndices[ i ].clone() ); + + } + + // line distances + + const lineDistances = source.lineDistances; + + for ( let i = 0, il = lineDistances.length; i < il; i ++ ) { + + this.lineDistances.push( lineDistances[ i ] ); + + } + + // bounding box + + const boundingBox = source.boundingBox; + + if ( boundingBox !== null ) { + + this.boundingBox = boundingBox.clone(); + + } + + // bounding sphere + + const boundingSphere = source.boundingSphere; + + if ( boundingSphere !== null ) { + + this.boundingSphere = boundingSphere.clone(); + + } + + // update flags + + this.elementsNeedUpdate = source.elementsNeedUpdate; + this.verticesNeedUpdate = source.verticesNeedUpdate; + this.uvsNeedUpdate = source.uvsNeedUpdate; + this.normalsNeedUpdate = source.normalsNeedUpdate; + this.colorsNeedUpdate = source.colorsNeedUpdate; + this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate; + this.groupsNeedUpdate = source.groupsNeedUpdate; + + return this; + + } + + toBufferGeometry() { + + const geometry = new DirectGeometry().fromGeometry( this ); + + const buffergeometry = new BufferGeometry(); + + const positions = new Float32Array( geometry.vertices.length * 3 ); + buffergeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) ); + + if ( geometry.normals.length > 0 ) { + + const normals = new Float32Array( geometry.normals.length * 3 ); + buffergeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) ); + + } + + if ( geometry.colors.length > 0 ) { + + const colors = new Float32Array( geometry.colors.length * 3 ); + buffergeometry.setAttribute( 'color', new BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) ); + + } + + if ( geometry.uvs.length > 0 ) { + + const uvs = new Float32Array( geometry.uvs.length * 2 ); + buffergeometry.setAttribute( 'uv', new BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) ); + + } + + if ( geometry.uvs2.length > 0 ) { + + const uvs2 = new Float32Array( geometry.uvs2.length * 2 ); + buffergeometry.setAttribute( 'uv2', new BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) ); + + } + + // groups + + buffergeometry.groups = geometry.groups; + + // morphs + + for ( const name in geometry.morphTargets ) { + + const array = []; + const morphTargets = geometry.morphTargets[ name ]; + + for ( let i = 0, l = morphTargets.length; i < l; i ++ ) { + + const morphTarget = morphTargets[ i ]; + + const attribute = new Float32BufferAttribute( morphTarget.data.length * 3, 3 ); + attribute.name = morphTarget.name; + + array.push( attribute.copyVector3sArray( morphTarget.data ) ); + + } + + buffergeometry.morphAttributes[ name ] = array; + + } + + // skinning + + if ( geometry.skinIndices.length > 0 ) { + + const skinIndices = new Float32BufferAttribute( geometry.skinIndices.length * 4, 4 ); + buffergeometry.setAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) ); + + } + + if ( geometry.skinWeights.length > 0 ) { + + const skinWeights = new Float32BufferAttribute( geometry.skinWeights.length * 4, 4 ); + buffergeometry.setAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) ); + + } + + // + + if ( geometry.boundingSphere !== null ) { + + buffergeometry.boundingSphere = geometry.boundingSphere.clone(); + + } + + if ( geometry.boundingBox !== null ) { + + buffergeometry.boundingBox = geometry.boundingBox.clone(); + + } + + return buffergeometry; + + } + + computeTangents() { + + console.error( 'THREE.Geometry: .computeTangents() has been removed.' ); + + } + + computeLineDistances() { + + console.error( 'THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.' ); + + } + + applyMatrix( matrix ) { + + console.warn( 'THREE.Geometry: .applyMatrix() has been renamed to .applyMatrix4().' ); + return this.applyMatrix4( matrix ); + + } + + dispose() { + + this.dispatchEvent( { type: 'dispose' } ); + + } + + 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; + + } + +} + +Geometry.prototype.isGeometry = true; + +class DirectGeometry { + + constructor() { + + this.vertices = []; + this.normals = []; + this.colors = []; + this.uvs = []; + this.uvs2 = []; + + this.groups = []; + + this.morphTargets = {}; + + this.skinWeights = []; + this.skinIndices = []; + + // this.lineDistances = []; + + this.boundingBox = null; + this.boundingSphere = null; + + // update flags + + this.verticesNeedUpdate = false; + this.normalsNeedUpdate = false; + this.colorsNeedUpdate = false; + this.uvsNeedUpdate = false; + this.groupsNeedUpdate = false; + + } + + computeGroups( geometry ) { + + const groups = []; + + let group, i; + let materialIndex = undefined; + + const faces = geometry.faces; + + for ( i = 0; i < faces.length; i ++ ) { + + const face = faces[ i ]; + + // materials + + if ( face.materialIndex !== materialIndex ) { + + materialIndex = face.materialIndex; + + if ( group !== undefined ) { + + group.count = ( i * 3 ) - group.start; + groups.push( group ); + + } + + group = { + start: i * 3, + materialIndex: materialIndex + }; + + } + + } + + if ( group !== undefined ) { + + group.count = ( i * 3 ) - group.start; + groups.push( group ); + + } + + this.groups = groups; + + } + + fromGeometry( geometry ) { + + const faces = geometry.faces; + const vertices = geometry.vertices; + const faceVertexUvs = geometry.faceVertexUvs; + + const hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0; + const hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0; + + // morphs + + const morphTargets = geometry.morphTargets; + const morphTargetsLength = morphTargets.length; + + let morphTargetsPosition; + + if ( morphTargetsLength > 0 ) { + + morphTargetsPosition = []; + + for ( let i = 0; i < morphTargetsLength; i ++ ) { + + morphTargetsPosition[ i ] = { + name: morphTargets[ i ].name, + data: [] + }; + + } + + this.morphTargets.position = morphTargetsPosition; + + } + + const morphNormals = geometry.morphNormals; + const morphNormalsLength = morphNormals.length; + + let morphTargetsNormal; + + if ( morphNormalsLength > 0 ) { + + morphTargetsNormal = []; + + for ( let i = 0; i < morphNormalsLength; i ++ ) { + + morphTargetsNormal[ i ] = { + name: morphNormals[ i ].name, + data: [] + }; + + } + + this.morphTargets.normal = morphTargetsNormal; + + } + + // skins + + const skinIndices = geometry.skinIndices; + const skinWeights = geometry.skinWeights; + + const hasSkinIndices = skinIndices.length === vertices.length; + const hasSkinWeights = skinWeights.length === vertices.length; + + // + + if ( vertices.length > 0 && faces.length === 0 ) { + + console.error( 'THREE.DirectGeometry: Faceless geometries are not supported.' ); + + } + + for ( let i = 0; i < faces.length; i ++ ) { + + const face = faces[ i ]; + + this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] ); + + const vertexNormals = face.vertexNormals; + + if ( vertexNormals.length === 3 ) { + + this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] ); + + } else { + + const normal = face.normal; + + this.normals.push( normal, normal, normal ); + + } + + const vertexColors = face.vertexColors; + + if ( vertexColors.length === 3 ) { + + this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] ); + + } else { + + const color = face.color; + + this.colors.push( color, color, color ); + + } + + if ( hasFaceVertexUv === true ) { + + const vertexUvs = faceVertexUvs[ 0 ][ i ]; + + if ( vertexUvs !== undefined ) { + + this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); + + } else { + + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i ); + + this.uvs.push( new Vector2(), new Vector2(), new Vector2() ); + + } + + } + + if ( hasFaceVertexUv2 === true ) { + + const vertexUvs = faceVertexUvs[ 1 ][ i ]; + + if ( vertexUvs !== undefined ) { + + this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] ); + + } else { + + console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i ); + + this.uvs2.push( new Vector2(), new Vector2(), new Vector2() ); + + } + + } + + // morphs + + for ( let j = 0; j < morphTargetsLength; j ++ ) { + + const morphTarget = morphTargets[ j ].vertices; + + morphTargetsPosition[ j ].data.push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] ); + + } + + for ( let j = 0; j < morphNormalsLength; j ++ ) { + + const morphNormal = morphNormals[ j ].vertexNormals[ i ]; + + morphTargetsNormal[ j ].data.push( morphNormal.a, morphNormal.b, morphNormal.c ); + + } + + // skins + + if ( hasSkinIndices ) { + + this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] ); + + } + + if ( hasSkinWeights ) { + + this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] ); + + } + + } + + this.computeGroups( geometry ); + + this.verticesNeedUpdate = geometry.verticesNeedUpdate; + this.normalsNeedUpdate = geometry.normalsNeedUpdate; + this.colorsNeedUpdate = geometry.colorsNeedUpdate; + this.uvsNeedUpdate = geometry.uvsNeedUpdate; + this.groupsNeedUpdate = geometry.groupsNeedUpdate; + + if ( geometry.boundingSphere !== null ) { + + this.boundingSphere = geometry.boundingSphere.clone(); + + } + + if ( geometry.boundingBox !== null ) { + + this.boundingBox = geometry.boundingBox.clone(); + + } + + return this; + + } + +} + +class Face3 { + + constructor( a, b, c, normal, color, materialIndex = 0 ) { + + this.a = a; + this.b = b; + this.c = c; + + this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3(); + this.vertexNormals = Array.isArray( normal ) ? normal : []; + + this.color = ( color && color.isColor ) ? color : new Color(); + this.vertexColors = Array.isArray( color ) ? color : []; + + this.materialIndex = materialIndex; + + } + + clone() { + + return new this.constructor().copy( this ); + + } + + copy( source ) { + + this.a = source.a; + this.b = source.b; + this.c = source.c; + + this.normal.copy( source.normal ); + this.color.copy( source.color ); + + this.materialIndex = source.materialIndex; + + for ( let i = 0, il = source.vertexNormals.length; i < il; i ++ ) { + + this.vertexNormals[ i ] = source.vertexNormals[ i ].clone(); + + } + + for ( let i = 0, il = source.vertexColors.length; i < il; i ++ ) { + + this.vertexColors[ i ] = source.vertexColors[ i ].clone(); + + } + + return this; + + } + +} + +export { Face3, Geometry }; diff --git a/jsm/effects/AnaglyphEffect.js b/jsm/effects/AnaglyphEffect.js new file mode 100644 index 0000000..d2b4134 --- /dev/null +++ b/jsm/effects/AnaglyphEffect.js @@ -0,0 +1,168 @@ +import { + LinearFilter, + Matrix3, + Mesh, + NearestFilter, + OrthographicCamera, + PlaneGeometry, + RGBAFormat, + Scene, + ShaderMaterial, + StereoCamera, + WebGLRenderTarget +} from 'three'; + +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 + + this.colorMatrixLeft = new Matrix3().fromArray( [ + 0.456100, - 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 }; + + const _renderTargetL = new WebGLRenderTarget( width, height, _params ); + const _renderTargetR = new WebGLRenderTarget( width, height, _params ); + + const _material = new ShaderMaterial( { + + uniforms: { + + 'mapLeft': { value: _renderTargetL.texture }, + 'mapRight': { value: _renderTargetR.texture }, + + 'colorMatrixLeft': { value: this.colorMatrixLeft }, + 'colorMatrixRight': { value: this.colorMatrixRight } + + }, + + vertexShader: [ + + 'varying vec2 vUv;', + + 'void main() {', + + ' vUv = vec2( uv.x, uv.y );', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}' + + ].join( '\n' ), + + fragmentShader: [ + + 'uniform sampler2D mapLeft;', + 'uniform sampler2D mapRight;', + 'varying vec2 vUv;', + + 'uniform mat3 colorMatrixLeft;', + 'uniform mat3 colorMatrixRight;', + + // These functions implement sRGB linearization and gamma correction + + 'float lin( float c ) {', + ' return c <= 0.04045 ? c * 0.0773993808 :', + ' pow( c * 0.9478672986 + 0.0521327014, 2.4 );', + '}', + + 'vec4 lin( vec4 c ) {', + ' return vec4( lin( c.r ), lin( c.g ), lin( c.b ), c.a );', + '}', + + 'float dev( float c ) {', + ' return c <= 0.0031308 ? c * 12.92', + ' : pow( c, 0.41666 ) * 1.055 - 0.055;', + '}', + + + 'void main() {', + + ' vec2 uv = vUv;', + + ' vec4 colorL = lin( texture2D( mapLeft, uv ) );', + ' vec4 colorR = lin( texture2D( mapRight, uv ) );', + + ' vec3 color = clamp(', + ' colorMatrixLeft * colorL.rgb +', + ' colorMatrixRight * colorR.rgb, 0., 1. );', + + ' gl_FragColor = vec4(', + ' dev( color.r ), dev( color.g ), dev( color.b ),', + ' max( colorL.a, colorR.a ) );', + + '}' + + ].join( '\n' ) + + } ); + + const _mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material ); + _scene.add( _mesh ); + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + const pixelRatio = renderer.getPixelRatio(); + + _renderTargetL.setSize( width * pixelRatio, height * pixelRatio ); + _renderTargetR.setSize( width * pixelRatio, height * pixelRatio ); + + }; + + this.render = function ( scene, camera ) { + + const currentRenderTarget = renderer.getRenderTarget(); + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.setRenderTarget( _renderTargetL ); + renderer.clear(); + renderer.render( scene, _stereo.cameraL ); + + renderer.setRenderTarget( _renderTargetR ); + renderer.clear(); + renderer.render( scene, _stereo.cameraR ); + + renderer.setRenderTarget( null ); + renderer.render( _scene, _camera ); + + renderer.setRenderTarget( currentRenderTarget ); + + }; + + this.dispose = function () { + + _renderTargetL.dispose(); + _renderTargetR.dispose(); + _mesh.geometry.dispose(); + _mesh.material.dispose(); + + }; + + } + +} + +export { AnaglyphEffect }; diff --git a/jsm/effects/AsciiEffect.js b/jsm/effects/AsciiEffect.js new file mode 100644 index 0000000..b42b73a --- /dev/null +++ b/jsm/effects/AsciiEffect.js @@ -0,0 +1,279 @@ +/** + * Ascii generation is based on https://github.com/hassadee/jsascii/blob/master/jsascii.js + * + * 16 April 2012 - @blurspline + */ + +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(''); + + // Some ASCII settings + + 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 strResolution = 'low'; + + let width, height; + + const domElement = document.createElement( 'div' ); + domElement.style.cursor = 'default'; + + const oAscii = document.createElement( 'table' ); + domElement.appendChild( oAscii ); + + let iWidth, iHeight; + let oImg; + + this.setSize = function ( w, h ) { + + width = w; + height = h; + + renderer.setSize( w, h ); + + initAsciiSize(); + + }; + + + this.render = function ( scene, camera ) { + + renderer.render( scene, camera ); + asciifyImage( oAscii ); + + }; + + this.domElement = domElement; + + + // Throw in ascii library from https://github.com/hassadee/jsascii/blob/master/jsascii.js (MIT License) + + 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; + + 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; + + } + + 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( '' ); + const strFont = 'courier new, monospace'; + + const oCanvasImg = renderer.domElement; + + const oCanvas = document.createElement( 'canvas' ); + if ( ! oCanvas.getContext ) { + + return; + + } + + const oCtx = oCanvas.getContext( '2d' ); + if ( ! oCtx.getImageData ) { + + return; + + } + + let aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList ); + + if ( charSet ) aCharList = charSet; + + let fResolution = 0.5; + + switch ( strResolution ) { + + case 'low' : fResolution = 0.25; break; + case 'medium' : fResolution = 0.5; break; + case 'high' : fResolution = 1; break; + + } + + if ( bResolution ) fResolution = bResolution; + + // Setup dom + + const fFontSize = ( 2 / fResolution ) * iScale; + const fLineHeight = ( 2 / fResolution ) * iScale; + + // adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width. + + let 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 == '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; + + } + + } + + + // 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; + + } + + } + + strChars += '
'; + + } + + oAscii.innerHTML = '' + strChars + ''; + + // console.timeEnd('rendering'); + + // return oAscii; + + } + + } + +} + +export { AsciiEffect }; diff --git a/jsm/effects/OutlineEffect.js b/jsm/effects/OutlineEffect.js new file mode 100644 index 0000000..02c70fa --- /dev/null +++ b/jsm/effects/OutlineEffect.js @@ -0,0 +1,574 @@ +import { + BackSide, + Color, + ShaderMaterial, + UniformsLib, + UniformsUtils +} from 'three'; + +/** + * Reference: https://en.wikipedia.org/wiki/Cel_shading + * + * API + * + * 1. Traditional + * + * const effect = new OutlineEffect( renderer ); + * + * function render() { + * + * effect.render( scene, camera ); + * + * } + * + * 2. VR compatible + * + * const effect = new OutlineEffect( renderer ); + * let renderingOutline = false; + * + * scene.onAfterRender = function () { + * + * if ( renderingOutline ) return; + * + * renderingOutline = true; + * + * effect.renderOutline( scene, camera ); + * + * renderingOutline = false; + * + * }; + * + * function render() { + * + * renderer.render( scene, camera ); + * + * } + * + * // How to set default outline parameters + * new OutlineEffect( renderer, { + * defaultThickness: 0.01, + * defaultColor: [ 0, 0, 0 ], + * defaultAlpha: 0.8, + * defaultKeepAlive: true // keeps outline material in cache even if material is removed from scene + * } ); + * + * // How to set outline parameters for each material + * material.userData.outlineParameters = { + * thickness: 0.01, + * color: [ 0, 0, 0 ] + * alpha: 0.8, + * visible: true, + * keepAlive: true + * }; + */ + +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 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 + } ); + + } + + function getOutlineMaterialFromCache( originalMaterial ) { + + let data = cache[ originalMaterial.uuid ]; + + if ( data === undefined ) { + + data = { + material: createMaterial(), + used: true, + keepAlive: defaultKeepAlive, + count: 0 + }; + + cache[ originalMaterial.uuid ] = data; + + } + + data.used = true; + + return data.material; + + } + + function getOutlineMaterial( originalMaterial ) { + + const outlineMaterial = getOutlineMaterialFromCache( originalMaterial ); + + originalMaterials[ outlineMaterial.uuid ] = originalMaterial; + + updateOutlineMaterial( outlineMaterial, originalMaterial ); + + return outlineMaterial; + + } + + function isCompatible( object ) { + + const geometry = object.geometry; + let hasNormals = false; + + if ( object.geometry !== undefined ) { + + if ( geometry.isBufferGeometry ) { + + hasNormals = geometry.attributes.normal !== undefined; + + } else { + + hasNormals = true; // the renderer always produces a normal attribute for Geometry + + } + + } + + return ( object.isMesh === true && object.material !== undefined && hasNormals === true ); + + } + + function setOutlineMaterial( 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 ] ); + + } + + } else { + + object.material = getOutlineMaterial( object.material ); + + } + + originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender; + object.onBeforeRender = onBeforeRender; + + } + + 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 ] = originalMaterials[ object.material[ i ].uuid ]; + + } + + } else { + + object.material = originalMaterials[ object.material.uuid ]; + + } + + object.onBeforeRender = originalOnBeforeRenders[ object.uuid ]; + + } + + function onBeforeRender( renderer, scene, camera, geometry, material ) { + + const originalMaterial = originalMaterials[ material.uuid ]; + + // just in case + if ( originalMaterial === undefined ) return; + + updateUniforms( material, originalMaterial ); + + } + + 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 ( 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; + + const outlineParameters = originalMaterial.userData.outlineParameters; + + 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; + + } + + 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.clippingPlanes ) { + + material.clipping = true; + + material.clippingPlanes = originalMaterial.clippingPlanes; + material.clipIntersection = originalMaterial.clipIntersection; + material.clipShadows = originalMaterial.clipShadows; + + } + + material.version = originalMaterial.version; // update outline material if necessary + + } + + function cleanupCache() { + + let keys; + + // clear originialMaterials + keys = Object.keys( originalMaterials ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + originalMaterials[ keys[ i ] ] = undefined; + + } + + // clear originalOnBeforeRenders + keys = Object.keys( originalOnBeforeRenders ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + originalOnBeforeRenders[ keys[ i ] ] = undefined; + + } + + // remove unused outlineMaterial from cache + keys = Object.keys( cache ); + + 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 ].keepAlive === false && cache[ key ].count > removeThresholdCount ) { + + delete cache[ key ]; + + } + + } 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 ); + + if ( forceClear ) renderer.clear(); + + if ( this.enabled === false ) { + + renderer.render( scene, camera ); + return; + + } + + const currentAutoClear = renderer.autoClear; + renderer.autoClear = this.autoClear; + + renderer.render( scene, camera ); + + renderer.autoClear = currentAutoClear; + + this.renderOutline( scene, camera ); + + }; + + this.renderOutline = function ( scene, camera ) { + + const currentAutoClear = renderer.autoClear; + const currentSceneAutoUpdate = scene.autoUpdate; + const currentSceneBackground = scene.background; + const currentShadowMapEnabled = renderer.shadowMap.enabled; + + scene.autoUpdate = false; + scene.background = null; + renderer.autoClear = false; + renderer.shadowMap.enabled = false; + + scene.traverse( setOutlineMaterial ); + + renderer.render( scene, camera ); + + scene.traverse( restoreOriginalMaterial ); + + cleanupCache(); + + scene.autoUpdate = 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 = function ( color, depth, stencil ) { + + renderer.clear( color, depth, stencil ); + + }; + + this.getPixelRatio = function () { + + return renderer.getPixelRatio(); + + }; + + this.setPixelRatio = function ( value ) { + + renderer.setPixelRatio( value ); + + }; + + this.getSize = function ( target ) { + + return renderer.getSize( target ); + + }; + + this.setSize = function ( width, height, updateStyle ) { + + renderer.setSize( width, height, updateStyle ); + + }; + + this.setViewport = function ( x, y, width, height ) { + + renderer.setViewport( x, y, width, height ); + + }; + + this.setScissor = function ( x, y, width, height ) { + + renderer.setScissor( x, y, width, height ); + + }; + + this.setScissorTest = function ( boolean ) { + + renderer.setScissorTest( boolean ); + + }; + + this.setRenderTarget = function ( renderTarget ) { + + renderer.setRenderTarget( renderTarget ); + + }; + + } + +} + +export { OutlineEffect }; diff --git a/jsm/effects/ParallaxBarrierEffect.js b/jsm/effects/ParallaxBarrierEffect.js new file mode 100644 index 0000000..5006793 --- /dev/null +++ b/jsm/effects/ParallaxBarrierEffect.js @@ -0,0 +1,116 @@ +import { + LinearFilter, + Mesh, + NearestFilter, + OrthographicCamera, + PlaneGeometry, + RGBAFormat, + Scene, + ShaderMaterial, + StereoCamera, + WebGLRenderTarget +} from 'three'; + +class ParallaxBarrierEffect { + + constructor( renderer ) { + + 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 }; + + 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 } + + }, + + vertexShader: [ + + 'varying vec2 vUv;', + + 'void main() {', + + ' vUv = vec2( uv.x, uv.y );', + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}' + + ].join( '\n' ), + + fragmentShader: [ + + 'uniform sampler2D mapLeft;', + 'uniform sampler2D mapRight;', + 'varying vec2 vUv;', + + 'void main() {', + + ' vec2 uv = vUv;', + + ' if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {', + + ' gl_FragColor = texture2D( mapLeft, uv );', + + ' } else {', + + ' gl_FragColor = texture2D( mapRight, uv );', + + ' }', + + '}' + + ].join( '\n' ) + + } ); + + const mesh = new Mesh( new PlaneGeometry( 2, 2 ), _material ); + _scene.add( mesh ); + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + const pixelRatio = renderer.getPixelRatio(); + + _renderTargetL.setSize( width * pixelRatio, height * pixelRatio ); + _renderTargetR.setSize( width * pixelRatio, height * pixelRatio ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.setRenderTarget( _renderTargetL ); + renderer.clear(); + renderer.render( scene, _stereo.cameraL ); + + renderer.setRenderTarget( _renderTargetR ); + renderer.clear(); + renderer.render( scene, _stereo.cameraR ); + + renderer.setRenderTarget( null ); + renderer.render( _scene, _camera ); + + }; + + } + +} + +export { ParallaxBarrierEffect }; diff --git a/jsm/effects/PeppersGhostEffect.js b/jsm/effects/PeppersGhostEffect.js new file mode 100644 index 0000000..5af1599 --- /dev/null +++ b/jsm/effects/PeppersGhostEffect.js @@ -0,0 +1,153 @@ +import { + PerspectiveCamera, + Quaternion, + Vector3 +} from 'three'; + +/** + * peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS + */ + +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 ); + + }; + + this.render = function ( 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 ); + + } + + renderer.setScissorTest( false ); + + }; + + } + +} + +export { PeppersGhostEffect }; diff --git a/jsm/effects/StereoEffect.js b/jsm/effects/StereoEffect.js new file mode 100644 index 0000000..e4c6d92 --- /dev/null +++ b/jsm/effects/StereoEffect.js @@ -0,0 +1,55 @@ +import { + StereoCamera, + Vector2 +} from 'three'; + +class StereoEffect { + + constructor( renderer ) { + + const _stereo = new StereoCamera(); + _stereo.aspect = 0.5; + const size = new Vector2(); + + this.setEyeSeparation = function ( eyeSep ) { + + _stereo.eyeSep = eyeSep; + + }; + + this.setSize = function ( width, height ) { + + renderer.setSize( width, height ); + + }; + + this.render = function ( scene, camera ) { + + scene.updateMatrixWorld(); + + if ( camera.parent === null ) camera.updateMatrixWorld(); + + _stereo.update( camera ); + + renderer.getSize( size ); + + 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( 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 ); + + }; + + } + +} + +export { StereoEffect }; diff --git a/jsm/environments/DebugEnvironment.js b/jsm/environments/DebugEnvironment.js new file mode 100644 index 0000000..ce3db06 --- /dev/null +++ b/jsm/environments/DebugEnvironment.js @@ -0,0 +1,52 @@ +import { + BackSide, + BoxGeometry, + Mesh, + MeshLambertMaterial, + MeshStandardMaterial, + PointLight, + Scene, +} from 'three'; + +class DebugEnvironment extends Scene { + + constructor() { + + super(); + + const geometry = new BoxGeometry(); + geometry.deleteAttribute( 'uv' ); + const roomMaterial = new MeshStandardMaterial( { metalness: 0, side: BackSide } ); + const room = new Mesh( geometry, roomMaterial ); + room.scale.setScalar( 10 ); + this.add( room ); + + const mainLight = new PointLight( 0xffffff, 50, 0, 2 ); + this.add( mainLight ); + + const material1 = new MeshLambertMaterial( { color: 0xff0000, emissive: 0xffffff, emissiveIntensity: 10 } ); + + const light1 = new Mesh( geometry, material1 ); + light1.position.set( - 5, 2, 0 ); + light1.scale.set( 0.1, 1, 1 ); + this.add( light1 ); + + const material2 = new MeshLambertMaterial( { color: 0x00ff00, emissive: 0xffffff, emissiveIntensity: 10 } ); + + const light2 = new Mesh( geometry, material2 ); + light2.position.set( 0, 5, 0 ); + light2.scale.set( 1, 0.1, 1 ); + this.add( light2 ); + + const material3 = new MeshLambertMaterial( { color: 0x0000ff, emissive: 0xffffff, emissiveIntensity: 10 } ); + + const light3 = new Mesh( geometry, material3 ); + light3.position.set( 2, 1, 5 ); + light3.scale.set( 1.5, 2, 0.1 ); + this.add( light3 ); + + } + +} + +export { DebugEnvironment }; diff --git a/jsm/environments/RoomEnvironment.js b/jsm/environments/RoomEnvironment.js new file mode 100644 index 0000000..eb222c1 --- /dev/null +++ b/jsm/environments/RoomEnvironment.js @@ -0,0 +1,121 @@ +/** + * https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts + */ + +import { + BackSide, + BoxGeometry, + Mesh, + MeshBasicMaterial, + MeshStandardMaterial, + PointLight, + Scene, +} from 'three'; + +class RoomEnvironment extends Scene { + + constructor() { + + super(); + + const geometry = new BoxGeometry(); + geometry.deleteAttribute( 'uv' ); + + const roomMaterial = new MeshStandardMaterial( { side: BackSide } ); + const boxMaterial = new MeshStandardMaterial(); + + const mainLight = new PointLight( 0xffffff, 5.0, 28, 2 ); + mainLight.position.set( 0.418, 16.199, 0.300 ); + this.add( mainLight ); + + const room = new Mesh( geometry, roomMaterial ); + room.position.set( - 0.757, 13.219, 0.717 ); + room.scale.set( 31.713, 28.305, 28.591 ); + this.add( room ); + + const box1 = new Mesh( geometry, boxMaterial ); + box1.position.set( - 10.906, 2.009, 1.846 ); + box1.rotation.set( 0, - 0.195, 0 ); + box1.scale.set( 2.328, 7.905, 4.651 ); + this.add( box1 ); + + const box2 = new Mesh( geometry, boxMaterial ); + box2.position.set( - 5.607, - 0.754, - 0.758 ); + box2.rotation.set( 0, 0.994, 0 ); + box2.scale.set( 1.970, 1.534, 3.955 ); + this.add( box2 ); + + const box3 = new Mesh( geometry, boxMaterial ); + box3.position.set( 6.167, 0.857, 7.803 ); + box3.rotation.set( 0, 0.561, 0 ); + box3.scale.set( 3.927, 6.285, 3.687 ); + this.add( box3 ); + + const box4 = new Mesh( geometry, boxMaterial ); + box4.position.set( - 2.017, 0.018, 6.124 ); + box4.rotation.set( 0, 0.333, 0 ); + box4.scale.set( 2.002, 4.566, 2.064 ); + this.add( box4 ); + + const box5 = new Mesh( geometry, boxMaterial ); + box5.position.set( 2.291, - 0.756, - 2.621 ); + box5.rotation.set( 0, - 0.286, 0 ); + box5.scale.set( 1.546, 1.552, 1.496 ); + this.add( box5 ); + + const box6 = new Mesh( geometry, boxMaterial ); + box6.position.set( - 2.193, - 0.369, - 5.547 ); + box6.rotation.set( 0, 0.516, 0 ); + box6.scale.set( 3.875, 3.487, 2.986 ); + this.add( box6 ); + + + // -x right + const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) ); + light1.position.set( - 16.116, 14.37, 8.208 ); + light1.scale.set( 0.1, 2.428, 2.739 ); + this.add( light1 ); + + // -x left + const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) ); + light2.position.set( - 16.109, 18.021, - 8.207 ); + light2.scale.set( 0.1, 2.425, 2.751 ); + this.add( light2 ); + + // +x + const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) ); + light3.position.set( 14.904, 12.198, - 1.832 ); + light3.scale.set( 0.15, 4.265, 6.331 ); + this.add( light3 ); + + // +z + const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) ); + light4.position.set( - 0.462, 8.89, 14.520 ); + light4.scale.set( 4.38, 5.441, 0.088 ); + this.add( light4 ); + + // -z + const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) ); + light5.position.set( 3.235, 11.486, - 12.541 ); + light5.scale.set( 2.5, 2.0, 0.1 ); + this.add( light5 ); + + // +y + const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) ); + light6.position.set( 0.0, 20.0, 0.0 ); + light6.scale.set( 1.0, 0.1, 1.0 ); + this.add( light6 ); + + } + +} + +function createAreaLightMaterial( intensity ) { + + const material = new MeshBasicMaterial(); + material.color.setScalar( intensity ); + return material; + +} + +export { RoomEnvironment }; diff --git a/jsm/exporters/ColladaExporter.js b/jsm/exporters/ColladaExporter.js new file mode 100644 index 0000000..c9cbcbf --- /dev/null +++ b/jsm/exporters/ColladaExporter.js @@ -0,0 +1,722 @@ +import { + Color, + DoubleSide, + Matrix4, + MeshBasicMaterial +} from 'three'; + +/** + * https://github.com/gkjohnson/collada-exporter-js + * + * Usage: + * const exporter = new ColladaExporter(); + * + * const data = exporter.parse(mesh); + * + * Format Definition: + * https://www.khronos.org/collada/ + */ + +class ColladaExporter { + + parse( object, onDone, options = {} ) { + + options = Object.assign( { + version: '1.4.1', + author: null, + textureDirectory: '', + upAxis: 'Y_UP', + unitName: null, + unitMeter: null, + }, options ); + + if ( options.upAxis.match( /^[XYZ]_UP$/ ) === null ) { + + console.error( 'ColladaExporter: Invalid upAxis: valid values are X_UP, Y_UP or Z_UP.' ); + return null; + + } + + if ( options.unitName !== null && options.unitMeter === null ) { + + console.error( 'ColladaExporter: unitMeter needs to be specified if unitName is specified.' ); + return null; + + } + + if ( options.unitMeter !== null && options.unitName === null ) { + + console.error( 'ColladaExporter: unitName needs to be specified if unitMeter is specified.' ); + return null; + + } + + if ( options.textureDirectory !== '' ) { + + options.textureDirectory = `${ options.textureDirectory }/` + .replace( /\\/g, '/' ) + .replace( /\/+/g, '/' ); + + } + + const version = options.version; + + if ( version !== '1.4.1' && version !== '1.5.0' ) { + + console.warn( `ColladaExporter : Version ${ version } not supported for export. Only 1.4.1 and 1.5.0.` ); + return null; + + } + + // Convert the urdf xml into a well-formatted, indented format + function format( urdf ) { + + const IS_END_TAG = /^<\//; + const IS_SELF_CLOSING = /(\?>$)|(\/>$)/; + const HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/; + + const pad = ( ch, num ) => ( num > 0 ? ch + pad( ch, num - 1 ) : '' ); + + let tagnum = 0; + + return urdf + .match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g ) + .map( tag => { + + if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && IS_END_TAG.test( tag ) ) { + + tagnum --; + + } + + const res = `${ pad( ' ', tagnum ) }${ tag }`; + + if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) { + + tagnum ++; + + } + + return res; + + } ) + .join( '\n' ); + + } + + // Convert an image into a png format for saving + function base64ToBuffer( str ) { + + const b = atob( str ); + const buf = new Uint8Array( b.length ); + + for ( let i = 0, l = buf.length; i < l; i ++ ) { + + buf[ i ] = b.charCodeAt( i ); + + } + + return buf; + + } + + let canvas, ctx; + + function imageToData( image, ext ) { + + canvas = canvas || document.createElement( 'canvas' ); + ctx = ctx || canvas.getContext( '2d' ); + + canvas.width = image.width; + canvas.height = image.height; + + ctx.drawImage( image, 0, 0 ); + + // Get the base64 encoded data + const base64data = canvas + .toDataURL( `image/${ ext }`, 1 ) + .replace( /^data:image\/(png|jpg);base64,/, '' ); + + // Convert to a uint8 array + return base64ToBuffer( base64data ); + + } + + // gets the attribute array. Generate a new array if the attribute is interleaved + const getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ]; + const tempColor = new Color(); + + function attrBufferToArray( attr, isColor = false ) { + + if ( isColor ) { + + // convert the colors to srgb before export + // colors are always written as floats + const arr = new Float32Array( attr.count * 3 ); + for ( let i = 0, l = attr.count; i < l; i ++ ) { + + tempColor + .fromBufferAttribute( attr, i ) + .convertLinearToSRGB(); + + arr[ 3 * i + 0 ] = tempColor.r; + arr[ 3 * i + 1 ] = tempColor.g; + arr[ 3 * i + 2 ] = tempColor.b; + + } + + return arr; + + } else if ( attr.isInterleavedBufferAttribute ) { + + // use the typed array constructor to save on memory + const arr = new attr.array.constructor( attr.count * attr.itemSize ); + const size = attr.itemSize; + + for ( let i = 0, l = attr.count; i < l; i ++ ) { + + for ( let j = 0; j < size; j ++ ) { + + arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i ); + + } + + } + + return arr; + + } else { + + return attr.array; + + } + + } + + // Returns an array of the same type starting at the `st` index, + // and `ct` length + function subArray( arr, st, ct ) { + + if ( Array.isArray( arr ) ) return arr.slice( st, st + ct ); + else return new arr.constructor( arr.buffer, st * arr.BYTES_PER_ELEMENT, ct ); + + } + + // Returns the string for a geometry's attribute + function getAttribute( attr, name, params, type, isColor = false ) { + + const array = attrBufferToArray( attr, isColor ); + const res = + `` + + + `` + + array.join( ' ' ) + + '' + + + '' + + `` + + + params.map( n => `` ).join( '' ) + + + '' + + '' + + ''; + + return res; + + } + + // Returns the string for a node's transform information + let transMat; + function getTransform( o ) { + + // ensure the object's matrix is up to date + // before saving the transform + o.updateMatrix(); + + transMat = transMat || new Matrix4(); + transMat.copy( o.matrix ); + transMat.transpose(); + return `${ transMat.toArray().join( ' ' ) }`; + + } + + // Process the given piece of geometry into the geometry library + // Returns the mesh id + function processGeometry( g ) { + + let info = geometryInfo.get( g ); + + if ( ! info ) { + + // convert the geometry to bufferGeometry if it isn't already + const bufferGeometry = g; + + if ( bufferGeometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.ColladaExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const meshid = `Mesh${ libraryGeometries.length + 1 }`; + + const indexCount = + bufferGeometry.index ? + bufferGeometry.index.count * bufferGeometry.index.itemSize : + bufferGeometry.attributes.position.count; + + const groups = + bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? + bufferGeometry.groups : + [ { start: 0, count: indexCount, materialIndex: 0 } ]; + + + const gname = g.name ? ` name="${ g.name }"` : ''; + let gnode = ``; + + // define the geometry node and the vertices for the geometry + const posName = `${ meshid }-position`; + const vertName = `${ meshid }-vertices`; + gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' ); + gnode += ``; + + // NOTE: We're not optimizing the attribute arrays here, so they're all the same length and + // can therefore share the same triangle indices. However, MeshLab seems to have trouble opening + // models with attributes that share an offset. + // MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/ + + // serialize normals + let triangleInputs = ``; + if ( 'normal' in bufferGeometry.attributes ) { + + const normName = `${ meshid }-normal`; + gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' ); + triangleInputs += ``; + + } + + // serialize uvs + if ( 'uv' in bufferGeometry.attributes ) { + + const uvName = `${ meshid }-texcoord`; + gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' ); + triangleInputs += ``; + + } + + // serialize lightmap uvs + if ( 'uv2' in bufferGeometry.attributes ) { + + const uvName = `${ meshid }-texcoord2`; + gnode += getAttribute( bufferGeometry.attributes.uv2, uvName, [ 'S', 'T' ], 'float' ); + triangleInputs += ``; + + } + + // serialize colors + if ( 'color' in bufferGeometry.attributes ) { + + // colors are always written as floats + const colName = `${ meshid }-color`; + gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'R', 'G', 'B' ], 'float', true ); + triangleInputs += ``; + + } + + let indexArray = null; + if ( bufferGeometry.index ) { + + indexArray = attrBufferToArray( bufferGeometry.index ); + + } else { + + indexArray = new Array( indexCount ); + for ( let i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i; + + } + + for ( let i = 0, l = groups.length; i < l; i ++ ) { + + const group = groups[ i ]; + const subarr = subArray( indexArray, group.start, group.count ); + const polycount = subarr.length / 3; + gnode += ``; + gnode += triangleInputs; + + gnode += `

${ subarr.join( ' ' ) }

`; + gnode += '
'; + + } + + gnode += '
'; + + libraryGeometries.push( gnode ); + + info = { meshid: meshid, bufferGeometry: bufferGeometry }; + geometryInfo.set( g, info ); + + } + + return info; + + } + + // Process the given texture into the image library + // Returns the image library + function processTexture( tex ) { + + let texid = imageMap.get( tex ); + if ( texid == null ) { + + texid = `image-${ libraryImages.length + 1 }`; + + const ext = 'png'; + const name = tex.name || texid; + let imageNode = ``; + + if ( version === '1.5.0' ) { + + imageNode += `${ options.textureDirectory }${ name }.${ ext }`; + + } else { + + // version image node 1.4.1 + imageNode += `${ options.textureDirectory }${ name }.${ ext }`; + + } + + imageNode += ''; + + libraryImages.push( imageNode ); + imageMap.set( tex, texid ); + textures.push( { + directory: options.textureDirectory, + name, + ext, + data: imageToData( tex.image, ext ), + original: tex + } ); + + } + + return texid; + + } + + // Process the given material into the material and effect libraries + // Returns the material id + function processMaterial( m ) { + + let matid = materialMap.get( m ); + + if ( matid == null ) { + + matid = `Mat${ libraryEffects.length + 1 }`; + + let type = 'phong'; + + if ( m.isMeshLambertMaterial === true ) { + + type = 'lambert'; + + } else if ( m.isMeshBasicMaterial === true ) { + + type = 'constant'; + + if ( m.map !== null ) { + + // The Collada spec does not support diffuse texture maps with the + // constant shader type. + // mrdoob/three.js#15469 + console.warn( 'ColladaExporter: Texture maps not supported with MeshBasicMaterial.' ); + + } + + } + + const emissive = m.emissive ? m.emissive : new Color( 0, 0, 0 ); + const diffuse = m.color ? m.color : new Color( 0, 0, 0 ); + const specular = m.specular ? m.specular : new Color( 1, 1, 1 ); + const shininess = m.shininess || 0; + const reflectivity = m.reflectivity || 0; + + emissive.convertLinearToSRGB(); + specular.convertLinearToSRGB(); + diffuse.convertLinearToSRGB(); + + // Do not export and alpha map for the reasons mentioned in issue (#13792) + // in three.js alpha maps are black and white, but collada expects the alpha + // channel to specify the transparency + let transparencyNode = ''; + if ( m.transparent === true ) { + + transparencyNode += + '' + + ( + m.map ? + '' : + '1' + ) + + ''; + + if ( m.opacity < 1 ) { + + transparencyNode += `${ m.opacity }`; + + } + + } + + const techniqueNode = `<${ type }>` + + + '' + + + ( + m.emissiveMap ? + '' : + `${ emissive.r } ${ emissive.g } ${ emissive.b } 1` + ) + + + '' + + + ( + type !== 'constant' ? + '' + + + ( + m.map ? + '' : + `${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1` + ) + + '' + : '' + ) + + + ( + type !== 'constant' ? + '' + + + ( + m.normalMap ? '' : '' + ) + + '' + : '' + ) + + + ( + type === 'phong' ? + `${ specular.r } ${ specular.g } ${ specular.b } 1` + + + '' + + + ( + m.specularMap ? + '' : + `${ shininess }` + ) + + + '' + : '' + ) + + + `${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1` + + + `${ reflectivity }` + + + transparencyNode + + + ``; + + const effectnode = + `` + + '' + + + ( + m.map ? + '' + + `${ processTexture( m.map ) }` + + '' + + 'diffuse-surface' : + '' + ) + + + ( + m.specularMap ? + '' + + `${ processTexture( m.specularMap ) }` + + '' + + 'specular-surface' : + '' + ) + + + ( + m.emissiveMap ? + '' + + `${ processTexture( m.emissiveMap ) }` + + '' + + 'emissive-surface' : + '' + ) + + + ( + m.normalMap ? + '' + + `${ processTexture( m.normalMap ) }` + + '' + + 'bump-surface' : + '' + ) + + + techniqueNode + + + ( + m.side === DoubleSide ? + '1' : + '' + ) + + + '' + + + ''; + + const materialName = m.name ? ` name="${ m.name }"` : ''; + const materialNode = ``; + + libraryMaterials.push( materialNode ); + libraryEffects.push( effectnode ); + materialMap.set( m, matid ); + + } + + return matid; + + } + + // Recursively process the object into a scene + function processObject( o ) { + + let node = ``; + + node += getTransform( o ); + + if ( o.isMesh === true && o.geometry !== null ) { + + // function returns the id associated with the mesh and a "BufferGeometry" version + // of the geometry in case it's not a geometry. + const geomInfo = processGeometry( o.geometry ); + const meshid = geomInfo.meshid; + const geometry = geomInfo.bufferGeometry; + + // ids of the materials to bind to the geometry + let matids = null; + let matidsArray; + + // get a list of materials to bind to the sub groups of the geometry. + // If the amount of subgroups is greater than the materials, than reuse + // the materials. + const mat = o.material || new MeshBasicMaterial(); + const materials = Array.isArray( mat ) ? mat : [ mat ]; + + if ( geometry.groups.length > materials.length ) { + + matidsArray = new Array( geometry.groups.length ); + + } else { + + matidsArray = new Array( materials.length ); + + } + + matids = matidsArray.fill().map( ( v, i ) => processMaterial( materials[ i % materials.length ] ) ); + + node += + `` + + + ( + matids.length > 0 ? + '' + + matids.map( ( id, i ) => + + `` + + + '' + + + '' + ).join( '' ) + + '' : + '' + ) + + + ''; + + } + + o.children.forEach( c => node += processObject( c ) ); + + node += ''; + + return node; + + } + + const geometryInfo = new WeakMap(); + const materialMap = new WeakMap(); + const imageMap = new WeakMap(); + const textures = []; + + const libraryImages = []; + const libraryGeometries = []; + const libraryEffects = []; + const libraryMaterials = []; + const libraryVisualScenes = processObject( object ); + + const specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/'; + let dae = + '' + + `` + + '' + + ( + '' + + 'three.js Collada Exporter' + + ( options.author !== null ? `${ options.author }` : '' ) + + '' + + `${ ( new Date() ).toISOString() }` + + `${ ( new Date() ).toISOString() }` + + ( options.unitName !== null ? `` : '' ) + + `${ options.upAxis }` + ) + + ''; + + dae += `${ libraryImages.join( '' ) }`; + + dae += `${ libraryEffects.join( '' ) }`; + + dae += `${ libraryMaterials.join( '' ) }`; + + dae += `${ libraryGeometries.join( '' ) }`; + + dae += `${ libraryVisualScenes }`; + + dae += ''; + + dae += ''; + + const res = { + data: format( dae ), + textures + }; + + if ( typeof onDone === 'function' ) { + + requestAnimationFrame( () => onDone( res ) ); + + } + + return res; + + } + +} + + +export { ColladaExporter }; diff --git a/jsm/exporters/DRACOExporter.js b/jsm/exporters/DRACOExporter.js new file mode 100644 index 0000000..6800d86 --- /dev/null +++ b/jsm/exporters/DRACOExporter.js @@ -0,0 +1,238 @@ +/** + * Export draco compressed files from threejs geometry objects. + * + * Draco files are compressed and usually are smaller than conventional 3D file formats. + * + * The exporter receives a options object containing + * - decodeSpeed, indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality) + * - encodeSpeed, indicates how to tune the encoder parameters (0 gives better speed but worst quality) + * - encoderMethod + * - quantization, indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC) + * - exportUvs + * - exportNormals + */ + +/* global DracoEncoderModule */ + +class DRACOExporter { + + parse( object, options = { + decodeSpeed: 5, + encodeSpeed: 5, + encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING, + quantization: [ 16, 8, 8, 8, 8 ], + exportUvs: true, + exportNormals: true, + exportColor: false, + } ) { + + if ( object.isBufferGeometry === true ) { + + throw new Error( 'DRACOExporter: The first parameter of parse() is now an instance of Mesh or Points.' ); + + } + + if ( DracoEncoderModule === undefined ) { + + throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' ); + + } + + const geometry = object.geometry; + + const dracoEncoder = DracoEncoderModule(); + const encoder = new dracoEncoder.Encoder(); + let builder; + let dracoObject; + + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.DRACOExporter.parse(geometry, options): geometry is not a THREE.BufferGeometry instance.' ); + + } + + if ( object.isMesh === true ) { + + builder = new dracoEncoder.MeshBuilder(); + dracoObject = new dracoEncoder.Mesh(); + + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + + const faces = geometry.getIndex(); + + if ( faces !== null ) { + + builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array ); + + } else { + + const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count ); + + for ( let i = 0; i < faces.length; i ++ ) { + + faces[ i ] = i; + + } + + builder.AddFacesToMesh( dracoObject, vertices.count, faces ); + + } + + if ( options.exportNormals === true ) { + + const normals = geometry.getAttribute( 'normal' ); + + if ( normals !== undefined ) { + + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array ); + + } + + } + + if ( options.exportUvs === true ) { + + const uvs = geometry.getAttribute( 'uv' ); + + if ( uvs !== undefined ) { + + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array ); + + } + + } + + if ( options.exportColor === true ) { + + const colors = geometry.getAttribute( 'color' ); + + if ( colors !== undefined ) { + + builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array ); + + } + + } + + } else if ( object.isPoints === true ) { + + builder = new dracoEncoder.PointCloudBuilder(); + dracoObject = new dracoEncoder.PointCloud(); + + const vertices = geometry.getAttribute( 'position' ); + builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array ); + + if ( options.exportColor === true ) { + + const colors = geometry.getAttribute( 'color' ); + + if ( colors !== undefined ) { + + builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array ); + + } + + } + + } else { + + throw new Error( 'DRACOExporter: Unsupported object type.' ); + + } + + //Compress using draco encoder + + const encodedData = new dracoEncoder.DracoInt8Array(); + + //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression). + + const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5; + const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5; + + encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); + + // Sets the desired encoding method for a given geometry. + + if ( options.encoderMethod !== undefined ) { + + encoder.SetEncodingMethod( options.encoderMethod ); + + } + + // Sets the quantization (number of bits used to represent) compression options for a named attribute. + // The attribute values will be quantized in a box defined by the maximum extent of the attribute values. + if ( options.quantization !== undefined ) { + + for ( let i = 0; i < 5; i ++ ) { + + if ( options.quantization[ i ] !== undefined ) { + + encoder.SetAttributeQuantization( i, options.quantization[ i ] ); + + } + + } + + } + + let length; + + if ( object.isMesh === true ) { + + length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData ); + + } else { + + length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData ); + + } + + dracoEncoder.destroy( dracoObject ); + + if ( length === 0 ) { + + throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' ); + + } + + //Copy encoded data to buffer. + const outputData = new Int8Array( new ArrayBuffer( length ) ); + + for ( let i = 0; i < length; i ++ ) { + + outputData[ i ] = encodedData.GetValue( i ); + + } + + dracoEncoder.destroy( encodedData ); + dracoEncoder.destroy( encoder ); + dracoEncoder.destroy( builder ); + + return outputData; + + } + +} + +// Encoder methods + +DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1; +DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; + +// Geometry type + +DRACOExporter.POINT_CLOUD = 0; +DRACOExporter.TRIANGULAR_MESH = 1; + +// Attribute type + +DRACOExporter.INVALID = - 1; +DRACOExporter.POSITION = 0; +DRACOExporter.NORMAL = 1; +DRACOExporter.COLOR = 2; +DRACOExporter.TEX_COORD = 3; +DRACOExporter.GENERIC = 4; + +export { DRACOExporter }; diff --git a/jsm/exporters/EXRExporter.js b/jsm/exporters/EXRExporter.js new file mode 100644 index 0000000..8e752b3 --- /dev/null +++ b/jsm/exporters/EXRExporter.js @@ -0,0 +1,507 @@ +/** + * @author sciecode / https://github.com/sciecode + * + * EXR format references: + * https://www.openexr.com/documentation/openexrfilelayout.pdf + */ + +import { + FloatType, + HalfFloatType, + RGBAFormat, + DataUtils, +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +const textEncoder = new TextEncoder(); + +const NO_COMPRESSION = 0; +const ZIPS_COMPRESSION = 2; +const ZIP_COMPRESSION = 3; + +class EXRExporter { + + parse( renderer, renderTarget, options ) { + + if ( ! supported( renderer, renderTarget ) ) return undefined; + + const info = buildInfo( renderTarget, options ), + dataBuffer = getPixelData( renderer, renderTarget, info ), + rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ), + chunks = compressData( rawContentBuffer, info ); + + return fillData( chunks, info ); + + } + +} + +function supported( renderer, renderTarget ) { + + if ( ! renderer || ! renderer.isWebGLRenderer ) { + + console.error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer.' ); + + return false; + + } + + if ( ! renderTarget || ! renderTarget.isWebGLRenderTarget ) { + + console.error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' ); + + return false; + + } + + if ( renderTarget.texture.type !== FloatType && renderTarget.texture.type !== HalfFloatType ) { + + console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' ); + + return false; + + } + + if ( renderTarget.texture.format !== RGBAFormat ) { + + console.error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' ); + + return false; + + } + + + return true; + +} + +function buildInfo( renderTarget, options = {} ) { + + const compressionSizes = { + 0: 1, + 2: 1, + 3: 16 + }; + + const WIDTH = renderTarget.width, + HEIGHT = renderTarget.height, + TYPE = renderTarget.texture.type, + FORMAT = renderTarget.texture.format, + ENCODING = renderTarget.texture.encoding, + COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION, + EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType, + OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1, + COMPRESSION_SIZE = compressionSizes[ COMPRESSION ], + NUM_CHANNELS = 4; + + return { + width: WIDTH, + height: HEIGHT, + type: TYPE, + format: FORMAT, + encoding: ENCODING, + compression: COMPRESSION, + blockLines: COMPRESSION_SIZE, + dataType: OUT_TYPE, + dataSize: 2 * OUT_TYPE, + numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ), + numInputChannels: 4, + numOutputChannels: NUM_CHANNELS, + }; + +} + +function getPixelData( renderer, rtt, info ) { + + let dataBuffer; + + if ( info.type === FloatType ) { + + dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels ); + + } else { + + dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels ); + + } + + renderer.readRenderTargetPixels( rtt, 0, 0, info.width, info.height, dataBuffer ); + + return dataBuffer; + +} + +function reorganizeDataBuffer( inBuffer, info ) { + + const w = info.width, + h = info.height, + dec = { r: 0, g: 0, b: 0, a: 0 }, + offset = { value: 0 }, + cOffset = ( info.numOutputChannels == 4 ) ? 1 : 0, + getValue = ( info.type == FloatType ) ? getFloat32 : getFloat16, + setValue = ( info.dataType == 1 ) ? setFloat16 : setFloat32, + outBuffer = new Uint8Array( info.width * info.height * info.numOutputChannels * info.dataSize ), + dv = new DataView( outBuffer.buffer ); + + for ( let y = 0; y < h; ++ y ) { + + for ( let x = 0; x < w; ++ x ) { + + const i = y * w * 4 + x * 4; + + const r = getValue( inBuffer, i ); + const g = getValue( inBuffer, i + 1 ); + const b = getValue( inBuffer, i + 2 ); + const a = getValue( inBuffer, i + 3 ); + + const line = ( h - y - 1 ) * w * ( 3 + cOffset ) * info.dataSize; + + decodeLinear( dec, r, g, b, a ); + + offset.value = line + x * info.dataSize; + setValue( dv, dec.a, offset ); + + offset.value = line + ( cOffset ) * w * info.dataSize + x * info.dataSize; + setValue( dv, dec.b, offset ); + + offset.value = line + ( 1 + cOffset ) * w * info.dataSize + x * info.dataSize; + setValue( dv, dec.g, offset ); + + offset.value = line + ( 2 + cOffset ) * w * info.dataSize + x * info.dataSize; + setValue( dv, dec.r, offset ); + + } + + } + + return outBuffer; + +} + +function compressData( inBuffer, info ) { + + let compress, + tmpBuffer, + sum = 0; + + const chunks = { data: new Array(), totalSize: 0 }, + size = info.width * info.numOutputChannels * info.blockLines * info.dataSize; + + switch ( info.compression ) { + + case 0: + compress = compressNONE; + break; + + case 2: + case 3: + compress = compressZIP; + break; + + } + + if ( info.compression !== 0 ) { + + tmpBuffer = new Uint8Array( size ); + + } + + for ( let i = 0; i < info.numBlocks; ++ i ) { + + const arr = inBuffer.subarray( size * i, size * ( i + 1 ) ); + + const block = compress( arr, tmpBuffer ); + + sum += block.length; + + chunks.data.push( { dataChunk: block, size: block.length } ); + + } + + chunks.totalSize = sum; + + return chunks; + +} + +function compressNONE( data ) { + + return data; + +} + +function compressZIP( data, tmpBuffer ) { + + // + // Reorder the pixel data. + // + + let t1 = 0, + t2 = Math.floor( ( data.length + 1 ) / 2 ), + s = 0; + + const stop = data.length - 1; + + while ( true ) { + + if ( s > stop ) break; + tmpBuffer[ t1 ++ ] = data[ s ++ ]; + + if ( s > stop ) break; + tmpBuffer[ t2 ++ ] = data[ s ++ ]; + + } + + // + // Predictor. + // + + let p = tmpBuffer[ 0 ]; + + for ( let t = 1; t < tmpBuffer.length; t ++ ) { + + const d = tmpBuffer[ t ] - p + ( 128 + 256 ); + p = tmpBuffer[ t ]; + tmpBuffer[ t ] = d; + + } + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.EXRLoader: External \`fflate.module.js\` required' ); + + } + + const deflate = fflate.zlibSync( tmpBuffer ); // eslint-disable-line no-undef + + return deflate; + +} + +function fillHeader( outBuffer, chunks, info ) { + + const offset = { value: 0 }; + const dv = new DataView( outBuffer.buffer ); + + setUint32( dv, 20000630, offset ); // magic + setUint32( dv, 2, offset ); // mask + + // = HEADER = + + setString( dv, 'compression', offset ); + setString( dv, 'compression', offset ); + setUint32( dv, 1, offset ); + setUint8( dv, info.compression, offset ); + + setString( dv, 'screenWindowCenter', offset ); + setString( dv, 'v2f', offset ); + setUint32( dv, 8, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, 0, offset ); + + setString( dv, 'screenWindowWidth', offset ); + setString( dv, 'float', offset ); + setUint32( dv, 4, offset ); + setFloat32( dv, 1.0, offset ); + + setString( dv, 'pixelAspectRatio', offset ); + setString( dv, 'float', offset ); + setUint32( dv, 4, offset ); + setFloat32( dv, 1.0, offset ); + + setString( dv, 'lineOrder', offset ); + setString( dv, 'lineOrder', offset ); + setUint32( dv, 1, offset ); + setUint8( dv, 0, offset ); + + setString( dv, 'dataWindow', offset ); + setString( dv, 'box2i', offset ); + setUint32( dv, 16, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, info.width - 1, offset ); + setUint32( dv, info.height - 1, offset ); + + setString( dv, 'displayWindow', offset ); + setString( dv, 'box2i', offset ); + setUint32( dv, 16, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, 0, offset ); + setUint32( dv, info.width - 1, offset ); + setUint32( dv, info.height - 1, offset ); + + setString( dv, 'channels', offset ); + setString( dv, 'chlist', offset ); + setUint32( dv, info.numOutputChannels * 18 + 1, offset ); + + setString( dv, 'A', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setString( dv, 'B', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setString( dv, 'G', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setString( dv, 'R', offset ); + setUint32( dv, info.dataType, offset ); + offset.value += 4; + setUint32( dv, 1, offset ); + setUint32( dv, 1, offset ); + + setUint8( dv, 0, offset ); + + // null-byte + setUint8( dv, 0, offset ); + + // = OFFSET TABLE = + + let sum = offset.value + info.numBlocks * 8; + + for ( let i = 0; i < chunks.data.length; ++ i ) { + + setUint64( dv, sum, offset ); + + sum += chunks.data[ i ].size + 8; + + } + +} + +function fillData( chunks, info ) { + + const TableSize = info.numBlocks * 8, + HeaderSize = 259 + ( 18 * info.numOutputChannels ), // 259 + 18 * chlist + offset = { value: HeaderSize + TableSize }, + outBuffer = new Uint8Array( HeaderSize + TableSize + chunks.totalSize + info.numBlocks * 8 ), + dv = new DataView( outBuffer.buffer ); + + fillHeader( outBuffer, chunks, info ); + + for ( let i = 0; i < chunks.data.length; ++ i ) { + + const data = chunks.data[ i ].dataChunk; + const size = chunks.data[ i ].size; + + setUint32( dv, i * info.blockLines, offset ); + setUint32( dv, size, offset ); + + outBuffer.set( data, offset.value ); + offset.value += size; + + } + + return outBuffer; + +} + +function decodeLinear( dec, r, g, b, a ) { + + dec.r = r; + dec.g = g; + dec.b = b; + dec.a = a; + +} + +// function decodeSRGB( dec, r, g, b, a ) { + +// dec.r = r > 0.04045 ? Math.pow( r * 0.9478672986 + 0.0521327014, 2.4 ) : r * 0.0773993808; +// dec.g = g > 0.04045 ? Math.pow( g * 0.9478672986 + 0.0521327014, 2.4 ) : g * 0.0773993808; +// dec.b = b > 0.04045 ? Math.pow( b * 0.9478672986 + 0.0521327014, 2.4 ) : b * 0.0773993808; +// dec.a = a; + +// } + + +function setUint8( dv, value, offset ) { + + dv.setUint8( offset.value, value ); + + offset.value += 1; + +} + +function setUint32( dv, value, offset ) { + + dv.setUint32( offset.value, value, true ); + + offset.value += 4; + +} + +function setFloat16( dv, value, offset ) { + + dv.setUint16( offset.value, DataUtils.toHalfFloat( value ), true ); + + offset.value += 2; + +} + +function setFloat32( dv, value, offset ) { + + dv.setFloat32( offset.value, value, true ); + + offset.value += 4; + +} + +function setUint64( dv, value, offset ) { + + dv.setBigUint64( offset.value, BigInt( value ), true ); + + offset.value += 8; + +} + +function setString( dv, string, offset ) { + + const tmp = textEncoder.encode( string + '\0' ); + + for ( let i = 0; i < tmp.length; ++ i ) { + + setUint8( dv, tmp[ i ], offset ); + + } + +} + +function decodeFloat16( binary ) { + + const exponent = ( binary & 0x7C00 ) >> 10, + fraction = binary & 0x03FF; + + return ( binary >> 15 ? - 1 : 1 ) * ( + exponent ? + ( + exponent === 0x1F ? + fraction ? NaN : Infinity : + Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 ) + ) : + 6.103515625e-5 * ( fraction / 0x400 ) + ); + +} + +function getFloat16( arr, i ) { + + return decodeFloat16( arr[ i ] ); + +} + +function getFloat32( arr, i ) { + + return arr[ i ]; + +} + +export { EXRExporter, NO_COMPRESSION, ZIP_COMPRESSION, ZIPS_COMPRESSION }; diff --git a/jsm/exporters/GLTFExporter.js b/jsm/exporters/GLTFExporter.js new file mode 100644 index 0000000..eb93845 --- /dev/null +++ b/jsm/exporters/GLTFExporter.js @@ -0,0 +1,2647 @@ +import { + BufferAttribute, + ClampToEdgeWrapping, + DoubleSide, + InterpolateDiscrete, + InterpolateLinear, + LinearFilter, + LinearMipmapLinearFilter, + LinearMipmapNearestFilter, + MathUtils, + Matrix4, + MirroredRepeatWrapping, + NearestFilter, + NearestMipmapLinearFilter, + NearestMipmapNearestFilter, + PropertyBinding, + RGBAFormat, + RepeatWrapping, + Scene, + Source, + Vector3 +} from 'three'; + + +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 GLTFMaterialsPBRSpecularGlossiness( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsTransmissionExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsVolumeExtension( writer ); + + } ); + + this.register( function ( writer ) { + + return new GLTFMaterialsClearcoatExtension( 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 ) { + + if ( typeof onError === 'object' ) { + + console.warn( 'THREE.GLTFExporter: parse() expects options as the fourth argument now.' ); + + options = onError; + + } + + 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, + + 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 +}; + +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' +}; + +// 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 ) { + + 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; + +} + +/** + * 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 ); + + } + + 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; + +} + +let cachedCanvas = null; + +/** + * 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.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, + truncateDrawRange: true, + embedImages: 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; + + // 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 === true ) { + + // 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 = 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 window.FileReader(); + glbReader.readAsArrayBuffer( glbBlob ); + glbReader.onloadend = function () { + + onDone( glbReader.result ); + + }; + + }; + + } else { + + if ( json.buffers && json.buffers.length > 0 ) { + + const reader = new window.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 ); + + } + + } + + /** + * Assign and return a temporal unique id for an object + * especially which doesn't have .uuid + * @param {Object} object + * @return {Integer} + */ + getUID( object ) { + + 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} + */ + 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; + + console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' ); + + const metalness = metalnessMap?.image; + const roughness = roughnessMap?.image; + + const width = Math.max( metalness?.width || 0, roughness?.width || 0 ); + const height = Math.max( metalness?.height || 0, roughness?.height || 0 ); + + const canvas = document.createElement( 'canvas' ); + 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 data = context.getImageData( 0, 0, width, height ).data; + + for ( let i = 2; i < data.length; i += 4 ) { + + composite.data[ i ] = data[ i ]; + + } + + } + + if ( roughness ) { + + context.drawImage( roughness, 0, 0, width, height ); + + const data = context.getImageData( 0, 0, width, height ).data; + + for ( let i = 1; i < data.length; i += 4 ) { + + composite.data[ i ] = data[ i ]; + + } + + } + + context.putImageData( composite, 0, 0 ); + + // + + const reference = metalnessMap || roughnessMap; + + const texture = reference.clone(); + + texture.source = new Source( canvas ); + + 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; + + if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) { + + componentSize = 1; + + } else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) { + + componentSize = 2; + + } else { + + 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 ( 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 = { + + 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 window.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 options = this.options; + const json = this.json; + + const types = { + + 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 = 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' ) { + + 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 }; + + if ( options.embedImages ) { + + const canvas = cachedCanvas = 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 === true ) { + + 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 RGBAFormat 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 ); + + 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 === true ) { + + pending.push( new Promise( function ( resolve ) { + + canvas.toBlob( function ( blob ) { + + writer.processBufferViewImage( blob ).then( function ( bufferViewIndex ) { + + imageDef.bufferView = bufferViewIndex; + resolve(); + + } ); + + }, mimeType ); + + } ) ); + + } else { + + imageDef.uri = canvas.toDataURL( mimeType ); + + } + + } else { + + imageDef.uri = image.src; + + } + + const index = json.images.push( imageDef ) - 1; + cachedImages[ key ] = index; + return index; + + } + + /** + * 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 cache = this.cache; + const json = this.json; + + if ( cache.textures.has( map ) ) return cache.textures.get( map ); + + if ( ! json.textures ) json.textures = []; + + 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 ) }; + this.applyTextureTransform( metalRoughMapDef, metalRoughTexture ); + materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef; + + } + + // pbrMetallicRoughness.baseColorTexture or pbrSpecularGlossiness diffuseTexture + if ( material.map ) { + + const baseColorMapDef = { index: this.processTexture( material.map ) }; + this.applyTextureTransform( baseColorMapDef, material.map ); + materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef; + + } + + if ( 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.normalMap ) { + + const normalMapDef = { 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.aoMap ) { + + const 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 ) { + + 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; + + } + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.GLTFExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const meshDef = {}; + const attributes = {}; + 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 && ! 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 ) { + + 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 ) ) ) { + + 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 ) + ); + + } + + } + + target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry ); + 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 = { + 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; + + } + + /** + * 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; + + } + +} + +/** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness + */ +class GLTFMaterialsPBRSpecularGlossiness { + + constructor( writer ) { + + this.writer = writer; + this.name = 'KHR_materials_pbrSpecularGlossiness'; + + } + + writeMaterial( material, materialDef ) { + + if ( ! material.isGLTFSpecularGlossinessMaterial ) return; + + const writer = this.writer; + const extensionsUsed = writer.extensionsUsed; + + const extensionDef = {}; + + if ( materialDef.pbrMetallicRoughness.baseColorFactor ) { + + extensionDef.diffuseFactor = materialDef.pbrMetallicRoughness.baseColorFactor; + + } + + const specularFactor = [ 1, 1, 1 ]; + material.specular.toArray( specularFactor, 0 ); + extensionDef.specularFactor = specularFactor; + extensionDef.glossinessFactor = material.glossiness; + + if ( materialDef.pbrMetallicRoughness.baseColorTexture ) { + + extensionDef.diffuseTexture = materialDef.pbrMetallicRoughness.baseColorTexture; + + } + + if ( 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; + + } + +} + +/** + * 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 ) 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 ) }; + writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap ); + extensionDef.clearcoatTexture = clearcoatMapDef; + + } + + extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness; + + if ( material.clearcoatRoughnessMap ) { + + const clearcoatRoughnessMapDef = { index: writer.processTexture( material.clearcoatRoughnessMap ) }; + writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap ); + extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef; + + } + + if ( material.clearcoatNormalMap ) { + + const clearcoatNormalMapDef = { index: writer.processTexture( material.clearcoatNormalMap ) }; + writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap ); + extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef; + + } + + 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 ) }; + 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 ) }; + 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; + + } + +} + +/** + * 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/jsm/exporters/MMDExporter.js b/jsm/exporters/MMDExporter.js new file mode 100644 index 0000000..36cc8b3 --- /dev/null +++ b/jsm/exporters/MMDExporter.js @@ -0,0 +1,217 @@ +import { + Matrix4, + Quaternion, + Vector3 +} from 'three'; +import { MMDParser } from '../libs/mmdparser.module.js'; + +/** + * Dependencies + * - mmd-parser https://github.com/takahirox/mmd-parser + */ + +class MMDExporter { + + /* TODO: implement + // mesh -> pmd + this.parsePmd = function ( object ) { + + }; + */ + + /* TODO: implement + // mesh -> pmx + this.parsePmx = function ( object ) { + + }; + */ + + /* TODO: implement + // animation + skeleton -> vmd + this.parseVmd = function ( object ) { + + }; + */ + + /* + * skeleton -> vpd + * Returns Shift_JIS encoded Uint8Array. Otherwise return strings. + */ + parseVpd( skin, outputShiftJis, useOriginalBones ) { + + if ( skin.isSkinnedMesh !== true ) { + + console.warn( 'THREE.MMDExporter: parseVpd() requires SkinnedMesh instance.' ); + return null; + + } + + function toStringsFromNumber( num ) { + + if ( Math.abs( num ) < 1e-6 ) num = 0; + + let a = num.toString(); + + if ( a.indexOf( '.' ) === - 1 ) { + + a += '.'; + + } + + a += '000000'; + + const index = a.indexOf( '.' ); + + const d = a.slice( 0, index ); + const p = a.slice( index + 1, index + 7 ); + + return d + '.' + p; + + } + + function toStringsFromArray( array ) { + + const a = []; + + for ( let i = 0, il = array.length; i < il; i ++ ) { + + a.push( toStringsFromNumber( array[ i ] ) ); + + } + + return a.join( ',' ); + + } + + skin.updateMatrixWorld( true ); + + const bones = skin.skeleton.bones; + const bones2 = getBindBones( skin ); + + const position = new Vector3(); + const quaternion = new Quaternion(); + const quaternion2 = new Quaternion(); + const matrix = new Matrix4(); + + const array = []; + array.push( 'Vocaloid Pose Data file' ); + array.push( '' ); + array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' ); + array.push( bones.length + ';' ); + array.push( '' ); + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + const bone = bones[ i ]; + const bone2 = bones2[ i ]; + + /* + * use the bone matrix saved before solving IK. + * see CCDIKSolver for the detail. + */ + if ( useOriginalBones === true && + bone.userData.ik !== undefined && + bone.userData.ik.originalMatrix !== undefined ) { + + matrix.fromArray( bone.userData.ik.originalMatrix ); + + } else { + + matrix.copy( bone.matrix ); + + } + + position.setFromMatrixPosition( matrix ); + quaternion.setFromRotationMatrix( matrix ); + + const pArray = position.sub( bone2.position ).toArray(); + const qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); + + // right to left + pArray[ 2 ] = - pArray[ 2 ]; + qArray[ 0 ] = - qArray[ 0 ]; + qArray[ 1 ] = - qArray[ 1 ]; + + array.push( 'Bone' + i + '{' + bone.name ); + array.push( ' ' + toStringsFromArray( pArray ) + ';' ); + array.push( ' ' + toStringsFromArray( qArray ) + ';' ); + array.push( '}' ); + array.push( '' ); + + } + + array.push( '' ); + + const lines = array.join( '\n' ); + + return ( outputShiftJis === true ) ? unicodeToShiftjis( lines ) : lines; + + } + +} + +// Unicode to Shift_JIS table +let u2sTable; + +function unicodeToShiftjis( str ) { + + if ( u2sTable === undefined ) { + + const encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef + const table = encoder.s2uTable; + u2sTable = {}; + + const keys = Object.keys( table ); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + let key = keys[ i ]; + + const value = table[ key ]; + key = parseInt( key ); + + u2sTable[ value ] = key; + + } + + } + + const array = []; + + for ( let i = 0, il = str.length; i < il; i ++ ) { + + const code = str.charCodeAt( i ); + + const value = u2sTable[ code ]; + + if ( value === undefined ) { + + throw new Error( 'cannot convert charcode 0x' + code.toString( 16 ) ); + + } else if ( value > 0xff ) { + + array.push( ( value >> 8 ) & 0xff ); + array.push( value & 0xff ); + + } else { + + array.push( value & 0xff ); + + } + + } + + return new Uint8Array( array ); + +} + +function getBindBones( skin ) { + + // any more efficient ways? + const poseSkin = skin.clone(); + poseSkin.pose(); + return poseSkin.skeleton.bones; + +} + +export { MMDExporter }; diff --git a/jsm/exporters/OBJExporter.js b/jsm/exporters/OBJExporter.js new file mode 100644 index 0000000..0c7265e --- /dev/null +++ b/jsm/exporters/OBJExporter.js @@ -0,0 +1,302 @@ +import { + Color, + Matrix3, + Vector2, + Vector3 +} from 'three'; + +class OBJExporter { + + parse( object ) { + + let output = ''; + + let indexVertex = 0; + let indexVertexUvs = 0; + let indexNormals = 0; + + const vertex = new Vector3(); + const color = new Color(); + const normal = new Vector3(); + const uv = new Vector2(); + + const face = []; + + function parseMesh( mesh ) { + + let nbVertex = 0; + let nbNormals = 0; + let nbVertexUvs = 0; + + const geometry = mesh.geometry; + + const normalMatrixWorld = new Matrix3(); + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + // shortcuts + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const indices = geometry.getIndex(); + + // name of the mesh object + output += 'o ' + mesh.name + '\n'; + + // name of the mesh material + if ( mesh.material && mesh.material.name ) { + + output += 'usemtl ' + mesh.material.name + '\n'; + + } + + // vertices + + if ( vertices !== undefined ) { + + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + // transform the vertex to world space + vertex.applyMatrix4( mesh.matrixWorld ); + + // transform the vertex to export format + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + // uvs + + if ( uvs !== undefined ) { + + for ( let i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) { + + uv.fromBufferAttribute( uvs, i ); + + // transform the uv to export format + output += 'vt ' + uv.x + ' ' + uv.y + '\n'; + + } + + } + + // normals + + if ( normals !== undefined ) { + + normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); + + for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) { + + normal.fromBufferAttribute( normals, i ); + + // transform the normal to world space + normal.applyMatrix3( normalMatrixWorld ).normalize(); + + // transform the normal to export format + output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n'; + + } + + } + + // faces + + if ( indices !== null ) { + + for ( let i = 0, l = indices.count; i < l; i += 3 ) { + + for ( let m = 0; m < 3; m ++ ) { + + const j = indices.getX( i + m ) + 1; + + face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); + + } + + // transform the face to export format + output += 'f ' + face.join( ' ' ) + '\n'; + + } + + } else { + + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { + + for ( let m = 0; m < 3; m ++ ) { + + const j = i + m + 1; + + face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' ); + + } + + // transform the face to export format + output += 'f ' + face.join( ' ' ) + '\n'; + + } + + } + + // update index + indexVertex += nbVertex; + indexVertexUvs += nbVertexUvs; + indexNormals += nbNormals; + + } + + function parseLine( line ) { + + let nbVertex = 0; + + const geometry = line.geometry; + const type = line.type; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + // shortcuts + const vertices = geometry.getAttribute( 'position' ); + + // name of the line object + output += 'o ' + line.name + '\n'; + + if ( vertices !== undefined ) { + + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + // transform the vertex to world space + vertex.applyMatrix4( line.matrixWorld ); + + // transform the vertex to export format + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + if ( type === 'Line' ) { + + output += 'l '; + + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { + + output += ( indexVertex + j ) + ' '; + + } + + output += '\n'; + + } + + if ( type === 'LineSegments' ) { + + for ( let j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) { + + output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n'; + + } + + } + + // update index + indexVertex += nbVertex; + + } + + function parsePoints( points ) { + + let nbVertex = 0; + + const geometry = points.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const vertices = geometry.getAttribute( 'position' ); + const colors = geometry.getAttribute( 'color' ); + + output += 'o ' + points.name + '\n'; + + if ( vertices !== undefined ) { + + for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + vertex.applyMatrix4( points.matrixWorld ); + + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z; + + if ( colors !== undefined ) { + + color.fromBufferAttribute( colors, i ).convertLinearToSRGB(); + + output += ' ' + color.r + ' ' + color.g + ' ' + color.b; + + } + + output += '\n'; + + } + + output += 'p '; + + for ( let j = 1, l = vertices.count; j <= l; j ++ ) { + + output += ( indexVertex + j ) + ' '; + + } + + output += '\n'; + + } + + // update index + indexVertex += nbVertex; + + } + + object.traverse( function ( child ) { + + if ( child.isMesh === true ) { + + parseMesh( child ); + + } + + if ( child.isLine === true ) { + + parseLine( child ); + + } + + if ( child.isPoints === true ) { + + parsePoints( child ); + + } + + } ); + + return output; + + } + +} + +export { OBJExporter }; diff --git a/jsm/exporters/PLYExporter.js b/jsm/exporters/PLYExporter.js new file mode 100644 index 0000000..a360658 --- /dev/null +++ b/jsm/exporters/PLYExporter.js @@ -0,0 +1,529 @@ +import { + Matrix3, + Vector3, + Color +} from 'three'; + +/** + * https://github.com/gkjohnson/ply-exporter-js + * + * Usage: + * const exporter = new PLYExporter(); + * + * // second argument is a list of options + * exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true }); + * + * Format Definition: + * http://paulbourke.net/dataformats/ply/ + */ + +class PLYExporter { + + parse( object, onDone, options ) { + + if ( onDone && typeof onDone === 'object' ) { + + console.warn( 'THREE.PLYExporter: The options parameter is now the third argument to the "parse" function. See the documentation for the new API.' ); + options = onDone; + onDone = undefined; + + } + + // Iterate over the valid meshes in the object + function traverseMeshes( cb ) { + + object.traverse( function ( child ) { + + if ( child.isMesh === true ) { + + const mesh = child; + const geometry = mesh.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + if ( geometry.hasAttribute( 'position' ) === true ) { + + cb( mesh, geometry ); + + } + + } + + } ); + + } + + // Default options + const defaultOptions = { + binary: false, + excludeAttributes: [], // normal, uv, color, index + littleEndian: false + }; + + options = Object.assign( defaultOptions, options ); + + const excludeAttributes = options.excludeAttributes; + let includeNormals = false; + let includeColors = false; + let includeUVs = false; + + // count the vertices, check which properties are used, + // and cache the BufferGeometry + let vertexCount = 0; + let faceCount = 0; + object.traverse( function ( child ) { + + if ( child.isMesh === true ) { + + const mesh = child; + const geometry = mesh.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); + + if ( vertices === undefined ) { + + return; + + } + + vertexCount += vertices.count; + faceCount += indices ? indices.count / 3 : vertices.count / 3; + + if ( normals !== undefined ) includeNormals = true; + + if ( uvs !== undefined ) includeUVs = true; + + if ( colors !== undefined ) includeColors = true; + + } + + } ); + + const tempColor = new Color(); + const includeIndices = excludeAttributes.indexOf( 'index' ) === - 1; + includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1; + includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1; + includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1; + + + if ( includeIndices && faceCount !== Math.floor( faceCount ) ) { + + // point cloud meshes will not have an index array and may not have a + // number of vertices that is divisble by 3 (and therefore representable + // as triangles) + console.error( + + 'PLYExporter: Failed to generate a valid PLY file with triangle indices because the ' + + 'number of indices is not divisible by 3.' + + ); + + return null; + + } + + const indexByteCount = 4; + + let header = + 'ply\n' + + `format ${ options.binary ? ( options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' ) : 'ascii' } 1.0\n` + + `element vertex ${vertexCount}\n` + + + // position + 'property float x\n' + + 'property float y\n' + + 'property float z\n'; + + if ( includeNormals === true ) { + + // normal + header += + 'property float nx\n' + + 'property float ny\n' + + 'property float nz\n'; + + } + + if ( includeUVs === true ) { + + // uvs + header += + 'property float s\n' + + 'property float t\n'; + + } + + if ( includeColors === true ) { + + // colors + header += + 'property uchar red\n' + + 'property uchar green\n' + + 'property uchar blue\n'; + + } + + if ( includeIndices === true ) { + + // faces + header += + `element face ${faceCount}\n` + + 'property list uchar int vertex_index\n'; + + } + + header += 'end_header\n'; + + + // Generate attribute data + const vertex = new Vector3(); + const normalMatrixWorld = new Matrix3(); + let result = null; + + if ( options.binary === true ) { + + // Binary File Generation + const headerBin = new TextEncoder().encode( header ); + + // 3 position values at 4 bytes + // 3 normal values at 4 bytes + // 3 color channels with 1 byte + // 2 uv values at 4 bytes + const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); + + // 1 byte shape desciptor + // 3 vertex indices at ${indexByteCount} bytes + const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0; + const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) ); + new Uint8Array( output.buffer ).set( headerBin, 0 ); + + + let vOffset = headerBin.length; + let fOffset = headerBin.length + vertexListLength; + let writtenVertices = 0; + traverseMeshes( function ( mesh, geometry ) { + + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); + + normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); + + for ( let i = 0, l = vertices.count; i < l; i ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + vertex.applyMatrix4( mesh.matrixWorld ); + + + // Position information + output.setFloat32( vOffset, vertex.x, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.y, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.z, options.littleEndian ); + vOffset += 4; + + // Normal information + if ( includeNormals === true ) { + + if ( normals != null ) { + + vertex.fromBufferAttribute( normals, i ); + + vertex.applyMatrix3( normalMatrixWorld ).normalize(); + + output.setFloat32( vOffset, vertex.x, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.y, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, vertex.z, options.littleEndian ); + vOffset += 4; + + } else { + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + } + + } + + // UV information + if ( includeUVs === true ) { + + if ( uvs != null ) { + + output.setFloat32( vOffset, uvs.getX( i ), options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, uvs.getY( i ), options.littleEndian ); + vOffset += 4; + + } else { + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + output.setFloat32( vOffset, 0, options.littleEndian ); + vOffset += 4; + + } + + } + + // Color information + if ( includeColors === true ) { + + if ( colors != null ) { + + tempColor + .fromBufferAttribute( colors, i ) + .convertLinearToSRGB(); + + output.setUint8( vOffset, Math.floor( tempColor.r * 255 ) ); + vOffset += 1; + + output.setUint8( vOffset, Math.floor( tempColor.g * 255 ) ); + vOffset += 1; + + output.setUint8( vOffset, Math.floor( tempColor.b * 255 ) ); + vOffset += 1; + + } else { + + output.setUint8( vOffset, 255 ); + vOffset += 1; + + output.setUint8( vOffset, 255 ); + vOffset += 1; + + output.setUint8( vOffset, 255 ); + vOffset += 1; + + } + + } + + } + + if ( includeIndices === true ) { + + // Create the face list + + if ( indices !== null ) { + + for ( let i = 0, l = indices.count; i < l; i += 3 ) { + + output.setUint8( fOffset, 3 ); + fOffset += 1; + + output.setUint32( fOffset, indices.getX( i + 0 ) + writtenVertices, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, indices.getX( i + 1 ) + writtenVertices, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, indices.getX( i + 2 ) + writtenVertices, options.littleEndian ); + fOffset += indexByteCount; + + } + + } else { + + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { + + output.setUint8( fOffset, 3 ); + fOffset += 1; + + output.setUint32( fOffset, writtenVertices + i, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, writtenVertices + i + 1, options.littleEndian ); + fOffset += indexByteCount; + + output.setUint32( fOffset, writtenVertices + i + 2, options.littleEndian ); + fOffset += indexByteCount; + + } + + } + + } + + + // Save the amount of verts we've already written so we can offset + // the face index on the next mesh + writtenVertices += vertices.count; + + } ); + + result = output.buffer; + + } else { + + // Ascii File Generation + // count the number of vertices + let writtenVertices = 0; + let vertexList = ''; + let faceList = ''; + + traverseMeshes( function ( mesh, geometry ) { + + const vertices = geometry.getAttribute( 'position' ); + const normals = geometry.getAttribute( 'normal' ); + const uvs = geometry.getAttribute( 'uv' ); + const colors = geometry.getAttribute( 'color' ); + const indices = geometry.getIndex(); + + normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); + + // form each line + for ( let i = 0, l = vertices.count; i < l; i ++ ) { + + vertex.fromBufferAttribute( vertices, i ); + + vertex.applyMatrix4( mesh.matrixWorld ); + + + // Position information + let line = + vertex.x + ' ' + + vertex.y + ' ' + + vertex.z; + + // Normal information + if ( includeNormals === true ) { + + if ( normals != null ) { + + vertex.fromBufferAttribute( normals, i ); + + vertex.applyMatrix3( normalMatrixWorld ).normalize(); + + line += ' ' + + vertex.x + ' ' + + vertex.y + ' ' + + vertex.z; + + } else { + + line += ' 0 0 0'; + + } + + } + + // UV information + if ( includeUVs === true ) { + + if ( uvs != null ) { + + line += ' ' + + uvs.getX( i ) + ' ' + + uvs.getY( i ); + + } else { + + line += ' 0 0'; + + } + + } + + // Color information + if ( includeColors === true ) { + + if ( colors != null ) { + + tempColor + .fromBufferAttribute( colors, i ) + .convertLinearToSRGB(); + + line += ' ' + + Math.floor( tempColor.r * 255 ) + ' ' + + Math.floor( tempColor.g * 255 ) + ' ' + + Math.floor( tempColor.b * 255 ); + + } else { + + line += ' 255 255 255'; + + } + + } + + vertexList += line + '\n'; + + } + + // Create the face list + if ( includeIndices === true ) { + + if ( indices !== null ) { + + for ( let i = 0, l = indices.count; i < l; i += 3 ) { + + faceList += `3 ${ indices.getX( i + 0 ) + writtenVertices }`; + faceList += ` ${ indices.getX( i + 1 ) + writtenVertices }`; + faceList += ` ${ indices.getX( i + 2 ) + writtenVertices }\n`; + + } + + } else { + + for ( let i = 0, l = vertices.count; i < l; i += 3 ) { + + faceList += `3 ${ writtenVertices + i } ${ writtenVertices + i + 1 } ${ writtenVertices + i + 2 }\n`; + + } + + } + + faceCount += indices ? indices.count / 3 : vertices.count / 3; + + } + + writtenVertices += vertices.count; + + } ); + + result = `${ header }${vertexList}${ includeIndices ? `${faceList}\n` : '\n' }`; + + } + + if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) ); + + return result; + + } + +} + +export { PLYExporter }; diff --git a/jsm/exporters/STLExporter.js b/jsm/exporters/STLExporter.js new file mode 100644 index 0000000..ff77e79 --- /dev/null +++ b/jsm/exporters/STLExporter.js @@ -0,0 +1,203 @@ +import { + Vector3 +} from 'three'; + +/** + * Usage: + * const exporter = new STLExporter(); + * + * // second argument is a list of options + * const data = exporter.parse( mesh, { binary: true } ); + * + */ + +class STLExporter { + + parse( scene, options = {} ) { + + const binary = options.binary !== undefined ? options.binary : false; + + // + + const objects = []; + let triangles = 0; + + scene.traverse( function ( object ) { + + if ( object.isMesh ) { + + const geometry = object.geometry; + + if ( geometry.isBufferGeometry !== true ) { + + throw new Error( 'THREE.STLExporter: Geometry is not of type THREE.BufferGeometry.' ); + + } + + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); + + triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 ); + + objects.push( { + object3d: object, + geometry: geometry + } ); + + } + + } ); + + let output; + let offset = 80; // skip header + + if ( binary === true ) { + + const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; + const arrayBuffer = new ArrayBuffer( bufferLength ); + output = new DataView( arrayBuffer ); + output.setUint32( offset, triangles, true ); offset += 4; + + } else { + + output = ''; + output += 'solid exported\n'; + + } + + const vA = new Vector3(); + const vB = new Vector3(); + const vC = new Vector3(); + const cb = new Vector3(); + const ab = new Vector3(); + const normal = new Vector3(); + + for ( let i = 0, il = objects.length; i < il; i ++ ) { + + const object = objects[ i ].object3d; + const geometry = objects[ i ].geometry; + + const index = geometry.index; + const positionAttribute = geometry.getAttribute( 'position' ); + + if ( index !== null ) { + + // indexed geometry + + for ( let j = 0; j < index.count; j += 3 ) { + + const a = index.getX( j + 0 ); + const b = index.getX( j + 1 ); + const c = index.getX( j + 2 ); + + writeFace( a, b, c, positionAttribute, object ); + + } + + } else { + + // non-indexed geometry + + for ( let j = 0; j < positionAttribute.count; j += 3 ) { + + const a = j + 0; + const b = j + 1; + const c = j + 2; + + writeFace( a, b, c, positionAttribute, object ); + + } + + } + + } + + if ( binary === false ) { + + output += 'endsolid exported\n'; + + } + + return output; + + function writeFace( a, b, c, positionAttribute, object ) { + + vA.fromBufferAttribute( positionAttribute, a ); + vB.fromBufferAttribute( positionAttribute, b ); + vC.fromBufferAttribute( positionAttribute, c ); + + if ( object.isSkinnedMesh === true ) { + + object.boneTransform( a, vA ); + object.boneTransform( b, vB ); + object.boneTransform( c, vC ); + + } + + vA.applyMatrix4( object.matrixWorld ); + vB.applyMatrix4( object.matrixWorld ); + vC.applyMatrix4( object.matrixWorld ); + + writeNormal( vA, vB, vC ); + + writeVertex( vA ); + writeVertex( vB ); + writeVertex( vC ); + + if ( binary === true ) { + + output.setUint16( offset, 0, true ); offset += 2; + + } else { + + output += '\t\tendloop\n'; + output += '\tendfacet\n'; + + } + + } + + function writeNormal( vA, vB, vC ) { + + cb.subVectors( vC, vB ); + ab.subVectors( vA, vB ); + cb.cross( ab ).normalize(); + + normal.copy( cb ).normalize(); + + if ( binary === true ) { + + output.setFloat32( offset, normal.x, true ); offset += 4; + output.setFloat32( offset, normal.y, true ); offset += 4; + output.setFloat32( offset, normal.z, true ); offset += 4; + + } else { + + output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n'; + output += '\t\touter loop\n'; + + } + + } + + function writeVertex( vertex ) { + + if ( binary === true ) { + + output.setFloat32( offset, vertex.x, true ); offset += 4; + output.setFloat32( offset, vertex.y, true ); offset += 4; + output.setFloat32( offset, vertex.z, true ); offset += 4; + + } else { + + output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + } + +} + +export { STLExporter }; diff --git a/jsm/exporters/USDZExporter.js b/jsm/exporters/USDZExporter.js new file mode 100644 index 0000000..71e32df --- /dev/null +++ b/jsm/exporters/USDZExporter.js @@ -0,0 +1,547 @@ +import * as fflate from '../libs/fflate.module.js'; + +class USDZExporter { + + async parse( scene ) { + + const files = {}; + const modelFileName = 'model.usda'; + + // model file should be first in USDZ archive so we init it here + files[ modelFileName ] = null; + + let output = buildHeader(); + + const materials = {}; + const textures = {}; + + scene.traverseVisible( ( object ) => { + + if ( object.isMesh ) { + + if ( object.material.isMeshStandardMaterial ) { + + const geometry = object.geometry; + const material = object.material; + + const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usd'; + + if ( ! ( geometryFileName in files ) ) { + + const meshObject = buildMeshObject( geometry ); + files[ geometryFileName ] = buildUSDFileAsString( meshObject ); + + } + + if ( ! ( material.uuid in materials ) ) { + + materials[ material.uuid ] = material; + + } + + output += buildXform( object, geometry, material ); + + } else { + + console.warn( 'THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)', object ); + + } + + } + + } ); + + output += buildMaterials( materials, textures ); + + files[ modelFileName ] = fflate.strToU8( output ); + output = null; + + for ( const id in textures ) { + + const texture = textures[ id ]; + const color = id.split( '_' )[ 1 ]; + const isRGBA = texture.format === 1023; + + const canvas = imageToCanvas( texture.image, color ); + const blob = await new Promise( resolve => canvas.toBlob( resolve, isRGBA ? 'image/png' : 'image/jpeg', 1 ) ); + + files[ `textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }` ] = new Uint8Array( await blob.arrayBuffer() ); + + } + + // 64 byte alignment + // https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109 + + let offset = 0; + + for ( const filename in files ) { + + const file = files[ filename ]; + const headerSize = 34 + filename.length; + + offset += headerSize; + + const offsetMod64 = offset & 63; + + if ( offsetMod64 !== 4 ) { + + const padLength = 64 - offsetMod64; + const padding = new Uint8Array( padLength ); + + files[ filename ] = [ file, { extra: { 12345: padding } } ]; + + } + + offset = file.length; + + } + + return fflate.zipSync( files, { level: 0 } ); + + } + +} + +function imageToCanvas( image, color ) { + + 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 ) ) { + + const scale = 1024 / Math.max( image.width, image.height ); + + const canvas = document.createElement( 'canvas' ); + canvas.width = image.width * Math.min( 1, scale ); + canvas.height = image.height * Math.min( 1, scale ); + + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0, canvas.width, canvas.height ); + + if ( color !== undefined ) { + + const hex = parseInt( color, 16 ); + + const r = ( hex >> 16 & 255 ) / 255; + const g = ( hex >> 8 & 255 ) / 255; + const b = ( hex & 255 ) / 255; + + const imagedata = context.getImageData( 0, 0, canvas.width, canvas.height ); + const data = imagedata.data; + + for ( let i = 0; i < data.length; i += 4 ) { + + data[ i + 0 ] = data[ i + 0 ] * r; + data[ i + 1 ] = data[ i + 1 ] * g; + data[ i + 2 ] = data[ i + 2 ] * b; + + } + + context.putImageData( imagedata, 0, 0 ); + + } + + return canvas; + + } + +} + +// + +const PRECISION = 7; + +function buildHeader() { + + return `#usda 1.0 +( + customLayerData = { + string creator = "Three.js USDZExporter" + } + metersPerUnit = 1 + upAxis = "Y" +) + +`; + +} + +function buildUSDFileAsString( dataToInsert ) { + + let output = buildHeader(); + output += dataToInsert; + return fflate.strToU8( output ); + +} + +// Xform + +function buildXform( object, geometry, material ) { + + const name = 'Object_' + object.id; + const transform = buildMatrix( object.matrixWorld ); + + if ( object.matrixWorld.determinant() < 0 ) { + + console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', object ); + + } + + return `def Xform "${ name }" ( + prepend references = @./geometries/Geometry_${ geometry.id }.usd@ +) +{ + matrix4d xformOp:transform = ${ transform } + uniform token[] xformOpOrder = ["xformOp:transform"] + + rel material:binding = +} + +`; + +} + +function buildMatrix( matrix ) { + + const array = matrix.elements; + + return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`; + +} + +function buildMatrixRow( array, offset ) { + + return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`; + +} + +// Mesh + +function buildMeshObject( geometry ) { + + const mesh = buildMesh( geometry ); + return ` +def "Geometry" +{ + ${mesh} +} +`; + +} + +function buildMesh( geometry ) { + + const name = 'Geometry'; + const attributes = geometry.attributes; + const count = attributes.position.count; + + return ` + def Mesh "${ name }" + { + int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }] + int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }] + normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] ( + interpolation = "vertex" + ) + point3f[] points = [${ buildVector3Array( attributes.position, count )}] + float2[] primvars:st = [${ buildVector2Array( attributes.uv, count )}] ( + interpolation = "vertex" + ) + uniform token subdivisionScheme = "none" + } +`; + +} + +function buildMeshVertexCount( geometry ) { + + const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count; + + return Array( count / 3 ).fill( 3 ).join( ', ' ); + +} + +function buildMeshVertexIndices( geometry ) { + + const index = geometry.index; + const array = []; + + if ( index !== null ) { + + for ( let i = 0; i < index.count; i ++ ) { + + array.push( index.getX( i ) ); + + } + + } else { + + const length = geometry.attributes.position.count; + + for ( let i = 0; i < length; i ++ ) { + + array.push( i ); + + } + + } + + return array.join( ', ' ); + +} + +function buildVector3Array( attribute, count ) { + + if ( attribute === undefined ) { + + console.warn( 'USDZExporter: Normals missing.' ); + return Array( count ).fill( '(0, 0, 0)' ).join( ', ' ); + + } + + const array = []; + + for ( let i = 0; i < attribute.count; i ++ ) { + + const x = attribute.getX( i ); + const y = attribute.getY( i ); + const z = attribute.getZ( i ); + + array.push( `(${ x.toPrecision( PRECISION ) }, ${ y.toPrecision( PRECISION ) }, ${ z.toPrecision( PRECISION ) })` ); + + } + + return array.join( ', ' ); + +} + +function buildVector2Array( attribute, count ) { + + if ( attribute === undefined ) { + + console.warn( 'USDZExporter: UVs missing.' ); + return Array( count ).fill( '(0, 0)' ).join( ', ' ); + + } + + const array = []; + + for ( let i = 0; i < attribute.count; i ++ ) { + + const x = attribute.getX( i ); + const y = attribute.getY( i ); + + array.push( `(${ x.toPrecision( PRECISION ) }, ${ 1 - y.toPrecision( PRECISION ) })` ); + + } + + return array.join( ', ' ); + +} + +// Materials + +function buildMaterials( materials, textures ) { + + const array = []; + + for ( const uuid in materials ) { + + const material = materials[ uuid ]; + + array.push( buildMaterial( material, textures ) ); + + } + + return `def "Materials" +{ +${ array.join( '' ) } +} + +`; + +} + +function buildMaterial( material, textures ) { + + // https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html + + const pad = ' '; + const inputs = []; + const samplers = []; + + function buildTexture( texture, mapType, color ) { + + const id = texture.id + ( color ? '_' + color.getHexString() : '' ); + const isRGBA = texture.format === 1023; + + textures[ id ] = texture; + + return ` + def Shader "Transform2d_${ mapType }" ( + sdrMetadata = { + string role = "math" + } + ) + { + uniform token info:id = "UsdTransform2d" + float2 inputs:in.connect = + float2 inputs:scale = ${ buildVector2( texture.repeat ) } + float2 inputs:translation = ${ buildVector2( texture.offset ) } + float2 outputs:result + } + + def Shader "Texture_${ texture.id }_${ mapType }" + { + uniform token info:id = "UsdUVTexture" + asset inputs:file = @textures/Texture_${ id }.${ isRGBA ? 'png' : 'jpg' }@ + float2 inputs:st.connect = + token inputs:wrapS = "repeat" + token inputs:wrapT = "repeat" + float outputs:r + float outputs:g + float outputs:b + float3 outputs:rgb + ${ material.transparent || material.alphaTest > 0.0 ? 'float outputs:a' : '' } + }`; + + } + + if ( material.map !== null ) { + + inputs.push( `${ pad }color3f inputs:diffuseColor.connect = ` ); + + if ( material.transparent ) { + + inputs.push( `${ pad }float inputs:opacity.connect = ` ); + + } else if ( material.alphaTest > 0.0 ) { + + inputs.push( `${ pad }float inputs:opacity.connect = ` ); + inputs.push( `${ pad }float inputs:opacityThreshold = ${material.alphaTest}` ); + + } + + samplers.push( buildTexture( material.map, 'diffuse', material.color ) ); + + } else { + + inputs.push( `${ pad }color3f inputs:diffuseColor = ${ buildColor( material.color ) }` ); + + } + + if ( material.emissiveMap !== null ) { + + inputs.push( `${ pad }color3f inputs:emissiveColor.connect = ` ); + + samplers.push( buildTexture( material.emissiveMap, 'emissive' ) ); + + } else if ( material.emissive.getHex() > 0 ) { + + inputs.push( `${ pad }color3f inputs:emissiveColor = ${ buildColor( material.emissive ) }` ); + + } + + if ( material.normalMap !== null ) { + + inputs.push( `${ pad }normal3f inputs:normal.connect = ` ); + + samplers.push( buildTexture( material.normalMap, 'normal' ) ); + + } + + if ( material.aoMap !== null ) { + + inputs.push( `${ pad }float inputs:occlusion.connect = ` ); + + samplers.push( buildTexture( material.aoMap, 'occlusion' ) ); + + } + + if ( material.roughnessMap !== null && material.roughness === 1 ) { + + inputs.push( `${ pad }float inputs:roughness.connect = ` ); + + samplers.push( buildTexture( material.roughnessMap, 'roughness' ) ); + + } else { + + inputs.push( `${ pad }float inputs:roughness = ${ material.roughness }` ); + + } + + if ( material.metalnessMap !== null && material.metalness === 1 ) { + + inputs.push( `${ pad }float inputs:metallic.connect = ` ); + + samplers.push( buildTexture( material.metalnessMap, 'metallic' ) ); + + } else { + + inputs.push( `${ pad }float inputs:metallic = ${ material.metalness }` ); + + } + + if ( material.alphaMap !== null ) { + + inputs.push( `${pad}float inputs:opacity.connect = ` ); + inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` ); + + samplers.push( buildTexture( material.alphaMap, 'opacity' ) ); + + } else { + + inputs.push( `${pad}float inputs:opacity = ${material.opacity}` ); + + } + + if ( material.isMeshPhysicalMaterial ) { + + inputs.push( `${ pad }float inputs:clearcoat = ${ material.clearcoat }` ); + inputs.push( `${ pad }float inputs:clearcoatRoughness = ${ material.clearcoatRoughness }` ); + inputs.push( `${ pad }float inputs:ior = ${ material.ior }` ); + + } + + return ` + def Material "Material_${ material.id }" + { + def Shader "PreviewSurface" + { + uniform token info:id = "UsdPreviewSurface" +${ inputs.join( '\n' ) } + int inputs:useSpecularWorkflow = 0 + token outputs:surface + } + + token outputs:surface.connect = + token inputs:frame:stPrimvarName = "st" + + def Shader "uvReader_st" + { + uniform token info:id = "UsdPrimvarReader_float2" + token inputs:varname.connect = + float2 inputs:fallback = (0.0, 0.0) + float2 outputs:result + } + +${ samplers.join( '\n' ) } + + } +`; + +} + +function buildColor( color ) { + + return `(${ color.r }, ${ color.g }, ${ color.b })`; + +} + +function buildVector2( vector ) { + + return `(${ vector.x }, ${ vector.y })`; + +} + +export { USDZExporter }; diff --git a/jsm/geometries/BoxLineGeometry.js b/jsm/geometries/BoxLineGeometry.js new file mode 100644 index 0000000..8f05833 --- /dev/null +++ b/jsm/geometries/BoxLineGeometry.js @@ -0,0 +1,69 @@ +import { + BufferGeometry, + Float32BufferAttribute +} from 'three'; + +class BoxLineGeometry extends BufferGeometry { + + constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) { + + super(); + + widthSegments = Math.floor( widthSegments ); + heightSegments = Math.floor( heightSegments ); + depthSegments = Math.floor( depthSegments ); + + const widthHalf = width / 2; + const heightHalf = height / 2; + const depthHalf = depth / 2; + + const segmentWidth = width / widthSegments; + const segmentHeight = height / heightSegments; + const segmentDepth = depth / depthSegments; + + const vertices = []; + + let x = - widthHalf; + let y = - heightHalf; + let z = - depthHalf; + + for ( let i = 0; i <= widthSegments; i ++ ) { + + vertices.push( x, - heightHalf, - depthHalf, x, heightHalf, - depthHalf ); + vertices.push( x, heightHalf, - depthHalf, x, heightHalf, depthHalf ); + vertices.push( x, heightHalf, depthHalf, x, - heightHalf, depthHalf ); + vertices.push( x, - heightHalf, depthHalf, x, - heightHalf, - depthHalf ); + + x += segmentWidth; + + } + + for ( let i = 0; i <= heightSegments; i ++ ) { + + vertices.push( - widthHalf, y, - depthHalf, widthHalf, y, - depthHalf ); + vertices.push( widthHalf, y, - depthHalf, widthHalf, y, depthHalf ); + vertices.push( widthHalf, y, depthHalf, - widthHalf, y, depthHalf ); + vertices.push( - widthHalf, y, depthHalf, - widthHalf, y, - depthHalf ); + + y += segmentHeight; + + } + + for ( let i = 0; i <= depthSegments; i ++ ) { + + vertices.push( - widthHalf, - heightHalf, z, - widthHalf, heightHalf, z ); + vertices.push( - widthHalf, heightHalf, z, widthHalf, heightHalf, z ); + vertices.push( widthHalf, heightHalf, z, widthHalf, - heightHalf, z ); + vertices.push( widthHalf, - heightHalf, z, - widthHalf, - heightHalf, z ); + + z += segmentDepth; + + } + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + } + +} + +export { BoxLineGeometry }; diff --git a/jsm/geometries/ConvexGeometry.js b/jsm/geometries/ConvexGeometry.js new file mode 100644 index 0000000..32dbe93 --- /dev/null +++ b/jsm/geometries/ConvexGeometry.js @@ -0,0 +1,59 @@ +import { + BufferGeometry, + Float32BufferAttribute +} from 'three'; +import { ConvexHull } from '../math/ConvexHull.js'; + +class ConvexGeometry extends BufferGeometry { + + constructor( points = [] ) { + + super(); + + // buffers + + const vertices = []; + const normals = []; + + if ( ConvexHull === undefined ) { + + console.error( 'THREE.ConvexBufferGeometry: ConvexBufferGeometry relies on ConvexHull' ); + + } + + const convexHull = new ConvexHull().setFromPoints( points ); + + // generate vertices and normals + + const faces = convexHull.faces; + + for ( let i = 0; i < faces.length; i ++ ) { + + const face = faces[ i ]; + let edge = face.edge; + + // we move along a doubly-connected edge list to access all face points (see HalfEdge docs) + + do { + + const point = edge.head().point; + + vertices.push( point.x, point.y, point.z ); + normals.push( face.normal.x, face.normal.y, face.normal.z ); + + edge = edge.next; + + } while ( edge !== face.edge ); + + } + + // build geometry + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + + } + +} + +export { ConvexGeometry }; diff --git a/jsm/geometries/DecalGeometry.js b/jsm/geometries/DecalGeometry.js new file mode 100644 index 0000000..dc0bea9 --- /dev/null +++ b/jsm/geometries/DecalGeometry.js @@ -0,0 +1,363 @@ +import { + BufferGeometry, + Float32BufferAttribute, + Matrix4, + Vector3 +} from 'three'; + +/** + * You can use this geometry to create a decal mesh, that serves different kinds of purposes. + * e.g. adding unique details to models, performing dynamic visual environmental changes or covering seams. + * + * Constructor parameter: + * + * mesh — Any mesh object + * position — Position of the decal projector + * orientation — Orientation of the decal projector + * size — Size of the decal projector + * + * reference: http://blog.wolfire.com/2009/06/how-to-project-decals/ + * + */ + +class DecalGeometry extends BufferGeometry { + + constructor( mesh, position, orientation, size ) { + + super(); + + // buffers + + const vertices = []; + const normals = []; + const uvs = []; + + // helpers + + const plane = new Vector3(); + + // this matrix represents the transformation of the decal projector + + const projectorMatrix = new Matrix4(); + projectorMatrix.makeRotationFromEuler( orientation ); + projectorMatrix.setPosition( position ); + + const projectorMatrixInverse = new Matrix4(); + projectorMatrixInverse.copy( projectorMatrix ).invert(); + + // generate buffers + + generate(); + + // build geometry + + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + function generate() { + + let decalVertices = []; + + const vertex = new Vector3(); + const normal = new Vector3(); + + // handle different geometry types + + if ( mesh.geometry.isGeometry === true ) { + + console.error( 'THREE.DecalGeometry no longer supports THREE.Geometry. Use BufferGeometry instead.' ); + return; + + } + + const geometry = mesh.geometry; + + const positionAttribute = geometry.attributes.position; + const normalAttribute = geometry.attributes.normal; + + // first, create an array of 'DecalVertex' objects + // three consecutive 'DecalVertex' objects represent a single face + // + // this data structure will be later used to perform the clipping + + if ( geometry.index !== null ) { + + // indexed BufferGeometry + + const index = geometry.index; + + for ( let i = 0; i < index.count; i ++ ) { + + vertex.fromBufferAttribute( positionAttribute, index.getX( i ) ); + normal.fromBufferAttribute( normalAttribute, index.getX( i ) ); + + pushDecalVertex( decalVertices, vertex, normal ); + + } + + } else { + + // non-indexed BufferGeometry + + for ( let i = 0; i < positionAttribute.count; i ++ ) { + + vertex.fromBufferAttribute( positionAttribute, i ); + normal.fromBufferAttribute( normalAttribute, i ); + + pushDecalVertex( decalVertices, vertex, normal ); + + } + + } + + // second, clip the geometry so that it doesn't extend out from the projector + + decalVertices = clipGeometry( decalVertices, plane.set( 1, 0, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( - 1, 0, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 1, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, - 1, 0 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, 1 ) ); + decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, - 1 ) ); + + // third, generate final vertices, normals and uvs + + for ( let i = 0; i < decalVertices.length; i ++ ) { + + const decalVertex = decalVertices[ i ]; + + // create texture coordinates (we are still in projector space) + + uvs.push( + 0.5 + ( decalVertex.position.x / size.x ), + 0.5 + ( decalVertex.position.y / size.y ) + ); + + // transform the vertex back to world space + + decalVertex.position.applyMatrix4( projectorMatrix ); + + // now create vertex and normal buffer data + + vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z ); + normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z ); + + } + + } + + function pushDecalVertex( decalVertices, vertex, normal ) { + + // transform the vertex to world space, then to projector space + + vertex.applyMatrix4( mesh.matrixWorld ); + vertex.applyMatrix4( projectorMatrixInverse ); + + normal.transformDirection( mesh.matrixWorld ); + + decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) ); + + } + + function clipGeometry( inVertices, plane ) { + + const outVertices = []; + + const s = 0.5 * Math.abs( size.dot( plane ) ); + + // a single iteration clips one face, + // which consists of three consecutive 'DecalVertex' objects + + for ( let i = 0; i < inVertices.length; i += 3 ) { + + let total = 0; + let nV1; + let nV2; + let nV3; + let nV4; + + const d1 = inVertices[ i + 0 ].position.dot( plane ) - s; + const d2 = inVertices[ i + 1 ].position.dot( plane ) - s; + const d3 = inVertices[ i + 2 ].position.dot( plane ) - s; + + const v1Out = d1 > 0; + const v2Out = d2 > 0; + const v3Out = d3 > 0; + + // calculate, how many vertices of the face lie outside of the clipping plane + + total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 ); + + switch ( total ) { + + case 0: { + + // the entire face lies inside of the plane, no clipping needed + + outVertices.push( inVertices[ i ] ); + outVertices.push( inVertices[ i + 1 ] ); + outVertices.push( inVertices[ i + 2 ] ); + break; + + } + + case 1: { + + // one vertex lies outside of the plane, perform clipping + + if ( v1Out ) { + + nV1 = inVertices[ i + 1 ]; + nV2 = inVertices[ i + 2 ]; + nV3 = clip( inVertices[ i ], nV1, plane, s ); + nV4 = clip( inVertices[ i ], nV2, plane, s ); + + } + + if ( v2Out ) { + + nV1 = inVertices[ i ]; + nV2 = inVertices[ i + 2 ]; + nV3 = clip( inVertices[ i + 1 ], nV1, plane, s ); + nV4 = clip( inVertices[ i + 1 ], nV2, plane, s ); + + outVertices.push( nV3 ); + outVertices.push( nV2.clone() ); + outVertices.push( nV1.clone() ); + + outVertices.push( nV2.clone() ); + outVertices.push( nV3.clone() ); + outVertices.push( nV4 ); + break; + + } + + if ( v3Out ) { + + nV1 = inVertices[ i ]; + nV2 = inVertices[ i + 1 ]; + nV3 = clip( inVertices[ i + 2 ], nV1, plane, s ); + nV4 = clip( inVertices[ i + 2 ], nV2, plane, s ); + + } + + outVertices.push( nV1.clone() ); + outVertices.push( nV2.clone() ); + outVertices.push( nV3 ); + + outVertices.push( nV4 ); + outVertices.push( nV3.clone() ); + outVertices.push( nV2.clone() ); + + break; + + } + + case 2: { + + // two vertices lies outside of the plane, perform clipping + + if ( ! v1Out ) { + + nV1 = inVertices[ i ].clone(); + nV2 = clip( nV1, inVertices[ i + 1 ], plane, s ); + nV3 = clip( nV1, inVertices[ i + 2 ], plane, s ); + outVertices.push( nV1 ); + outVertices.push( nV2 ); + outVertices.push( nV3 ); + + } + + if ( ! v2Out ) { + + nV1 = inVertices[ i + 1 ].clone(); + nV2 = clip( nV1, inVertices[ i + 2 ], plane, s ); + nV3 = clip( nV1, inVertices[ i ], plane, s ); + outVertices.push( nV1 ); + outVertices.push( nV2 ); + outVertices.push( nV3 ); + + } + + if ( ! v3Out ) { + + nV1 = inVertices[ i + 2 ].clone(); + nV2 = clip( nV1, inVertices[ i ], plane, s ); + nV3 = clip( nV1, inVertices[ i + 1 ], plane, s ); + outVertices.push( nV1 ); + outVertices.push( nV2 ); + outVertices.push( nV3 ); + + } + + break; + + } + + case 3: { + + // the entire face lies outside of the plane, so let's discard the corresponding vertices + + break; + + } + + } + + } + + return outVertices; + + } + + function clip( v0, v1, p, s ) { + + const d0 = v0.position.dot( p ) - s; + const d1 = v1.position.dot( p ) - s; + + const s0 = d0 / ( d0 - d1 ); + + const v = new DecalVertex( + new Vector3( + v0.position.x + s0 * ( v1.position.x - v0.position.x ), + v0.position.y + s0 * ( v1.position.y - v0.position.y ), + v0.position.z + s0 * ( v1.position.z - v0.position.z ) + ), + new Vector3( + v0.normal.x + s0 * ( v1.normal.x - v0.normal.x ), + v0.normal.y + s0 * ( v1.normal.y - v0.normal.y ), + v0.normal.z + s0 * ( v1.normal.z - v0.normal.z ) + ) + ); + + // need to clip more values (texture coordinates)? do it this way: + // intersectpoint.value = a.value + s * ( b.value - a.value ); + + return v; + + } + + } + +} + +// helper + +class DecalVertex { + + constructor( position, normal ) { + + this.position = position; + this.normal = normal; + + } + + clone() { + + return new this.constructor( this.position.clone(), this.normal.clone() ); + + } + +} + +export { DecalGeometry, DecalVertex }; diff --git a/jsm/geometries/LightningStrike.js b/jsm/geometries/LightningStrike.js new file mode 100644 index 0000000..b8b3353 --- /dev/null +++ b/jsm/geometries/LightningStrike.js @@ -0,0 +1,1017 @@ +import { + BufferGeometry, + DynamicDrawUsage, + Float32BufferAttribute, + MathUtils, + Uint32BufferAttribute, + Vector3 +} from 'three'; +import { SimplexNoise } from '../math/SimplexNoise.js'; + +/** + * @fileoverview LightningStrike object for creating lightning strikes and voltaic arcs. + * + * + * Usage + * + * var myRay = new LightningStrike( paramsObject ); + * var myRayMesh = new THREE.Mesh( myRay, myMaterial ); + * scene.add( myRayMesh ); + * ... + * myRay.update( currentTime ); + * + * The "currentTime" can vary its rate, go forwards, backwards or even jump, but it cannot be negative. + * + * You should normally leave the ray position to (0, 0, 0). You should control it by changing the sourceOffset and destOffset parameters. + * + * + * LightningStrike parameters + * + * The paramsObject can contain any of the following parameters. + * + * Legend: + * 'LightningStrike' (also called 'ray'): An independent voltaic arc with its ramifications and defined with a set of parameters. + * 'Subray': A ramification of the ray. It is not a LightningStrike object. + * 'Segment': A linear segment piece of a subray. + * 'Leaf segment': A ray segment which cannot be smaller. + * + * + * The following parameters can be changed any time and if they vary smoothly, the ray form will also change smoothly: + * + * @param {Vector3} sourceOffset The point where the ray starts. + * + * @param {Vector3} destOffset The point where the ray ends. + * + * @param {double} timeScale The rate at wich the ray form changes in time. Default: 1 + * + * @param {double} roughness From 0 to 1. The higher the value, the more wrinkled is the ray. Default: 0.9 + * + * @param {double} straightness From 0 to 1. The higher the value, the more straight will be a subray path. Default: 0.7 + * + * @param {Vector3} up0 Ray 'up' direction at the ray starting point. Must be normalized. It should be perpendicular to the ray forward direction but it doesn't matter much. + * + * @param {Vector3} up1 Like the up0 parameter but at the end of the ray. Must be normalized. + * + * @param {double} radius0 Radius of the main ray trunk at the start point. Default: 1 + * + * @param {double} radius1 Radius of the main ray trunk at the end point. Default: 1 + * + * @param {double} radius0Factor The radius0 of a subray is this factor times the radius0 of its parent subray. Default: 0.5 + * + * @param {double} radius1Factor The radius1 of a subray is this factor times the radius1 of its parent subray. Default: 0.2 + * + * @param {minRadius} Minimum value a subray radius0 or radius1 can get. Default: 0.1 + * + * + * The following parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly: + * + * @param {boolean} isEternal If true the ray never extinguishes. Otherwise its life is controlled by the 'birthTime' and 'deathTime' parameters. Default: true if any of those two parameters is undefined. + * + * @param {double} birthTime The time at which the ray starts its life and begins propagating. Only if isEternal is false. Default: None. + * + * @param {double} deathTime The time at which the ray ends vanishing and its life. Only if isEternal is false. Default: None. + * + * @param {double} propagationTimeFactor From 0 to 1. Lifetime factor at which the ray ends propagating and enters the steady phase. For example, 0.1 means it is propagating 1/10 of its lifetime. Default: 0.1 + * + * @param {double} vanishingTimeFactor From 0 to 1. Lifetime factor at which the ray ends the steady phase and begins vanishing. For example, 0.9 means it is vanishing 1/10 of its lifetime. Default: 0.9 + * + * @param {double} subrayPeriod Subrays cycle periodically. This is their time period. Default: 4 + * + * @param {double} subrayDutyCycle From 0 to 1. This is the fraction of time a subray is active. Default: 0.6 + * + * + * These parameters cannot change after lightning creation: + * + * @param {integer} maxIterations: Greater than 0. The number of ray's leaf segments is 2**maxIterations. Default: 9 + * + * @param {boolean} isStatic Set to true only for rays which won't change over time and are not attached to moving objects (Rare case). It is used to set the vertex buffers non-dynamic. You can omit calling update() for these rays. + * + * @param {integer} ramification Greater than 0. Maximum number of child subrays a subray can have. Default: 5 + * + * @param {integer} maxSubrayRecursion Greater than 0. Maximum level of recursion (subray descendant generations). Default: 3 + * + * @param {double} recursionProbability From 0 to 1. The lower the value, the less chance each new generation of subrays has to generate new subrays. Default: 0.6 + * + * @param {boolean} generateUVs If true, the ray geometry will have uv coordinates generated. u runs along the ray, and v across its perimeter. Default: false. + * + * @param {Object} randomGenerator Set here your random number generator which will seed the SimplexNoise and other decisions during ray tree creation. + * It can be used to generate repeatable rays. For that, set also the noiseSeed parameter, and each ray created with that generator and seed pair will be identical in time. + * The randomGenerator parameter should be an object with a random() function similar to Math.random, but seedable. + * It must have also a getSeed() method, which returns the current seed, and a setSeed( seed ) method, which accepts as seed a fractional number from 0 to 1, as well as any other number. + * The default value is an internal generator for some uses and Math.random for others (It is non-repeatable even if noiseSeed is supplied) + * + * @param {double} noiseSeed Seed used to make repeatable rays (see the randomGenerator) + * + * @param {function} onDecideSubrayCreation Set this to change the callback which decides subray creation. You can look at the default callback in the code (createDefaultSubrayCreationCallbacks)for more info. + * + * @param {function} onSubrayCreation This is another callback, more simple than the previous one. It can be used to adapt the form of subrays or other parameters once a subray has been created and initialized. It is used in the examples to adapt subrays to a sphere or to a plane. + * + * +*/ + +class LightningStrike extends BufferGeometry { + + constructor( rayParameters = {} ) { + + super(); + + this.type = 'LightningStrike'; + + // Set parameters, and set undefined parameters to default values + 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; + + if ( this.rayParameters.isEternal || ( this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) ) { + + this.updateMesh( time ); + + if ( time < this.subrays[ 0 ].endPropagationTime ) { + + this.state = LightningStrike.RAY_PROPAGATING; + + } else if ( time > this.subrays[ 0 ].beginVanishingTime ) { + + this.state = LightningStrike.RAY_VANISHING; + + } else { + + this.state = LightningStrike.RAY_STEADY; + + } + + this.visible = true; + + } else { + + this.visible = false; + + if ( time < this.rayParameters.birthTime ) { + + this.state = LightningStrike.RAY_UNBORN; + + } else { + + this.state = LightningStrike.RAY_EXTINGUISHED; + + } + + } + + } + + init( rayParameters ) { + + // Init all the state from the parameters + + this.rayParameters = rayParameters; + + // These parameters cannot change after lightning creation: + + this.maxIterations = rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9; + rayParameters.maxIterations = this.maxIterations; + this.isStatic = rayParameters.isStatic !== undefined ? rayParameters.isStatic : false; + rayParameters.isStatic = this.isStatic; + this.ramification = rayParameters.ramification !== undefined ? Math.floor( rayParameters.ramification ) : 5; + rayParameters.ramification = this.ramification; + this.maxSubrayRecursion = rayParameters.maxSubrayRecursion !== undefined ? Math.floor( rayParameters.maxSubrayRecursion ) : 3; + rayParameters.maxSubrayRecursion = this.maxSubrayRecursion; + this.recursionProbability = rayParameters.recursionProbability !== undefined ? rayParameters.recursionProbability : 0.6; + rayParameters.recursionProbability = this.recursionProbability; + this.generateUVs = rayParameters.generateUVs !== undefined ? rayParameters.generateUVs : false; + rayParameters.generateUVs = this.generateUVs; + + // Random generator + if ( rayParameters.randomGenerator !== undefined ) { + + this.randomGenerator = rayParameters.randomGenerator; + this.seedGenerator = rayParameters.randomGenerator; + + if ( rayParameters.noiseSeed !== undefined ) { + + this.seedGenerator.setSeed( rayParameters.noiseSeed ); + + } + + } else { + + this.randomGenerator = LightningStrike.createRandomGenerator(); + this.seedGenerator = Math; + + } + + // Ray creation callbacks + if ( rayParameters.onDecideSubrayCreation !== undefined ) { + + this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation; + + } else { + + this.createDefaultSubrayCreationCallbacks(); + + if ( rayParameters.onSubrayCreation !== undefined ) { + + this.onSubrayCreation = rayParameters.onSubrayCreation; + + } + + } + + // Internal state + + this.state = LightningStrike.RAY_INITIALIZED; + + this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) ); + rayParameters.maxSubrays = this.maxSubrays; + + this.maxRaySegments = 2 * ( 1 << this.maxIterations ); + + this.subrays = []; + + for ( let i = 0; i < this.maxSubrays; i ++ ) { + + this.subrays.push( this.createSubray() ); + + } + + this.raySegments = []; + + for ( let i = 0; i < this.maxRaySegments; i ++ ) { + + this.raySegments.push( this.createSegment() ); + + } + + this.time = 0; + this.timeFraction = 0; + this.currentSegmentCallback = null; + this.currentCreateTriangleVertices = this.generateUVs ? this.createTriangleVerticesWithUVs : this.createTriangleVerticesWithoutUVs; + this.numSubrays = 0; + this.currentSubray = null; + this.currentSegmentIndex = 0; + this.isInitialSegment = false; + this.subrayProbability = 0; + + this.currentVertex = 0; + this.currentIndex = 0; + this.currentCoordinate = 0; + this.currentUVCoordinate = 0; + this.vertices = null; + this.uvs = null; + this.indices = null; + this.positionAttribute = null; + this.uvsAttribute = null; + + this.simplexX = new SimplexNoise( this.seedGenerator ); + this.simplexY = new SimplexNoise( this.seedGenerator ); + this.simplexZ = new SimplexNoise( this.seedGenerator ); + + // Temp vectors + this.forwards = new Vector3(); + this.forwardsFill = new Vector3(); + this.side = new Vector3(); + this.down = new Vector3(); + this.middlePos = new Vector3(); + this.middleLinPos = new Vector3(); + this.newPos = new Vector3(); + this.vPos = new Vector3(); + this.cross1 = new Vector3(); + + } + + createMesh() { + + const maxDrawableSegmentsPerSubRay = 1 << this.maxIterations; + + const maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays; + const maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays; + + this.vertices = new Float32Array( maxVerts * 3 ); + this.indices = new Uint32Array( maxIndices ); + + if ( this.generateUVs ) { + + this.uvs = new Float32Array( maxVerts * 2 ); + + } + + // Populate the mesh + this.fillMesh( 0 ); + + this.setIndex( new Uint32BufferAttribute( this.indices, 1 ) ); + + this.positionAttribute = new Float32BufferAttribute( this.vertices, 3 ); + this.setAttribute( 'position', this.positionAttribute ); + + if ( this.generateUVs ) { + + this.uvsAttribute = new Float32BufferAttribute( new Float32Array( this.uvs ), 2 ); + this.setAttribute( 'uv', this.uvsAttribute ); + + } + + if ( ! this.isStatic ) { + + this.index.usage = DynamicDrawUsage; + this.positionAttribute.usage = DynamicDrawUsage; + + if ( this.generateUVs ) { + + this.uvsAttribute.usage = DynamicDrawUsage; + + } + + } + + // Store buffers for later modification + this.vertices = this.positionAttribute.array; + this.indices = this.index.array; + + if ( this.generateUVs ) { + + this.uvs = this.uvsAttribute.array; + + } + + } + + updateMesh( time ) { + + this.fillMesh( time ); + + this.drawRange.count = this.currentIndex; + + this.index.needsUpdate = true; + + this.positionAttribute.needsUpdate = true; + + if ( this.generateUVs ) { + + this.uvsAttribute.needsUpdate = true; + + } + + } + + fillMesh( time ) { + + const scope = this; + + this.currentVertex = 0; + this.currentIndex = 0; + this.currentCoordinate = 0; + this.currentUVCoordinate = 0; + + this.fractalRay( time, function fillVertices( segment ) { + + const subray = scope.currentSubray; + + if ( time < subray.birthTime ) { //&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) { + + return; + + } else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) { + + // Eternal rays don't propagate nor vanish, but its subrays do + + scope.createPrism( segment ); + + scope.onDecideSubrayCreation( segment, scope ); + + } else if ( time < subray.endPropagationTime ) { + + if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) { + + // Ray propagation has arrived to this segment + + scope.createPrism( segment ); + + scope.onDecideSubrayCreation( segment, scope ); + + } + + } else if ( time < subray.beginVanishingTime ) { + + // Ray is steady (nor propagating nor vanishing) + + scope.createPrism( segment ); + + scope.onDecideSubrayCreation( segment, scope ); + + } else { + + if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) { + + // Segment has not yet vanished + + scope.createPrism( segment ); + + } + + scope.onDecideSubrayCreation( segment, scope ); + + } + + } ); + + } + + addNewSubray( /*rayParameters*/ ) { + + return this.subrays[ this.numSubrays ++ ]; + + } + + initSubray( subray, rayParameters ) { + + subray.pos0.copy( rayParameters.sourceOffset ); + subray.pos1.copy( rayParameters.destOffset ); + subray.up0.copy( rayParameters.up0 ); + subray.up1.copy( rayParameters.up1 ); + subray.radius0 = rayParameters.radius0; + subray.radius1 = rayParameters.radius1; + subray.birthTime = rayParameters.birthTime; + subray.deathTime = rayParameters.deathTime; + subray.timeScale = rayParameters.timeScale; + subray.roughness = rayParameters.roughness; + subray.straightness = rayParameters.straightness; + subray.propagationTimeFactor = rayParameters.propagationTimeFactor; + subray.vanishingTimeFactor = rayParameters.vanishingTimeFactor; + + subray.maxIterations = this.maxIterations; + subray.seed = rayParameters.noiseSeed !== undefined ? rayParameters.noiseSeed : 0; + subray.recursion = 0; + + } + + fractalRay( time, segmentCallback ) { + + this.time = time; + this.currentSegmentCallback = segmentCallback; + this.numSubrays = 0; + + // Add the top level subray + this.initSubray( this.addNewSubray(), this.rayParameters ); + + // Process all subrays that are being generated until consuming all of them + for ( let subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex ++ ) { + + const subray = this.subrays[ subrayIndex ]; + this.currentSubray = subray; + + this.randomGenerator.setSeed( subray.seed ); + + subray.endPropagationTime = MathUtils.lerp( subray.birthTime, subray.deathTime, subray.propagationTimeFactor ); + subray.beginVanishingTime = MathUtils.lerp( subray.deathTime, subray.birthTime, 1 - subray.vanishingTimeFactor ); + + const random1 = this.randomGenerator.random; + subray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + subray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + + this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime ); + + this.currentSegmentIndex = 0; + this.isInitialSegment = true; + + const segment = this.getNewSegment(); + segment.iteration = 0; + segment.pos0.copy( subray.pos0 ); + segment.pos1.copy( subray.pos1 ); + segment.linPos0.copy( subray.linPos0 ); + segment.linPos1.copy( subray.linPos1 ); + segment.up0.copy( subray.up0 ); + segment.up1.copy( subray.up1 ); + segment.radius0 = subray.radius0; + segment.radius1 = subray.radius1; + segment.fraction0 = 0; + segment.fraction1 = 1; + segment.positionVariationFactor = 1 - subray.straightness; + + this.subrayProbability = this.ramification * Math.pow( this.recursionProbability, subray.recursion ) / ( 1 << subray.maxIterations ); + + this.fractalRayRecursive( segment ); + + } + + this.currentSegmentCallback = null; + this.currentSubray = null; + + } + + fractalRayRecursive( segment ) { + + // Leave recursion condition + if ( segment.iteration >= this.currentSubray.maxIterations ) { + + this.currentSegmentCallback( segment ); + + return; + + } + + // Interpolation + this.forwards.subVectors( segment.pos1, segment.pos0 ); + let lForwards = this.forwards.length(); + + if ( lForwards < 0.000001 ) { + + this.forwards.set( 0, 0, 0.01 ); + lForwards = this.forwards.length(); + + } + + const middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5; + const middleFraction = ( segment.fraction0 + segment.fraction1 ) * 0.5; + + const timeDimension = this.time * this.currentSubray.timeScale * Math.pow( 2, segment.iteration ); + + this.middlePos.lerpVectors( segment.pos0, segment.pos1, 0.5 ); + this.middleLinPos.lerpVectors( segment.linPos0, segment.linPos1, 0.5 ); + const p = this.middleLinPos; + + // Noise + this.newPos.set( this.simplexX.noise4d( p.x, p.y, p.z, timeDimension ), + this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ), + this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) ); + + this.newPos.multiplyScalar( segment.positionVariationFactor * lForwards ); + this.newPos.add( this.middlePos ); + + // Recursion + + const newSegment1 = this.getNewSegment(); + newSegment1.pos0.copy( segment.pos0 ); + newSegment1.pos1.copy( this.newPos ); + newSegment1.linPos0.copy( segment.linPos0 ); + newSegment1.linPos1.copy( this.middleLinPos ); + newSegment1.up0.copy( segment.up0 ); + newSegment1.up1.copy( segment.up1 ); + newSegment1.radius0 = segment.radius0; + newSegment1.radius1 = middleRadius; + newSegment1.fraction0 = segment.fraction0; + newSegment1.fraction1 = middleFraction; + newSegment1.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; + newSegment1.iteration = segment.iteration + 1; + + const newSegment2 = this.getNewSegment(); + newSegment2.pos0.copy( this.newPos ); + newSegment2.pos1.copy( segment.pos1 ); + newSegment2.linPos0.copy( this.middleLinPos ); + newSegment2.linPos1.copy( segment.linPos1 ); + this.cross1.crossVectors( segment.up0, this.forwards.normalize() ); + newSegment2.up0.crossVectors( this.forwards, this.cross1 ).normalize(); + newSegment2.up1.copy( segment.up1 ); + newSegment2.radius0 = middleRadius; + newSegment2.radius1 = segment.radius1; + newSegment2.fraction0 = middleFraction; + newSegment2.fraction1 = segment.fraction1; + newSegment2.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness; + newSegment2.iteration = segment.iteration + 1; + + this.fractalRayRecursive( newSegment1 ); + + this.fractalRayRecursive( newSegment2 ); + + } + + createPrism( segment ) { + + // Creates one triangular prism and its vertices at the segment + + this.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize(); + + if ( this.isInitialSegment ) { + + this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 ); + + this.isInitialSegment = false; + + } + + this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 ); + + this.createPrismFaces(); + + } + + createTriangleVerticesWithoutUVs( pos, up, forwards, radius ) { + + // Create an equilateral triangle (only vertices) + + this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG ); + this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG ); + + const p = this.vPos; + const v = this.vertices; + + p.copy( pos ).sub( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + p.copy( pos ).add( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + p.copy( up ).multiplyScalar( radius ).add( pos ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + this.currentVertex += 3; + + } + + createTriangleVerticesWithUVs( pos, up, forwards, radius, u ) { + + // Create an equilateral triangle (only vertices) + + this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG ); + this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG ); + + const p = this.vPos; + const v = this.vertices; + const uv = this.uvs; + + p.copy( pos ).sub( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + uv[ this.currentUVCoordinate ++ ] = u; + uv[ this.currentUVCoordinate ++ ] = 0; + + p.copy( pos ).add( this.side ).add( this.down ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + uv[ this.currentUVCoordinate ++ ] = u; + uv[ this.currentUVCoordinate ++ ] = 0.5; + + p.copy( up ).multiplyScalar( radius ).add( pos ); + + v[ this.currentCoordinate ++ ] = p.x; + v[ this.currentCoordinate ++ ] = p.y; + v[ this.currentCoordinate ++ ] = p.z; + + uv[ this.currentUVCoordinate ++ ] = u; + uv[ this.currentUVCoordinate ++ ] = 1; + + this.currentVertex += 3; + + } + + createPrismFaces( vertex/*, index*/ ) { + + const indices = this.indices; + vertex = this.currentVertex - 6; + + indices[ this.currentIndex ++ ] = vertex + 1; + indices[ this.currentIndex ++ ] = vertex + 2; + indices[ this.currentIndex ++ ] = vertex + 5; + indices[ this.currentIndex ++ ] = vertex + 1; + indices[ this.currentIndex ++ ] = vertex + 5; + indices[ this.currentIndex ++ ] = vertex + 4; + indices[ this.currentIndex ++ ] = vertex + 0; + indices[ this.currentIndex ++ ] = vertex + 1; + indices[ this.currentIndex ++ ] = vertex + 4; + indices[ this.currentIndex ++ ] = vertex + 0; + indices[ this.currentIndex ++ ] = vertex + 4; + indices[ this.currentIndex ++ ] = vertex + 3; + indices[ this.currentIndex ++ ] = vertex + 2; + indices[ this.currentIndex ++ ] = vertex + 0; + indices[ this.currentIndex ++ ] = vertex + 3; + indices[ this.currentIndex ++ ] = vertex + 2; + indices[ this.currentIndex ++ ] = vertex + 3; + indices[ this.currentIndex ++ ] = vertex + 5; + + } + + createDefaultSubrayCreationCallbacks() { + + const random1 = this.randomGenerator.random; + + this.onDecideSubrayCreation = function ( segment, lightningStrike ) { + + // Decide subrays creation at parent (sub)ray segment + + const subray = lightningStrike.currentSubray; + + const period = lightningStrike.rayParameters.subrayPeriod; + const dutyCycle = lightningStrike.rayParameters.subrayDutyCycle; + + const phase0 = ( lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) ? - random1() * period : MathUtils.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period; + + const phase = lightningStrike.time - phase0; + const currentCycle = Math.floor( phase / period ); + + const childSubraySeed = random1() * ( currentCycle + 1 ); + + const isActive = phase % period <= dutyCycle * period; + + let probability = 0; + + if ( isActive ) { + + probability = lightningStrike.subrayProbability; + // Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0; + + } + + if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) { + + const childSubray = lightningStrike.addNewSubray(); + + const parentSeed = lightningStrike.randomGenerator.getSeed(); + childSubray.seed = childSubraySeed; + lightningStrike.randomGenerator.setSeed( childSubraySeed ); + + childSubray.recursion = subray.recursion + 1; + childSubray.maxIterations = Math.max( 1, subray.maxIterations - 1 ); + + childSubray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 ); + childSubray.up0.copy( subray.up0 ); + childSubray.up1.copy( subray.up1 ); + childSubray.radius0 = segment.radius0 * lightningStrike.rayParameters.radius0Factor; + childSubray.radius1 = Math.min( lightningStrike.rayParameters.minRadius, segment.radius1 * lightningStrike.rayParameters.radius1Factor ); + + childSubray.birthTime = phase0 + ( currentCycle ) * period; + childSubray.deathTime = childSubray.birthTime + period * dutyCycle; + + if ( ! lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) { + + childSubray.birthTime = Math.max( childSubray.birthTime, subray.birthTime ); + childSubray.deathTime = Math.min( childSubray.deathTime, subray.deathTime ); + + } + + childSubray.timeScale = subray.timeScale * 2; + childSubray.roughness = subray.roughness; + childSubray.straightness = subray.straightness; + childSubray.propagationTimeFactor = subray.propagationTimeFactor; + childSubray.vanishingTimeFactor = subray.vanishingTimeFactor; + + lightningStrike.onSubrayCreation( segment, subray, childSubray, lightningStrike ); + + lightningStrike.randomGenerator.setSeed( parentSeed ); + + } + + }; + + const vec1Pos = new Vector3(); + const vec2Forward = new Vector3(); + const vec3Side = new Vector3(); + const vec4Up = new Vector3(); + + 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 = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { + + // Sets childSubray pos0 and pos1 in a cone + + childSubray.pos0.copy( segment.pos0 ); + + vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); + vec2Forward.copy( vec1Pos ).normalize(); + vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( random1() * heightFactor ) ); + const length = vec1Pos.length(); + vec3Side.crossVectors( parentSubray.up0, vec2Forward ); + const angle = 2 * Math.PI * random1(); + vec3Side.multiplyScalar( Math.cos( angle ) ); + vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) ); + + childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); + + }; + + this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) { + + // Sets childSubray pos0 and pos1 in a cylinder + + childSubray.pos0.copy( segment.pos0 ); + + vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 ); + vec2Forward.copy( vec1Pos ).normalize(); + vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( ( 2 * random1() - 1 ) * heightFactor ) ); + const length = vec1Pos.length(); + vec3Side.crossVectors( parentSubray.up0, vec2Forward ); + const angle = 2 * Math.PI * random1(); + vec3Side.multiplyScalar( Math.cos( angle ) ); + vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) ); + + childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 ); + + }; + + } + + createSubray() { + + return { + + seed: 0, + maxIterations: 0, + recursion: 0, + pos0: new Vector3(), + pos1: new Vector3(), + linPos0: new Vector3(), + linPos1: new Vector3(), + up0: new Vector3(), + up1: new Vector3(), + radius0: 0, + radius1: 0, + birthTime: 0, + deathTime: 0, + timeScale: 0, + roughness: 0, + straightness: 0, + propagationTimeFactor: 0, + vanishingTimeFactor: 0, + endPropagationTime: 0, + beginVanishingTime: 0 + + }; + + } + + createSegment() { + + return { + iteration: 0, + pos0: new Vector3(), + pos1: new Vector3(), + linPos0: new Vector3(), + linPos1: new Vector3(), + up0: new Vector3(), + up1: new Vector3(), + radius0: 0, + radius1: 0, + fraction0: 0, + fraction1: 0, + positionVariationFactor: 0 + }; + + } + + getNewSegment() { + + return this.raySegments[ this.currentSegmentIndex ++ ]; + + } + + copy( source ) { + + super.copy( source ); + + this.init( LightningStrike.copyParameters( {}, source.rayParameters ) ); + + return this; + + } + + clone() { + + return new this.constructor( LightningStrike.copyParameters( {}, this.rayParameters ) ); + + } + +} + +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 ); + +export { LightningStrike }; diff --git a/jsm/geometries/ParametricGeometries.js b/jsm/geometries/ParametricGeometries.js new file mode 100644 index 0000000..6716735 --- /dev/null +++ b/jsm/geometries/ParametricGeometries.js @@ -0,0 +1,254 @@ +import { + Curve, + Vector3 +} from 'three'; + +import { ParametricGeometry } from './ParametricGeometry.js'; + +/** + * Experimenting of primitive geometry creation using Surface Parametric equations + */ + +const ParametricGeometries = { + + klein: function ( v, u, target ) { + + u *= Math.PI; + v *= 2 * Math.PI; + + u = u * 2; + let x, z; + if ( u < Math.PI ) { + + x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( u ) * Math.cos( v ); + z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v ); + + } else { + + x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( v + Math.PI ); + z = - 8 * Math.sin( u ); + + } + + const y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v ); + + target.set( x, y, z ); + + }, + + plane: function ( width, height ) { + + return function ( u, v, target ) { + + const x = u * width; + const y = 0; + const z = v * height; + + target.set( x, y, z ); + + }; + + }, + + mobius: function ( u, t, target ) { + + // flat mobius strip + // http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations- + u = u - 0.5; + const v = 2 * Math.PI * t; + + const a = 2; + + const x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) ); + const y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) ); + const z = u * Math.sin( v / 2 ); + + target.set( x, y, z ); + + }, + + mobius3d: function ( u, t, target ) { + + // volumetric mobius strip + + u *= Math.PI; + t *= 2 * Math.PI; + + u = u * 2; + const phi = u / 2; + const major = 2.25, a = 0.125, b = 0.65; + + let x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi ); + const z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi ); + const y = ( major + x ) * Math.sin( u ); + x = ( major + x ) * Math.cos( u ); + + 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(); + + 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; + + } + +}; + + +/********************************************* + * + * 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 ); + + return point.set( x, y, z ).multiplyScalar( radius ); + + } + + } + + 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 ); + + } + +}; + + +/********************************************* + * + * 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 ); + + } + +}; + +export { ParametricGeometries }; diff --git a/jsm/geometries/ParametricGeometry.js b/jsm/geometries/ParametricGeometry.js new file mode 100644 index 0000000..33166f8 --- /dev/null +++ b/jsm/geometries/ParametricGeometry.js @@ -0,0 +1,135 @@ +/** + * 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(); + + this.type = 'ParametricGeometry'; + + this.parameters = { + func: func, + slices: slices, + stacks: stacks + }; + + // buffers + + const indices = []; + const vertices = []; + const normals = []; + const uvs = []; + + const EPS = 0.00001; + + const normal = new Vector3(); + + const p0 = new Vector3(), p1 = new Vector3(); + const pu = new Vector3(), pv = new Vector3(); + + if ( func.length < 3 ) { + + console.error( 'THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.' ); + + } + + // generate vertices, normals and uvs + + const sliceCount = slices + 1; + + for ( let i = 0; i <= stacks; i ++ ) { + + const v = i / stacks; + + for ( let j = 0; j <= slices; j ++ ) { + + const u = j / slices; + + // vertex + + func( u, v, p0 ); + vertices.push( p0.x, p0.y, p0.z ); + + // normal + + // approximate tangent vectors via finite differences + + if ( u - EPS >= 0 ) { + + func( u - EPS, v, p1 ); + pu.subVectors( p0, p1 ); + + } else { + + func( u + EPS, v, p1 ); + pu.subVectors( p1, p0 ); + + } + + if ( v - EPS >= 0 ) { + + func( u, v - EPS, p1 ); + pv.subVectors( p0, p1 ); + + } else { + + func( u, v + EPS, p1 ); + pv.subVectors( p1, p0 ); + + } + + // cross product of tangent vectors returns surface normal + + normal.crossVectors( pu, pv ).normalize(); + normals.push( normal.x, normal.y, normal.z ); + + // uv + + uvs.push( u, v ); + + } + + } + + // generate indices + + for ( let i = 0; i < stacks; i ++ ) { + + for ( let j = 0; j < slices; j ++ ) { + + const a = i * sliceCount + j; + const b = i * sliceCount + j + 1; + const c = ( i + 1 ) * sliceCount + j + 1; + const d = ( i + 1 ) * sliceCount + j; + + // faces one and two + + indices.push( a, b, d ); + indices.push( b, c, d ); + + } + + } + + // build geometry + + this.setIndex( indices ); + this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + +} + +export { ParametricGeometry }; diff --git a/jsm/geometries/RoundedBoxGeometry.js b/jsm/geometries/RoundedBoxGeometry.js new file mode 100644 index 0000000..8baa168 --- /dev/null +++ b/jsm/geometries/RoundedBoxGeometry.js @@ -0,0 +1,155 @@ +import { + BoxGeometry, + Vector3 +} from 'three'; + +const _tempNormal = new Vector3(); + +function getUv( faceDirVector, normal, uvAxis, projectionAxis, radius, sideLength ) { + + const totArcLength = 2 * Math.PI * radius / 4; + + // length of the planes between the arcs on each axis + const centerLength = Math.max( sideLength - 2 * radius, 0 ); + const halfArc = Math.PI / 4; + + // Get the vector projected onto the Y plane + _tempNormal.copy( normal ); + _tempNormal[ projectionAxis ] = 0; + _tempNormal.normalize(); + + // total amount of UV space alloted to a single arc + const arcUvRatio = 0.5 * totArcLength / ( totArcLength + centerLength ); + + // the distance along one arc the point is at + const arcAngleRatio = 1.0 - ( _tempNormal.angleTo( faceDirVector ) / halfArc ); + + if ( Math.sign( _tempNormal[ uvAxis ] ) === 1 ) { + + return arcAngleRatio * arcUvRatio; + + } else { + + // total amount of UV space alloted to the plane between the arcs + const lenUv = centerLength / ( totArcLength + centerLength ); + return lenUv + arcUvRatio + arcUvRatio * ( 1.0 - arcAngleRatio ); + + } + +} + +class RoundedBoxGeometry extends BoxGeometry { + + constructor( width = 1, height = 1, depth = 1, segments = 2, radius = 0.1 ) { + + // ensure segments is odd so we have a plane connecting the rounded corners + segments = segments * 2 + 1; + + // ensure radius isn't bigger than shortest side + radius = Math.min( width / 2, height / 2, depth / 2, radius ); + + super( 1, 1, 1, segments, segments, segments ); + + // if we just have one segment we're the same as a regular box + if ( segments === 1 ) return; + + const geometry2 = this.toNonIndexed(); + + this.index = null; + this.attributes.position = geometry2.attributes.position; + this.attributes.normal = geometry2.attributes.normal; + this.attributes.uv = geometry2.attributes.uv; + + // + + const position = new Vector3(); + const normal = new Vector3(); + + const box = new Vector3( width, height, depth ).divideScalar( 2 ).subScalar( radius ); + + const positions = this.attributes.position.array; + const normals = this.attributes.normal.array; + const uvs = this.attributes.uv.array; + + const faceTris = positions.length / 6; + const faceDirVector = new Vector3(); + const halfSegmentSize = 0.5 / segments; + + for ( let i = 0, j = 0; i < positions.length; i += 3, j += 2 ) { + + position.fromArray( positions, i ); + normal.copy( position ); + normal.x -= Math.sign( normal.x ) * halfSegmentSize; + normal.y -= Math.sign( normal.y ) * halfSegmentSize; + normal.z -= Math.sign( normal.z ) * halfSegmentSize; + normal.normalize(); + + positions[ i + 0 ] = box.x * Math.sign( position.x ) + normal.x * radius; + positions[ i + 1 ] = box.y * Math.sign( position.y ) + normal.y * radius; + positions[ i + 2 ] = box.z * Math.sign( position.z ) + normal.z * radius; + + normals[ i + 0 ] = normal.x; + normals[ i + 1 ] = normal.y; + normals[ i + 2 ] = normal.z; + + const side = Math.floor( i / faceTris ); + + switch ( side ) { + + case 0: // right + + // generate UVs along Z then Y + faceDirVector.set( 1, 0, 0 ); + uvs[ j + 0 ] = getUv( faceDirVector, normal, 'z', 'y', radius, depth ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height ); + break; + + case 1: // left + + // generate UVs along Z then Y + faceDirVector.set( - 1, 0, 0 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'y', radius, depth ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height ); + break; + + case 2: // top + + // generate UVs along X then Z + faceDirVector.set( 0, 1, 0 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width ); + uvs[ j + 1 ] = getUv( faceDirVector, normal, 'z', 'x', radius, depth ); + break; + + case 3: // bottom + + // generate UVs along X then Z + faceDirVector.set( 0, - 1, 0 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'x', radius, depth ); + break; + + case 4: // front + + // generate UVs along X then Y + faceDirVector.set( 0, 0, 1 ); + uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'y', radius, width ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height ); + break; + + case 5: // back + + // generate UVs along X then Y + faceDirVector.set( 0, 0, - 1 ); + uvs[ j + 0 ] = getUv( faceDirVector, normal, 'x', 'y', radius, width ); + uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height ); + break; + + } + + } + + } + +} + +export { RoundedBoxGeometry }; diff --git a/jsm/geometries/TeapotGeometry.js b/jsm/geometries/TeapotGeometry.js new file mode 100644 index 0000000..b6b5ff1 --- /dev/null +++ b/jsm/geometries/TeapotGeometry.js @@ -0,0 +1,704 @@ +import { + BufferAttribute, + BufferGeometry, + Matrix4, + Vector3, + Vector4 +} from 'three'; + +/** + * Tessellates the famous Utah teapot database by Martin Newell into triangles. + * + * Parameters: size = 50, segments = 10, bottom = true, lid = true, body = true, + * fitLid = false, blinn = true + * + * size is a relative scale: I've scaled the teapot to fit vertically between -1 and 1. + * Think of it as a "radius". + * segments - number of line segments to subdivide each patch edge; + * 1 is possible but gives degenerates, so two is the real minimum. + * bottom - boolean, if true (default) then the bottom patches are added. Some consider + * adding the bottom heresy, so set this to "false" to adhere to the One True Way. + * lid - to remove the lid and look inside, set to true. + * body - to remove the body and leave the lid, set this and "bottom" to false. + * fitLid - the lid is a tad small in the original. This stretches it a bit so you can't + * see the teapot's insides through the gap. + * blinn - Jim Blinn scaled the original data vertically by dividing by about 1.3 to look + * nicer. If you want to see the original teapot, similar to the real-world model, set + * this to false. True by default. + * See http://en.wikipedia.org/wiki/File:Original_Utah_Teapot.jpg for the original + * real-world teapot (from http://en.wikipedia.org/wiki/Utah_teapot). + * + * Note that the bottom (the last four patches) is not flat - blame Frank Crow, not me. + * + * The teapot should normally be rendered as a double sided object, since for some + * patches both sides can be seen, e.g., the gap around the lid and inside the spout. + * + * Segments 'n' determines the number of triangles output. + * Total triangles = 32*2*n*n - 8*n [degenerates at the top and bottom cusps are deleted] + * + * size_factor # triangles + * 1 56 + * 2 240 + * 3 552 + * 4 992 + * + * 10 6320 + * 20 25440 + * 30 57360 + * + * Code converted from my ancient SPD software, http://tog.acm.org/resources/SPD/ + * Created for the Udacity course "Interactive Rendering", http://bit.ly/ericity + * YouTube video on teapot history: https://www.youtube.com/watch?v=DxMfblPzFNc + * + * See https://en.wikipedia.org/wiki/Utah_teapot for the history of the teapot + * + */ + +class TeapotGeometry extends BufferGeometry { + + constructor( size = 50, segments = 10, bottom = true, lid = true, body = true, fitLid = true, blinn = true ) { + + // 32 * 4 * 4 Bezier spline patches + const teapotPatches = [ + /*rim*/ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 3, 16, 17, 18, 7, 19, 20, 21, 11, 22, 23, 24, 15, 25, 26, 27, + 18, 28, 29, 30, 21, 31, 32, 33, 24, 34, 35, 36, 27, 37, 38, 39, + 30, 40, 41, 0, 33, 42, 43, 4, 36, 44, 45, 8, 39, 46, 47, 12, + /*body*/ + 12, 13, 14, 15, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, + 15, 25, 26, 27, 51, 60, 61, 62, 55, 63, 64, 65, 59, 66, 67, 68, + 27, 37, 38, 39, 62, 69, 70, 71, 65, 72, 73, 74, 68, 75, 76, 77, + 39, 46, 47, 12, 71, 78, 79, 48, 74, 80, 81, 52, 77, 82, 83, 56, + 56, 57, 58, 59, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 59, 66, 67, 68, 87, 96, 97, 98, 91, 99, 100, 101, 95, 102, 103, 104, + 68, 75, 76, 77, 98, 105, 106, 107, 101, 108, 109, 110, 104, 111, 112, 113, + 77, 82, 83, 56, 107, 114, 115, 84, 110, 116, 117, 88, 113, 118, 119, 92, + /*handle*/ + 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, + 123, 136, 137, 120, 127, 138, 139, 124, 131, 140, 141, 128, 135, 142, 143, 132, + 132, 133, 134, 135, 144, 145, 146, 147, 148, 149, 150, 151, 68, 152, 153, 154, + 135, 142, 143, 132, 147, 155, 156, 144, 151, 157, 158, 148, 154, 159, 160, 68, + /*spout*/ + 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, + 164, 177, 178, 161, 168, 179, 180, 165, 172, 181, 182, 169, 176, 183, 184, 173, + 173, 174, 175, 176, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, + 176, 183, 184, 173, 188, 197, 198, 185, 192, 199, 200, 189, 196, 201, 202, 193, + /*lid*/ + 203, 203, 203, 203, 204, 205, 206, 207, 208, 208, 208, 208, 209, 210, 211, 212, + 203, 203, 203, 203, 207, 213, 214, 215, 208, 208, 208, 208, 212, 216, 217, 218, + 203, 203, 203, 203, 215, 219, 220, 221, 208, 208, 208, 208, 218, 222, 223, 224, + 203, 203, 203, 203, 221, 225, 226, 204, 208, 208, 208, 208, 224, 227, 228, 209, + 209, 210, 211, 212, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, + 212, 216, 217, 218, 232, 241, 242, 243, 236, 244, 245, 246, 240, 247, 248, 249, + 218, 222, 223, 224, 243, 250, 251, 252, 246, 253, 254, 255, 249, 256, 257, 258, + 224, 227, 228, 209, 252, 259, 260, 229, 255, 261, 262, 233, 258, 263, 264, 237, + /*bottom*/ + 265, 265, 265, 265, 266, 267, 268, 269, 270, 271, 272, 273, 92, 119, 118, 113, + 265, 265, 265, 265, 269, 274, 275, 276, 273, 277, 278, 279, 113, 112, 111, 104, + 265, 265, 265, 265, 276, 280, 281, 282, 279, 283, 284, 285, 104, 103, 102, 95, + 265, 265, 265, 265, 282, 286, 287, 266, 285, 288, 289, 270, 95, 94, 93, 92 + ]; + + const teapotVertices = [ + 1.4, 0, 2.4, + 1.4, - 0.784, 2.4, + 0.784, - 1.4, 2.4, + 0, - 1.4, 2.4, + 1.3375, 0, 2.53125, + 1.3375, - 0.749, 2.53125, + 0.749, - 1.3375, 2.53125, + 0, - 1.3375, 2.53125, + 1.4375, 0, 2.53125, + 1.4375, - 0.805, 2.53125, + 0.805, - 1.4375, 2.53125, + 0, - 1.4375, 2.53125, + 1.5, 0, 2.4, + 1.5, - 0.84, 2.4, + 0.84, - 1.5, 2.4, + 0, - 1.5, 2.4, + - 0.784, - 1.4, 2.4, + - 1.4, - 0.784, 2.4, + - 1.4, 0, 2.4, + - 0.749, - 1.3375, 2.53125, + - 1.3375, - 0.749, 2.53125, + - 1.3375, 0, 2.53125, + - 0.805, - 1.4375, 2.53125, + - 1.4375, - 0.805, 2.53125, + - 1.4375, 0, 2.53125, + - 0.84, - 1.5, 2.4, + - 1.5, - 0.84, 2.4, + - 1.5, 0, 2.4, + - 1.4, 0.784, 2.4, + - 0.784, 1.4, 2.4, + 0, 1.4, 2.4, + - 1.3375, 0.749, 2.53125, + - 0.749, 1.3375, 2.53125, + 0, 1.3375, 2.53125, + - 1.4375, 0.805, 2.53125, + - 0.805, 1.4375, 2.53125, + 0, 1.4375, 2.53125, + - 1.5, 0.84, 2.4, + - 0.84, 1.5, 2.4, + 0, 1.5, 2.4, + 0.784, 1.4, 2.4, + 1.4, 0.784, 2.4, + 0.749, 1.3375, 2.53125, + 1.3375, 0.749, 2.53125, + 0.805, 1.4375, 2.53125, + 1.4375, 0.805, 2.53125, + 0.84, 1.5, 2.4, + 1.5, 0.84, 2.4, + 1.75, 0, 1.875, + 1.75, - 0.98, 1.875, + 0.98, - 1.75, 1.875, + 0, - 1.75, 1.875, + 2, 0, 1.35, + 2, - 1.12, 1.35, + 1.12, - 2, 1.35, + 0, - 2, 1.35, + 2, 0, 0.9, + 2, - 1.12, 0.9, + 1.12, - 2, 0.9, + 0, - 2, 0.9, + - 0.98, - 1.75, 1.875, + - 1.75, - 0.98, 1.875, + - 1.75, 0, 1.875, + - 1.12, - 2, 1.35, + - 2, - 1.12, 1.35, + - 2, 0, 1.35, + - 1.12, - 2, 0.9, + - 2, - 1.12, 0.9, + - 2, 0, 0.9, + - 1.75, 0.98, 1.875, + - 0.98, 1.75, 1.875, + 0, 1.75, 1.875, + - 2, 1.12, 1.35, + - 1.12, 2, 1.35, + 0, 2, 1.35, + - 2, 1.12, 0.9, + - 1.12, 2, 0.9, + 0, 2, 0.9, + 0.98, 1.75, 1.875, + 1.75, 0.98, 1.875, + 1.12, 2, 1.35, + 2, 1.12, 1.35, + 1.12, 2, 0.9, + 2, 1.12, 0.9, + 2, 0, 0.45, + 2, - 1.12, 0.45, + 1.12, - 2, 0.45, + 0, - 2, 0.45, + 1.5, 0, 0.225, + 1.5, - 0.84, 0.225, + 0.84, - 1.5, 0.225, + 0, - 1.5, 0.225, + 1.5, 0, 0.15, + 1.5, - 0.84, 0.15, + 0.84, - 1.5, 0.15, + 0, - 1.5, 0.15, + - 1.12, - 2, 0.45, + - 2, - 1.12, 0.45, + - 2, 0, 0.45, + - 0.84, - 1.5, 0.225, + - 1.5, - 0.84, 0.225, + - 1.5, 0, 0.225, + - 0.84, - 1.5, 0.15, + - 1.5, - 0.84, 0.15, + - 1.5, 0, 0.15, + - 2, 1.12, 0.45, + - 1.12, 2, 0.45, + 0, 2, 0.45, + - 1.5, 0.84, 0.225, + - 0.84, 1.5, 0.225, + 0, 1.5, 0.225, + - 1.5, 0.84, 0.15, + - 0.84, 1.5, 0.15, + 0, 1.5, 0.15, + 1.12, 2, 0.45, + 2, 1.12, 0.45, + 0.84, 1.5, 0.225, + 1.5, 0.84, 0.225, + 0.84, 1.5, 0.15, + 1.5, 0.84, 0.15, + - 1.6, 0, 2.025, + - 1.6, - 0.3, 2.025, + - 1.5, - 0.3, 2.25, + - 1.5, 0, 2.25, + - 2.3, 0, 2.025, + - 2.3, - 0.3, 2.025, + - 2.5, - 0.3, 2.25, + - 2.5, 0, 2.25, + - 2.7, 0, 2.025, + - 2.7, - 0.3, 2.025, + - 3, - 0.3, 2.25, + - 3, 0, 2.25, + - 2.7, 0, 1.8, + - 2.7, - 0.3, 1.8, + - 3, - 0.3, 1.8, + - 3, 0, 1.8, + - 1.5, 0.3, 2.25, + - 1.6, 0.3, 2.025, + - 2.5, 0.3, 2.25, + - 2.3, 0.3, 2.025, + - 3, 0.3, 2.25, + - 2.7, 0.3, 2.025, + - 3, 0.3, 1.8, + - 2.7, 0.3, 1.8, + - 2.7, 0, 1.575, + - 2.7, - 0.3, 1.575, + - 3, - 0.3, 1.35, + - 3, 0, 1.35, + - 2.5, 0, 1.125, + - 2.5, - 0.3, 1.125, + - 2.65, - 0.3, 0.9375, + - 2.65, 0, 0.9375, + - 2, - 0.3, 0.9, + - 1.9, - 0.3, 0.6, + - 1.9, 0, 0.6, + - 3, 0.3, 1.35, + - 2.7, 0.3, 1.575, + - 2.65, 0.3, 0.9375, + - 2.5, 0.3, 1.125, + - 1.9, 0.3, 0.6, + - 2, 0.3, 0.9, + 1.7, 0, 1.425, + 1.7, - 0.66, 1.425, + 1.7, - 0.66, 0.6, + 1.7, 0, 0.6, + 2.6, 0, 1.425, + 2.6, - 0.66, 1.425, + 3.1, - 0.66, 0.825, + 3.1, 0, 0.825, + 2.3, 0, 2.1, + 2.3, - 0.25, 2.1, + 2.4, - 0.25, 2.025, + 2.4, 0, 2.025, + 2.7, 0, 2.4, + 2.7, - 0.25, 2.4, + 3.3, - 0.25, 2.4, + 3.3, 0, 2.4, + 1.7, 0.66, 0.6, + 1.7, 0.66, 1.425, + 3.1, 0.66, 0.825, + 2.6, 0.66, 1.425, + 2.4, 0.25, 2.025, + 2.3, 0.25, 2.1, + 3.3, 0.25, 2.4, + 2.7, 0.25, 2.4, + 2.8, 0, 2.475, + 2.8, - 0.25, 2.475, + 3.525, - 0.25, 2.49375, + 3.525, 0, 2.49375, + 2.9, 0, 2.475, + 2.9, - 0.15, 2.475, + 3.45, - 0.15, 2.5125, + 3.45, 0, 2.5125, + 2.8, 0, 2.4, + 2.8, - 0.15, 2.4, + 3.2, - 0.15, 2.4, + 3.2, 0, 2.4, + 3.525, 0.25, 2.49375, + 2.8, 0.25, 2.475, + 3.45, 0.15, 2.5125, + 2.9, 0.15, 2.475, + 3.2, 0.15, 2.4, + 2.8, 0.15, 2.4, + 0, 0, 3.15, + 0.8, 0, 3.15, + 0.8, - 0.45, 3.15, + 0.45, - 0.8, 3.15, + 0, - 0.8, 3.15, + 0, 0, 2.85, + 0.2, 0, 2.7, + 0.2, - 0.112, 2.7, + 0.112, - 0.2, 2.7, + 0, - 0.2, 2.7, + - 0.45, - 0.8, 3.15, + - 0.8, - 0.45, 3.15, + - 0.8, 0, 3.15, + - 0.112, - 0.2, 2.7, + - 0.2, - 0.112, 2.7, + - 0.2, 0, 2.7, + - 0.8, 0.45, 3.15, + - 0.45, 0.8, 3.15, + 0, 0.8, 3.15, + - 0.2, 0.112, 2.7, + - 0.112, 0.2, 2.7, + 0, 0.2, 2.7, + 0.45, 0.8, 3.15, + 0.8, 0.45, 3.15, + 0.112, 0.2, 2.7, + 0.2, 0.112, 2.7, + 0.4, 0, 2.55, + 0.4, - 0.224, 2.55, + 0.224, - 0.4, 2.55, + 0, - 0.4, 2.55, + 1.3, 0, 2.55, + 1.3, - 0.728, 2.55, + 0.728, - 1.3, 2.55, + 0, - 1.3, 2.55, + 1.3, 0, 2.4, + 1.3, - 0.728, 2.4, + 0.728, - 1.3, 2.4, + 0, - 1.3, 2.4, + - 0.224, - 0.4, 2.55, + - 0.4, - 0.224, 2.55, + - 0.4, 0, 2.55, + - 0.728, - 1.3, 2.55, + - 1.3, - 0.728, 2.55, + - 1.3, 0, 2.55, + - 0.728, - 1.3, 2.4, + - 1.3, - 0.728, 2.4, + - 1.3, 0, 2.4, + - 0.4, 0.224, 2.55, + - 0.224, 0.4, 2.55, + 0, 0.4, 2.55, + - 1.3, 0.728, 2.55, + - 0.728, 1.3, 2.55, + 0, 1.3, 2.55, + - 1.3, 0.728, 2.4, + - 0.728, 1.3, 2.4, + 0, 1.3, 2.4, + 0.224, 0.4, 2.55, + 0.4, 0.224, 2.55, + 0.728, 1.3, 2.55, + 1.3, 0.728, 2.55, + 0.728, 1.3, 2.4, + 1.3, 0.728, 2.4, + 0, 0, 0, + 1.425, 0, 0, + 1.425, 0.798, 0, + 0.798, 1.425, 0, + 0, 1.425, 0, + 1.5, 0, 0.075, + 1.5, 0.84, 0.075, + 0.84, 1.5, 0.075, + 0, 1.5, 0.075, + - 0.798, 1.425, 0, + - 1.425, 0.798, 0, + - 1.425, 0, 0, + - 0.84, 1.5, 0.075, + - 1.5, 0.84, 0.075, + - 1.5, 0, 0.075, + - 1.425, - 0.798, 0, + - 0.798, - 1.425, 0, + 0, - 1.425, 0, + - 1.5, - 0.84, 0.075, + - 0.84, - 1.5, 0.075, + 0, - 1.5, 0.075, + 0.798, - 1.425, 0, + 1.425, - 0.798, 0, + 0.84, - 1.5, 0.075, + 1.5, - 0.84, 0.075 + ]; + + super(); + + // number of segments per patch + segments = Math.max( 2, Math.floor( segments ) ); + + // Jim Blinn scaled the teapot down in size by about 1.3 for + // some rendering tests. He liked the new proportions that he kept + // the data in this form. The model was distributed with these new + // proportions and became the norm. Trivia: comparing images of the + // real teapot and the computer model, the ratio for the bowl of the + // real teapot is more like 1.25, but since 1.3 is the traditional + // value given, we use it here. + const blinnScale = 1.3; + + // scale the size to be the real scaling factor + const maxHeight = 3.15 * ( blinn ? 1 : blinnScale ); + + const maxHeight2 = maxHeight / 2; + const trueSize = size / maxHeight2; + + // Number of elements depends on what is needed. Subtract degenerate + // triangles at tip of bottom and lid out in advance. + let numTriangles = bottom ? ( 8 * segments - 4 ) * segments : 0; + numTriangles += lid ? ( 16 * segments - 4 ) * segments : 0; + numTriangles += body ? 40 * segments * segments : 0; + + const indices = new Uint32Array( numTriangles * 3 ); + + let numVertices = bottom ? 4 : 0; + numVertices += lid ? 8 : 0; + numVertices += body ? 20 : 0; + numVertices *= ( segments + 1 ) * ( segments + 1 ); + + const vertices = new Float32Array( numVertices * 3 ); + const normals = new Float32Array( numVertices * 3 ); + const uvs = new Float32Array( numVertices * 2 ); + + // Bezier form + const ms = new Matrix4(); + ms.set( + - 1.0, 3.0, - 3.0, 1.0, + 3.0, - 6.0, 3.0, 0.0, + - 3.0, 3.0, 0.0, 0.0, + 1.0, 0.0, 0.0, 0.0 ); + + const g = []; + + const sp = []; + const tp = []; + const dsp = []; + const dtp = []; + + // M * G * M matrix, sort of see + // http://www.cs.helsinki.fi/group/goa/mallinnus/curves/surfaces.html + const mgm = []; + + const vert = []; + const sdir = []; + const tdir = []; + + const norm = new Vector3(); + + let tcoord; + + let sval; + let tval; + let p; + let dsval = 0; + let dtval = 0; + + const normOut = new Vector3(); + + const gmx = new Matrix4(); + const tmtx = new Matrix4(); + + const vsp = new Vector4(); + const vtp = new Vector4(); + const vdsp = new Vector4(); + const vdtp = new Vector4(); + + const vsdir = new Vector3(); + const vtdir = new Vector3(); + + const mst = ms.clone(); + mst.transpose(); + + // internal function: test if triangle has any matching vertices; + // if so, don't save triangle, since it won't display anything. + const notDegenerate = ( vtx1, vtx2, vtx3 ) => // if any vertex matches, return false + ! ( ( ( vertices[ vtx1 * 3 ] === vertices[ vtx2 * 3 ] ) && + ( vertices[ vtx1 * 3 + 1 ] === vertices[ vtx2 * 3 + 1 ] ) && + ( vertices[ vtx1 * 3 + 2 ] === vertices[ vtx2 * 3 + 2 ] ) ) || + ( ( vertices[ vtx1 * 3 ] === vertices[ vtx3 * 3 ] ) && + ( vertices[ vtx1 * 3 + 1 ] === vertices[ vtx3 * 3 + 1 ] ) && + ( vertices[ vtx1 * 3 + 2 ] === vertices[ vtx3 * 3 + 2 ] ) ) || ( vertices[ vtx2 * 3 ] === vertices[ vtx3 * 3 ] ) && + ( vertices[ vtx2 * 3 + 1 ] === vertices[ vtx3 * 3 + 1 ] ) && + ( vertices[ vtx2 * 3 + 2 ] === vertices[ vtx3 * 3 + 2 ] ) ); + + + for ( let i = 0; i < 3; i ++ ) { + + mgm[ i ] = new Matrix4(); + + } + + const minPatches = body ? 0 : 20; + const maxPatches = bottom ? 32 : 28; + + const vertPerRow = segments + 1; + + let surfCount = 0; + + let vertCount = 0; + let normCount = 0; + let uvCount = 0; + + let indexCount = 0; + + for ( let surf = minPatches; surf < maxPatches; surf ++ ) { + + // lid is in the middle of the data, patches 20-27, + // so ignore it for this part of the loop if the lid is not desired + if ( lid || ( surf < 20 || surf >= 28 ) ) { + + // get M * G * M matrix for x,y,z + for ( let i = 0; i < 3; i ++ ) { + + // get control patches + for ( let r = 0; r < 4; r ++ ) { + + for ( let c = 0; c < 4; c ++ ) { + + // transposed + g[ c * 4 + r ] = teapotVertices[ teapotPatches[ surf * 16 + r * 4 + c ] * 3 + i ]; + + // is the lid to be made larger, and is this a point on the lid + // that is X or Y? + if ( fitLid && ( surf >= 20 && surf < 28 ) && ( i !== 2 ) ) { + + // increase XY size by 7.7%, found empirically. I don't + // increase Z so that the teapot will continue to fit in the + // space -1 to 1 for Y (Y is up for the final model). + g[ c * 4 + r ] *= 1.077; + + } + + // Blinn "fixed" the teapot by dividing Z by blinnScale, and that's the + // data we now use. The original teapot is taller. Fix it: + if ( ! blinn && ( i === 2 ) ) { + + g[ c * 4 + r ] *= blinnScale; + + } + + } + + } + + gmx.set( g[ 0 ], g[ 1 ], g[ 2 ], g[ 3 ], g[ 4 ], g[ 5 ], g[ 6 ], g[ 7 ], g[ 8 ], g[ 9 ], g[ 10 ], g[ 11 ], g[ 12 ], g[ 13 ], g[ 14 ], g[ 15 ] ); + + tmtx.multiplyMatrices( gmx, ms ); + mgm[ i ].multiplyMatrices( mst, tmtx ); + + } + + // step along, get points, and output + for ( let sstep = 0; sstep <= segments; sstep ++ ) { + + const s = sstep / segments; + + for ( let tstep = 0; tstep <= segments; tstep ++ ) { + + const t = tstep / segments; + + // point from basis + // get power vectors and their derivatives + for ( p = 4, sval = tval = 1.0; p --; ) { + + sp[ p ] = sval; + tp[ p ] = tval; + sval *= s; + tval *= t; + + if ( p === 3 ) { + + dsp[ p ] = dtp[ p ] = 0.0; + dsval = dtval = 1.0; + + } else { + + dsp[ p ] = dsval * ( 3 - p ); + dtp[ p ] = dtval * ( 3 - p ); + dsval *= s; + dtval *= t; + + } + + } + + vsp.fromArray( sp ); + vtp.fromArray( tp ); + vdsp.fromArray( dsp ); + vdtp.fromArray( dtp ); + + // do for x,y,z + for ( let i = 0; i < 3; i ++ ) { + + // multiply power vectors times matrix to get value + tcoord = vsp.clone(); + tcoord.applyMatrix4( mgm[ i ] ); + vert[ i ] = tcoord.dot( vtp ); + + // get s and t tangent vectors + tcoord = vdsp.clone(); + tcoord.applyMatrix4( mgm[ i ] ); + sdir[ i ] = tcoord.dot( vtp ); + + tcoord = vsp.clone(); + tcoord.applyMatrix4( mgm[ i ] ); + tdir[ i ] = tcoord.dot( vdtp ); + + } + + // find normal + vsdir.fromArray( sdir ); + vtdir.fromArray( tdir ); + norm.crossVectors( vtdir, vsdir ); + norm.normalize(); + + // if X and Z length is 0, at the cusp, so point the normal up or down, depending on patch number + if ( vert[ 0 ] === 0 && vert[ 1 ] === 0 ) { + + // if above the middle of the teapot, normal points up, else down + normOut.set( 0, vert[ 2 ] > maxHeight2 ? 1 : - 1, 0 ); + + } else { + + // standard output: rotate on X axis + normOut.set( norm.x, norm.z, - norm.y ); + + } + + // store it all + vertices[ vertCount ++ ] = trueSize * vert[ 0 ]; + vertices[ vertCount ++ ] = trueSize * ( vert[ 2 ] - maxHeight2 ); + vertices[ vertCount ++ ] = - trueSize * vert[ 1 ]; + + normals[ normCount ++ ] = normOut.x; + normals[ normCount ++ ] = normOut.y; + normals[ normCount ++ ] = normOut.z; + + uvs[ uvCount ++ ] = 1 - t; + uvs[ uvCount ++ ] = 1 - s; + + } + + } + + // save the faces + for ( let sstep = 0; sstep < segments; sstep ++ ) { + + for ( let tstep = 0; tstep < segments; tstep ++ ) { + + const v1 = surfCount * vertPerRow * vertPerRow + sstep * vertPerRow + tstep; + const v2 = v1 + 1; + const v3 = v2 + vertPerRow; + const v4 = v1 + vertPerRow; + + // Normals and UVs cannot be shared. Without clone(), you can see the consequences + // of sharing if you call geometry.applyMatrix4( matrix ). + if ( notDegenerate( v1, v2, v3 ) ) { + + indices[ indexCount ++ ] = v1; + indices[ indexCount ++ ] = v2; + indices[ indexCount ++ ] = v3; + + } + + if ( notDegenerate( v1, v3, v4 ) ) { + + indices[ indexCount ++ ] = v1; + indices[ indexCount ++ ] = v3; + indices[ indexCount ++ ] = v4; + + } + + } + + } + + // increment only if a surface was used + surfCount ++; + + } + + } + + this.setIndex( new BufferAttribute( indices, 1 ) ); + this.setAttribute( 'position', new BufferAttribute( vertices, 3 ) ); + this.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); + this.setAttribute( 'uv', new BufferAttribute( uvs, 2 ) ); + + this.computeBoundingSphere(); + + } + +} + +export { TeapotGeometry }; diff --git a/jsm/geometries/TextGeometry.js b/jsm/geometries/TextGeometry.js new file mode 100644 index 0000000..5275c03 --- /dev/null +++ b/jsm/geometries/TextGeometry.js @@ -0,0 +1,57 @@ +/** + * Text = 3D Text + * + * parameters = { + * font: , // font + * + * size: , // size of the text + * height: , // thickness to extrude text + * curveSegments: , // number of points on the curves + * + * bevelEnabled: , // turn on bevel + * bevelThickness: , // how deep into text bevel goes + * bevelSize: , // how far from text outline (including bevelOffset) is bevel + * bevelOffset: // how far from text outline does bevel start + * } + */ + +import { + ExtrudeGeometry +} from 'three'; + +class TextGeometry extends ExtrudeGeometry { + + constructor( text, parameters = {} ) { + + const font = parameters.font; + + if ( font === undefined ) { + + super(); // generate default extrude geometry + + } else { + + const shapes = font.generateShapes( text, parameters.size ); + + // translate parameters to ExtrudeGeometry API + + parameters.depth = parameters.height !== undefined ? parameters.height : 50; + + // defaults + + if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10; + if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8; + if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false; + + super( shapes, parameters ); + + } + + this.type = 'TextGeometry'; + + } + +} + + +export { TextGeometry }; diff --git a/jsm/helpers/LightProbeHelper.js b/jsm/helpers/LightProbeHelper.js new file mode 100644 index 0000000..8120488 --- /dev/null +++ b/jsm/helpers/LightProbeHelper.js @@ -0,0 +1,130 @@ +import { + Mesh, + ShaderMaterial, + SphereGeometry +} from 'three'; + +class LightProbeHelper extends Mesh { + + constructor( lightProbe, size ) { + + const material = new ShaderMaterial( { + + type: 'LightProbeHelperMaterial', + + uniforms: { + + sh: { value: lightProbe.sh.coefficients }, // by reference + + intensity: { value: lightProbe.intensity } + + }, + + vertexShader: [ + + 'varying vec3 vNormal;', + + 'void main() {', + + ' vNormal = normalize( normalMatrix * normal );', + + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + '}', + + ].join( '\n' ), + + fragmentShader: [ + + '#define RECIPROCAL_PI 0.318309886', + + 'vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {', + + ' // matrix is assumed to be orthogonal', + + ' return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );', + + '}', + + '// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf', + 'vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {', + + ' // normal is assumed to have unit length', + + ' float x = normal.x, y = normal.y, z = normal.z;', + + ' // band 0', + ' vec3 result = shCoefficients[ 0 ] * 0.886227;', + + ' // band 1', + ' result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;', + ' result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;', + ' result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;', + + ' // band 2', + ' result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;', + ' result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;', + ' result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );', + ' result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;', + ' result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );', + + ' return result;', + + '}', + + 'uniform vec3 sh[ 9 ]; // sh coefficients', + + 'uniform float intensity; // light probe intensity', + + 'varying vec3 vNormal;', + + 'void main() {', + + ' vec3 normal = normalize( vNormal );', + + ' vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );', + + ' vec3 irradiance = shGetIrradianceAt( worldNormal, sh );', + + ' vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;', + + ' gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) );', + + '}' + + ].join( '\n' ) + + } ); + + const geometry = new SphereGeometry( 1, 32, 16 ); + + super( geometry, material ); + + this.lightProbe = lightProbe; + this.size = size; + this.type = 'LightProbeHelper'; + + this.onBeforeRender(); + + } + + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + + } + + onBeforeRender() { + + this.position.copy( this.lightProbe.position ); + + this.scale.set( 1, 1, 1 ).multiplyScalar( this.size ); + + this.material.uniforms.intensity.value = this.lightProbe.intensity; + + } + +} + +export { LightProbeHelper }; diff --git a/jsm/helpers/OctreeHelper.js b/jsm/helpers/OctreeHelper.js new file mode 100644 index 0000000..88c8bd8 --- /dev/null +++ b/jsm/helpers/OctreeHelper.js @@ -0,0 +1,58 @@ +import { + LineSegments, + BufferGeometry, + Float32BufferAttribute, + LineBasicMaterial +} from 'three'; + +class OctreeHelper extends LineSegments { + + constructor( octree, color = 0xffff00 ) { + + const vertices = []; + + function traverse( tree ) { + + for ( let i = 0; i < tree.length; i ++ ) { + + const min = tree[ i ].box.min; + const max = tree[ i ].box.max; + + vertices.push( max.x, max.y, max.z ); vertices.push( min.x, max.y, max.z ); // 0, 1 + vertices.push( min.x, max.y, max.z ); vertices.push( min.x, min.y, max.z ); // 1, 2 + vertices.push( min.x, min.y, max.z ); vertices.push( max.x, min.y, max.z ); // 2, 3 + vertices.push( max.x, min.y, max.z ); vertices.push( max.x, max.y, max.z ); // 3, 0 + + vertices.push( max.x, max.y, min.z ); vertices.push( min.x, max.y, min.z ); // 4, 5 + vertices.push( min.x, max.y, min.z ); vertices.push( min.x, min.y, min.z ); // 5, 6 + vertices.push( min.x, min.y, min.z ); vertices.push( max.x, min.y, min.z ); // 6, 7 + vertices.push( max.x, min.y, min.z ); vertices.push( max.x, max.y, min.z ); // 7, 4 + + vertices.push( max.x, max.y, max.z ); vertices.push( max.x, max.y, min.z ); // 0, 4 + vertices.push( min.x, max.y, max.z ); vertices.push( min.x, max.y, min.z ); // 1, 5 + vertices.push( min.x, min.y, max.z ); vertices.push( min.x, min.y, min.z ); // 2, 6 + vertices.push( max.x, min.y, max.z ); vertices.push( max.x, min.y, min.z ); // 3, 7 + + traverse( tree[ i ].subTrees ); + + } + + } + + traverse( octree.subTrees ); + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + + super( geometry, new LineBasicMaterial( { color: color, toneMapped: false } ) ); + + this.octree = octree; + this.color = color; + + this.type = 'OctreeHelper'; + + } + +} + +export { OctreeHelper }; diff --git a/jsm/helpers/PositionalAudioHelper.js b/jsm/helpers/PositionalAudioHelper.js new file mode 100644 index 0000000..0a20ea5 --- /dev/null +++ b/jsm/helpers/PositionalAudioHelper.js @@ -0,0 +1,109 @@ +import { + BufferGeometry, + BufferAttribute, + LineBasicMaterial, + Line, + MathUtils +} from 'three'; + +class PositionalAudioHelper extends Line { + + constructor( audio, range = 1, divisionsInnerAngle = 16, divisionsOuterAngle = 2 ) { + + const geometry = new BufferGeometry(); + const divisions = divisionsInnerAngle + divisionsOuterAngle * 2; + const positions = new Float32Array( ( divisions * 3 + 3 ) * 3 ); + geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); + + const materialInnerAngle = new LineBasicMaterial( { color: 0x00ff00 } ); + const materialOuterAngle = new LineBasicMaterial( { color: 0xffff00 } ); + + super( geometry, [ materialOuterAngle, materialInnerAngle ] ); + + this.audio = audio; + this.range = range; + this.divisionsInnerAngle = divisionsInnerAngle; + this.divisionsOuterAngle = divisionsOuterAngle; + this.type = 'PositionalAudioHelper'; + + this.update(); + + } + + update() { + + const audio = this.audio; + const range = this.range; + const divisionsInnerAngle = this.divisionsInnerAngle; + const divisionsOuterAngle = this.divisionsOuterAngle; + + const coneInnerAngle = MathUtils.degToRad( audio.panner.coneInnerAngle ); + const coneOuterAngle = MathUtils.degToRad( audio.panner.coneOuterAngle ); + + const halfConeInnerAngle = coneInnerAngle / 2; + const halfConeOuterAngle = coneOuterAngle / 2; + + let start = 0; + let count = 0; + let i; + let stride; + + const geometry = this.geometry; + const positionAttribute = geometry.attributes.position; + + geometry.clearGroups(); + + // + + function generateSegment( from, to, divisions, materialIndex ) { + + const step = ( to - from ) / divisions; + + positionAttribute.setXYZ( start, 0, 0, 0 ); + count ++; + + for ( i = from; i < to; i += step ) { + + stride = start + count; + + positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range ); + positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range ); + positionAttribute.setXYZ( stride + 2, 0, 0, 0 ); + + count += 3; + + } + + geometry.addGroup( start, count, materialIndex ); + + start += count; + count = 0; + + } + + // + + generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 ); + generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 ); + generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 ); + + // + + positionAttribute.needsUpdate = true; + + if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false; + + } + + dispose() { + + this.geometry.dispose(); + this.material[ 0 ].dispose(); + this.material[ 1 ].dispose(); + + } + +} + + +export { PositionalAudioHelper }; diff --git a/jsm/helpers/RectAreaLightHelper.js b/jsm/helpers/RectAreaLightHelper.js new file mode 100644 index 0000000..416fe1b --- /dev/null +++ b/jsm/helpers/RectAreaLightHelper.js @@ -0,0 +1,85 @@ +import { + BackSide, + BufferGeometry, + Float32BufferAttribute, + Line, + LineBasicMaterial, + Mesh, + MeshBasicMaterial +} from 'three'; + +/** + * This helper must be added as a child of the light + */ + +class RectAreaLightHelper extends Line { + + constructor( light, color ) { + + const positions = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ]; + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.computeBoundingSphere(); + + const material = new LineBasicMaterial( { fog: false } ); + + super( geometry, material ); + + this.light = light; + this.color = color; // optional hardwired color for the helper + this.type = 'RectAreaLightHelper'; + + // + + const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ]; + + const geometry2 = new BufferGeometry(); + geometry2.setAttribute( 'position', new Float32BufferAttribute( positions2, 3 ) ); + geometry2.computeBoundingSphere(); + + this.add( new Mesh( geometry2, new MeshBasicMaterial( { side: BackSide, fog: false } ) ) ); + + } + + updateMatrixWorld() { + + this.scale.set( 0.5 * this.light.width, 0.5 * this.light.height, 1 ); + + if ( this.color !== undefined ) { + + this.material.color.set( this.color ); + this.children[ 0 ].material.color.set( this.color ); + + } else { + + this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); + + // prevent hue shift + const c = this.material.color; + const max = Math.max( c.r, c.g, c.b ); + if ( max > 1 ) c.multiplyScalar( 1 / max ); + + this.children[ 0 ].material.color.copy( this.material.color ); + + } + + // 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 ); + + } + + dispose() { + + this.geometry.dispose(); + this.material.dispose(); + this.children[ 0 ].geometry.dispose(); + this.children[ 0 ].material.dispose(); + + } + +} + +export { RectAreaLightHelper }; diff --git a/jsm/helpers/VertexNormalsHelper.js b/jsm/helpers/VertexNormalsHelper.js new file mode 100644 index 0000000..502a668 --- /dev/null +++ b/jsm/helpers/VertexNormalsHelper.js @@ -0,0 +1,95 @@ +import { + BufferGeometry, + Float32BufferAttribute, + LineSegments, + LineBasicMaterial, + Matrix3, + Vector3 +} from 'three'; + +const _v1 = new Vector3(); +const _v2 = new Vector3(); +const _normalMatrix = new Matrix3(); + +class VertexNormalsHelper extends LineSegments { + + constructor( object, size = 1, color = 0xff0000 ) { + + const geometry = new BufferGeometry(); + + 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'; + + // + + this.matrixAutoUpdate = false; + + this.update(); + + } + + update() { + + this.object.updateMatrixWorld( true ); + + _normalMatrix.getNormalMatrix( this.object.matrixWorld ); + + const matrixWorld = this.object.matrixWorld; + + const position = this.geometry.attributes.position; + + // + + 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 ) { + + const objPos = objGeometry.attributes.position; + + const objNorm = objGeometry.attributes.normal; + + let idx = 0; + + // for simplicity, ignore index and drawcalls, and render every normal + + for ( let j = 0, jl = objPos.count; j < jl; j ++ ) { + + _v1.fromBufferAttribute( objPos, j ).applyMatrix4( matrixWorld ); + + _v2.fromBufferAttribute( objNorm, j ); + + _v2.applyMatrix3( _normalMatrix ).normalize().multiplyScalar( this.size ).add( _v1 ); + + position.setXYZ( idx, _v1.x, _v1.y, _v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, _v2.x, _v2.y, _v2.z ); + + idx = idx + 1; + + } + + } + + position.needsUpdate = true; + + } + +} + + +export { VertexNormalsHelper }; diff --git a/jsm/helpers/VertexTangentsHelper.js b/jsm/helpers/VertexTangentsHelper.js new file mode 100644 index 0000000..1938d7a --- /dev/null +++ b/jsm/helpers/VertexTangentsHelper.js @@ -0,0 +1,81 @@ +import { + BufferGeometry, + Float32BufferAttribute, + LineSegments, + LineBasicMaterial, + Vector3 +} from 'three'; + +const _v1 = new Vector3(); +const _v2 = new Vector3(); + +class VertexTangentsHelper extends LineSegments { + + 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.object = object; + this.size = size; + this.type = 'VertexTangentsHelper'; + + // + + this.matrixAutoUpdate = false; + + this.update(); + + } + + update() { + + this.object.updateMatrixWorld( true ); + + const matrixWorld = this.object.matrixWorld; + + const position = this.geometry.attributes.position; + + // + + const objGeometry = this.object.geometry; + + const objPos = objGeometry.attributes.position; + + const objTan = objGeometry.attributes.tangent; + + let idx = 0; + + // for simplicity, ignore index and drawcalls, and render every tangent + + for ( let j = 0, jl = objPos.count; j < jl; j ++ ) { + + _v1.fromBufferAttribute( objPos, j ).applyMatrix4( matrixWorld ); + + _v2.fromBufferAttribute( objTan, j ); + + _v2.transformDirection( matrixWorld ).multiplyScalar( this.size ).add( _v1 ); + + position.setXYZ( idx, _v1.x, _v1.y, _v1.z ); + + idx = idx + 1; + + position.setXYZ( idx, _v2.x, _v2.y, _v2.z ); + + idx = idx + 1; + + } + + position.needsUpdate = true; + + } + +} + +export { VertexTangentsHelper }; diff --git a/jsm/helpers/ViewHelper.js b/jsm/helpers/ViewHelper.js new file mode 100644 index 0000000..dd6c8ee --- /dev/null +++ b/jsm/helpers/ViewHelper.js @@ -0,0 +1,295 @@ +import * as THREE from 'three'; + +const vpTemp = new THREE.Vector4(); + +class ViewHelper extends THREE.Object3D { + + constructor( editorCamera, dom ) { + + super(); + + this.animating = false; + this.controls = null; + + const color1 = new THREE.Color( '#ff3653' ); + const color2 = new THREE.Color( '#8adb00' ); + const color3 = new THREE.Color( '#2c8fff' ); + + const interactiveObjects = []; + const raycaster = new THREE.Raycaster(); + const mouse = new THREE.Vector2(); + const dummy = new THREE.Object3D(); + + const camera = new THREE.OrthographicCamera( - 2, 2, 2, - 2, 0, 4 ); + camera.position.set( 0, 0, 2 ); + + const geometry = new THREE.BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 ); + + const xAxis = new THREE.Mesh( geometry, getAxisMaterial( color1 ) ); + const yAxis = new THREE.Mesh( geometry, getAxisMaterial( color2 ) ); + const zAxis = new THREE.Mesh( geometry, getAxisMaterial( color3 ) ); + + yAxis.rotation.z = Math.PI / 2; + zAxis.rotation.y = - Math.PI / 2; + + this.add( xAxis ); + this.add( zAxis ); + this.add( yAxis ); + + const posXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1, 'X' ) ); + posXAxisHelper.userData.type = 'posX'; + const posYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2, 'Y' ) ); + posYAxisHelper.userData.type = 'posY'; + const posZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3, 'Z' ) ); + posZAxisHelper.userData.type = 'posZ'; + const negXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1 ) ); + negXAxisHelper.userData.type = 'negX'; + const negYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2 ) ); + negYAxisHelper.userData.type = 'negY'; + const negZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3 ) ); + negZAxisHelper.userData.type = 'negZ'; + + posXAxisHelper.position.x = 1; + posYAxisHelper.position.y = 1; + posZAxisHelper.position.z = 1; + negXAxisHelper.position.x = - 1; + negXAxisHelper.scale.setScalar( 0.8 ); + negYAxisHelper.position.y = - 1; + negYAxisHelper.scale.setScalar( 0.8 ); + negZAxisHelper.position.z = - 1; + negZAxisHelper.scale.setScalar( 0.8 ); + + this.add( posXAxisHelper ); + this.add( posYAxisHelper ); + this.add( posZAxisHelper ); + this.add( negXAxisHelper ); + this.add( negYAxisHelper ); + this.add( negZAxisHelper ); + + interactiveObjects.push( posXAxisHelper ); + interactiveObjects.push( posYAxisHelper ); + interactiveObjects.push( posZAxisHelper ); + interactiveObjects.push( negXAxisHelper ); + interactiveObjects.push( negYAxisHelper ); + interactiveObjects.push( negZAxisHelper ); + + const point = new THREE.Vector3(); + const dim = 128; + const turnRate = 2 * Math.PI; // turn rate in angles per second + + this.render = function ( renderer ) { + + this.quaternion.copy( editorCamera.quaternion ).invert(); + this.updateMatrixWorld(); + + point.set( 0, 0, 1 ); + point.applyQuaternion( editorCamera.quaternion ); + + if ( point.x >= 0 ) { + + posXAxisHelper.material.opacity = 1; + negXAxisHelper.material.opacity = 0.5; + + } else { + + posXAxisHelper.material.opacity = 0.5; + negXAxisHelper.material.opacity = 1; + + } + + if ( point.y >= 0 ) { + + posYAxisHelper.material.opacity = 1; + negYAxisHelper.material.opacity = 0.5; + + } else { + + posYAxisHelper.material.opacity = 0.5; + negYAxisHelper.material.opacity = 1; + + } + + if ( point.z >= 0 ) { + + posZAxisHelper.material.opacity = 1; + negZAxisHelper.material.opacity = 0.5; + + } else { + + posZAxisHelper.material.opacity = 0.5; + negZAxisHelper.material.opacity = 1; + + } + + // + + const x = dom.offsetWidth - dim; + + renderer.clearDepth(); + + renderer.getViewport( vpTemp ); + renderer.setViewport( x, 0, dim, dim ); + + renderer.render( this, camera ); + + renderer.setViewport( vpTemp.x, vpTemp.y, vpTemp.z, vpTemp.w ); + + }; + + const targetPosition = new THREE.Vector3(); + const targetQuaternion = new THREE.Quaternion(); + + const q1 = new THREE.Quaternion(); + const q2 = new THREE.Quaternion(); + let radius = 0; + + this.handleClick = function ( event ) { + + if ( this.animating === true ) return false; + + const rect = dom.getBoundingClientRect(); + const offsetX = rect.left + ( dom.offsetWidth - dim ); + const offsetY = rect.top + ( dom.offsetHeight - dim ); + mouse.x = ( ( event.clientX - offsetX ) / ( rect.width - offsetX ) ) * 2 - 1; + mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1; + + raycaster.setFromCamera( mouse, camera ); + + const intersects = raycaster.intersectObjects( interactiveObjects ); + + if ( intersects.length > 0 ) { + + const intersection = intersects[ 0 ]; + const object = intersection.object; + + prepareAnimationData( object, this.controls.center ); + + this.animating = true; + + return true; + + } else { + + return false; + + } + + }; + + this.update = function ( delta ) { + + const step = delta * turnRate; + const focusPoint = this.controls.center; + + // animate position by doing a slerp and then scaling the position on the unit sphere + + q1.rotateTowards( q2, step ); + editorCamera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( focusPoint ); + + // animate orientation + + editorCamera.quaternion.rotateTowards( targetQuaternion, step ); + + if ( q1.angleTo( q2 ) === 0 ) { + + this.animating = false; + + } + + }; + + function prepareAnimationData( object, focusPoint ) { + + switch ( object.userData.type ) { + + case 'posX': + targetPosition.set( 1, 0, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI * 0.5, 0 ) ); + break; + + case 'posY': + targetPosition.set( 0, 1, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( - Math.PI * 0.5, 0, 0 ) ); + break; + + case 'posZ': + targetPosition.set( 0, 0, 1 ); + targetQuaternion.setFromEuler( new THREE.Euler() ); + break; + + case 'negX': + targetPosition.set( - 1, 0, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( 0, - Math.PI * 0.5, 0 ) ); + break; + + case 'negY': + targetPosition.set( 0, - 1, 0 ); + targetQuaternion.setFromEuler( new THREE.Euler( Math.PI * 0.5, 0, 0 ) ); + break; + + case 'negZ': + targetPosition.set( 0, 0, - 1 ); + targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) ); + break; + + default: + console.error( 'ViewHelper: Invalid axis.' ); + + } + + // + + radius = editorCamera.position.distanceTo( focusPoint ); + targetPosition.multiplyScalar( radius ).add( focusPoint ); + + dummy.position.copy( focusPoint ); + + dummy.lookAt( editorCamera.position ); + q1.copy( dummy.quaternion ); + + dummy.lookAt( targetPosition ); + q2.copy( dummy.quaternion ); + + } + + function getAxisMaterial( color ) { + + return new THREE.MeshBasicMaterial( { color: color, toneMapped: false } ); + + } + + function getSpriteMaterial( color, text = null ) { + + const canvas = document.createElement( 'canvas' ); + canvas.width = 64; + canvas.height = 64; + + const context = canvas.getContext( '2d' ); + context.beginPath(); + context.arc( 32, 32, 16, 0, 2 * Math.PI ); + context.closePath(); + context.fillStyle = color.getStyle(); + context.fill(); + + if ( text !== null ) { + + context.font = '24px Arial'; + context.textAlign = 'center'; + context.fillStyle = '#000000'; + context.fillText( text, 32, 41 ); + + } + + const texture = new THREE.CanvasTexture( canvas ); + + return new THREE.SpriteMaterial( { map: texture, toneMapped: false } ); + + } + + } + +} + +ViewHelper.prototype.isViewHelper = true; + +export { ViewHelper }; diff --git a/jsm/interactive/HTMLMesh.js b/jsm/interactive/HTMLMesh.js new file mode 100644 index 0000000..d9212e8 --- /dev/null +++ b/jsm/interactive/HTMLMesh.js @@ -0,0 +1,386 @@ +import { + CanvasTexture, + LinearFilter, + Mesh, + MeshBasicMaterial, + PlaneGeometry, + sRGBEncoding +} from 'three'; + +class HTMLMesh extends Mesh { + + constructor( dom ) { + + const texture = new HTMLTexture( dom ); + + const geometry = new PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 ); + const material = new MeshBasicMaterial( { map: texture, toneMapped: false } ); + + super( geometry, material ); + + function onEvent( event ) { + + material.map.dispatchDOMEvent( event ); + + } + + this.addEventListener( 'mousedown', onEvent ); + this.addEventListener( 'mousemove', onEvent ); + this.addEventListener( 'mouseup', onEvent ); + this.addEventListener( 'click', onEvent ); + + this.dispose = function () { + + geometry.dispose(); + material.dispose(); + + material.map.dispose(); + + this.removeEventListener( 'mousedown', onEvent ); + this.removeEventListener( 'mousemove', onEvent ); + this.removeEventListener( 'mouseup', onEvent ); + this.removeEventListener( 'click', onEvent ); + + }; + + } + +} + +class HTMLTexture extends CanvasTexture { + + constructor( dom ) { + + super( html2canvas( dom ) ); + + this.dom = dom; + + this.anisotropy = 16; + this.encoding = sRGBEncoding; + this.minFilter = LinearFilter; + this.magFilter = LinearFilter; + + // Create an observer on the DOM, and run html2canvas update in the next loop + const observer = new MutationObserver( () => { + + if ( ! this.scheduleUpdate ) { + + // ideally should use xr.requestAnimationFrame, here setTimeout to avoid passing the renderer + this.scheduleUpdate = setTimeout( () => this.update(), 16 ); + + } + + } ); + + const config = { attributes: true, childList: true, subtree: true, characterData: true }; + observer.observe( dom, config ); + + this.observer = observer; + + } + + dispatchDOMEvent( event ) { + + if ( event.data ) { + + htmlevent( this.dom, event.type, event.data.x, event.data.y ); + + } + + } + + update() { + + this.image = html2canvas( this.dom ); + this.needsUpdate = true; + + this.scheduleUpdate = null; + + } + + dispose() { + + if ( this.observer ) { + + this.observer.disconnect(); + + } + + this.scheduleUpdate = clearTimeout( this.scheduleUpdate ); + + super.dispose(); + + } + +} + + +// + +const canvases = new WeakMap(); + +function html2canvas( element ) { + + const range = document.createRange(); + + function Clipper( context ) { + + const clips = []; + let isClipping = false; + + function doClip() { + + if ( isClipping ) { + + isClipping = false; + context.restore(); + + } + + if ( clips.length === 0 ) return; + + let minX = - Infinity, minY = - Infinity; + let maxX = Infinity, maxY = Infinity; + + for ( let i = 0; i < clips.length; i ++ ) { + + const clip = clips[ i ]; + + minX = Math.max( minX, clip.x ); + minY = Math.max( minY, clip.y ); + maxX = Math.min( maxX, clip.x + clip.width ); + maxY = Math.min( maxY, clip.y + clip.height ); + + } + + context.save(); + context.beginPath(); + context.rect( minX, minY, maxX - minX, maxY - minY ); + context.clip(); + + isClipping = true; + + } + + return { + + add: function ( clip ) { + + clips.push( clip ); + doClip(); + + }, + + remove: function () { + + clips.pop(); + doClip(); + + } + + }; + + } + + function drawText( style, x, y, string ) { + + if ( string !== '' ) { + + if ( style.textTransform === 'uppercase' ) { + + string = string.toUpperCase(); + + } + + context.font = style.fontSize + ' ' + style.fontFamily; + context.textBaseline = 'top'; + context.fillStyle = style.color; + context.fillText( string, x, y ); + + } + + } + + function drawBorder( style, which, x, y, width, height ) { + + const borderWidth = style[ which + 'Width' ]; + const borderStyle = style[ which + 'Style' ]; + const borderColor = style[ which + 'Color' ]; + + if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) { + + context.strokeStyle = borderColor; + context.beginPath(); + context.moveTo( x, y ); + context.lineTo( x + width, y + height ); + context.stroke(); + + } + + } + + function drawElement( element, style ) { + + let x = 0, y = 0, width = 0, height = 0; + + if ( element.nodeType === Node.TEXT_NODE ) { + + // text + + range.selectNode( element ); + + const rect = range.getBoundingClientRect(); + + x = rect.left - offset.left - 0.5; + y = rect.top - offset.top - 0.5; + width = rect.width; + height = rect.height; + + drawText( style, x, y, element.nodeValue.trim() ); + + } else if ( element.nodeType === Node.COMMENT_NODE ) { + + return; + + } else if ( element instanceof HTMLCanvasElement ) { + + // Canvas element + if ( element.style.display === 'none' ) return; + + context.save(); + const dpr = window.devicePixelRatio; + context.scale(1/dpr, 1/dpr); + context.drawImage(element, 0, 0 ); + context.restore(); + + } else { + + if ( element.style.display === 'none' ) return; + + const rect = element.getBoundingClientRect(); + + x = rect.left - offset.left - 0.5; + y = rect.top - offset.top - 0.5; + width = rect.width; + height = rect.height; + + style = window.getComputedStyle( element ); + + const backgroundColor = style.backgroundColor; + + if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) { + + context.fillStyle = backgroundColor; + context.fillRect( x, y, width, height ); + + } + + drawBorder( style, 'borderTop', x, y, width, 0 ); + drawBorder( style, 'borderLeft', x, y, 0, height ); + drawBorder( style, 'borderBottom', x, y + height, width, 0 ); + drawBorder( style, 'borderRight', x + width, y, 0, height ); + + if ( element.type === 'color' || element.type === 'text' || element.type === 'number' ) { + + clipper.add( { x: x, y: y, width: width, height: height } ); + + drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value ); + + clipper.remove(); + + } + + } + + /* + // debug + context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 ); + context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 ); + */ + + const isClipping = style.overflow === 'auto' || style.overflow === 'hidden'; + + if ( isClipping ) clipper.add( { x: x, y: y, width: width, height: height } ); + + for ( let i = 0; i < element.childNodes.length; i ++ ) { + + drawElement( element.childNodes[ i ], style ); + + } + + if ( isClipping ) clipper.remove(); + + } + + const offset = element.getBoundingClientRect(); + + let canvas; + + if ( canvases.has( element ) ) { + + canvas = canvases.get( element ); + + } else { + + canvas = document.createElement( 'canvas' ); + canvas.width = offset.width; + canvas.height = offset.height; + + } + + const context = canvas.getContext( '2d'/*, { alpha: false }*/ ); + + const clipper = new Clipper( context ); + + // console.time( 'drawElement' ); + + drawElement( element ); + + // console.timeEnd( 'drawElement' ); + + return canvas; + +} + +function htmlevent( element, event, x, y ) { + + const mouseEventInit = { + clientX: ( x * element.offsetWidth ) + element.offsetLeft, + clientY: ( y * element.offsetHeight ) + element.offsetTop, + view: element.ownerDocument.defaultView + }; + + window.dispatchEvent( new MouseEvent( event, mouseEventInit ) ); + + const rect = element.getBoundingClientRect(); + + x = x * rect.width + rect.left; + y = y * rect.height + rect.top; + + function traverse( element ) { + + if ( element.nodeType !== Node.TEXT_NODE && element.nodeType !== Node.COMMENT_NODE ) { + + const rect = element.getBoundingClientRect(); + + if ( x > rect.left && x < rect.right && y > rect.top && y < rect.bottom ) { + + element.dispatchEvent( new MouseEvent( event, mouseEventInit ) ); + + } + + for ( let i = 0; i < element.childNodes.length; i ++ ) { + + traverse( element.childNodes[ i ] ); + + } + + } + + } + + traverse( element ); + +} + +export { HTMLMesh }; diff --git a/jsm/interactive/InteractiveGroup.js b/jsm/interactive/InteractiveGroup.js new file mode 100644 index 0000000..6ae5b82 --- /dev/null +++ b/jsm/interactive/InteractiveGroup.js @@ -0,0 +1,114 @@ +import { + Group, + Matrix4, + Raycaster, + Vector2 +} from 'three'; + +const _pointer = new Vector2(); +const _event = { type: '', data: _pointer }; + +class InteractiveGroup extends Group { + + constructor( renderer, camera ) { + + super(); + + const scope = this; + + const raycaster = new Raycaster(); + const tempMatrix = new Matrix4(); + + // Pointer Events + + const element = renderer.domElement; + + function onPointerEvent( event ) { + + event.stopPropagation(); + + _pointer.x = ( event.clientX / element.clientWidth ) * 2 - 1; + _pointer.y = - ( event.clientY / element.clientHeight ) * 2 + 1; + + raycaster.setFromCamera( _pointer, camera ); + + const intersects = raycaster.intersectObjects( scope.children, false ); + + if ( intersects.length > 0 ) { + + const intersection = intersects[ 0 ]; + + const object = intersection.object; + const uv = intersection.uv; + + _event.type = event.type; + _event.data.set( uv.x, 1 - uv.y ); + + object.dispatchEvent( _event ); + + } + + } + + element.addEventListener( 'pointerdown', onPointerEvent ); + element.addEventListener( 'pointerup', onPointerEvent ); + element.addEventListener( 'pointermove', onPointerEvent ); + element.addEventListener( 'mousedown', onPointerEvent ); + element.addEventListener( 'mouseup', onPointerEvent ); + element.addEventListener( 'mousemove', onPointerEvent ); + element.addEventListener( 'click', onPointerEvent ); + + // WebXR Controller Events + // TODO: Dispatch pointerevents too + + const events = { + 'move': 'mousemove', + 'select': 'click', + 'selectstart': 'mousedown', + 'selectend': 'mouseup' + }; + + function onXRControllerEvent( event ) { + + const controller = event.target; + + tempMatrix.identity().extractRotation( controller.matrixWorld ); + + raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); + raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); + + const intersections = raycaster.intersectObjects( scope.children, false ); + + if ( intersections.length > 0 ) { + + const intersection = intersections[ 0 ]; + + const object = intersection.object; + const uv = intersection.uv; + + _event.type = events[ event.type ]; + _event.data.set( uv.x, 1 - uv.y ); + + object.dispatchEvent( _event ); + + } + + } + + const controller1 = renderer.xr.getController( 0 ); + controller1.addEventListener( 'move', onXRControllerEvent ); + controller1.addEventListener( 'select', onXRControllerEvent ); + controller1.addEventListener( 'selectstart', onXRControllerEvent ); + controller1.addEventListener( 'selectend', onXRControllerEvent ); + + const controller2 = renderer.xr.getController( 1 ); + controller2.addEventListener( 'move', onXRControllerEvent ); + controller2.addEventListener( 'select', onXRControllerEvent ); + controller2.addEventListener( 'selectstart', onXRControllerEvent ); + controller2.addEventListener( 'selectend', onXRControllerEvent ); + + } + +} + +export { InteractiveGroup }; diff --git a/jsm/interactive/SelectionBox.js b/jsm/interactive/SelectionBox.js new file mode 100644 index 0000000..597b9b2 --- /dev/null +++ b/jsm/interactive/SelectionBox.js @@ -0,0 +1,227 @@ +import { + Frustum, + Vector3, + Matrix4, + Quaternion, +} from 'three'; + +/** + * This is a class to check whether objects are in a selection area in 3D space + */ + +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(); + +const _matrix = new Matrix4(); +const _quaternion = new Quaternion(); +const _scale = new Vector3(); + +class SelectionBox { + + constructor( camera, scene, deep = Number.MAX_VALUE ) { + + this.camera = camera; + this.scene = scene; + this.startPoint = new Vector3(); + this.endPoint = new Vector3(); + this.collection = []; + this.instances = {}; + this.deep = deep; + + } + + 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 ); + + return this.collection; + + } + + updateFrustum( startPoint, endPoint ) { + + startPoint = startPoint || this.startPoint; + endPoint = endPoint || this.endPoint; + + // Avoid invalid frustum + + if ( startPoint.x === endPoint.x ) { + + endPoint.x += 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 ); + + const 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 ); + + const 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.isInstancedMesh ) { + + this.instances[ object.uuid ] = []; + + for ( let instanceId = 0; instanceId < object.count; instanceId ++ ) { + + object.getMatrixAt( instanceId, _matrix ); + _matrix.decompose( _center, _quaternion, _scale ); + _center.applyMatrix4( object.matrixWorld ); + + if ( frustum.containsPoint( _center ) ) { + + this.instances[ object.uuid ].push( instanceId ); + + } + + } + + } else { + + if ( object.geometry.boundingSphere === null ) object.geometry.computeBoundingSphere(); + + _center.copy( object.geometry.boundingSphere.center ); + + _center.applyMatrix4( object.matrixWorld ); + + 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 ] ); + + } + + } + + } + +} + +export { SelectionBox }; diff --git a/jsm/interactive/SelectionHelper.js b/jsm/interactive/SelectionHelper.js new file mode 100644 index 0000000..00153a9 --- /dev/null +++ b/jsm/interactive/SelectionHelper.js @@ -0,0 +1,95 @@ +import { + Vector2 +} from 'three'; + +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.startPoint = new Vector2(); + this.pointTopLeft = new Vector2(); + this.pointBottomRight = new Vector2(); + + this.isDown = false; + + this.onPointerDown = function ( event ) { + + this.isDown = true; + this.onSelectStart( event ); + + }.bind( this ); + + this.onPointerMove = function ( event ) { + + if ( this.isDown ) { + + this.onSelectMove( event ); + + } + + }.bind( this ); + + this.onPointerUp = function ( ) { + + this.isDown = false; + this.onSelectOver(); + + }.bind( this ); + + this.renderer.domElement.addEventListener( 'pointerdown', this.onPointerDown ); + this.renderer.domElement.addEventListener( 'pointermove', this.onPointerMove ); + this.renderer.domElement.addEventListener( 'pointerup', this.onPointerUp ); + + } + + dispose() { + + this.renderer.domElement.removeEventListener( 'pointerdown', this.onPointerDown ); + this.renderer.domElement.removeEventListener( 'pointermove', this.onPointerMove ); + this.renderer.domElement.removeEventListener( 'pointerup', this.onPointerUp ); + + } + + onSelectStart( event ) { + + this.renderer.domElement.parentElement.appendChild( this.element ); + + this.element.style.left = event.clientX + 'px'; + this.element.style.top = event.clientY + 'px'; + this.element.style.width = '0px'; + this.element.style.height = '0px'; + + this.startPoint.x = event.clientX; + this.startPoint.y = event.clientY; + + } + + 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.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'; + + } + + onSelectOver() { + + this.element.parentElement.removeChild( this.element ); + + } + +} + +export { SelectionHelper }; diff --git a/jsm/libs/OimoPhysics/OimoPhysics.js b/jsm/libs/OimoPhysics/OimoPhysics.js new file mode 100644 index 0000000..3c4eb49 --- /dev/null +++ b/jsm/libs/OimoPhysics/OimoPhysics.js @@ -0,0 +1,37071 @@ +// Generated by Haxe 4.2.0 +var oimo = oimo || {}; +if(!oimo.collision) oimo.collision = {}; +if(!oimo.collision.broadphase) oimo.collision.broadphase = {}; +oimo.collision.broadphase.BroadPhase = class oimo_collision_broadphase_BroadPhase { + constructor(type) { + this._type = type; + this._numProxies = 0; + this._proxyList = null; + this._proxyListLast = null; + this._proxyPairList = null; + this._incremental = false; + this._testCount = 0; + this._proxyPairPool = null; + this._idCount = 0; + this._convexSweep = new oimo.collision.broadphase._BroadPhase.ConvexSweepGeometry(); + this._aabb = new oimo.collision.broadphase._BroadPhase.AabbGeometry(); + this.identity = new oimo.common.Transform(); + this.zero = new oimo.common.Vec3(); + this.rayCastHit = new oimo.collision.geometry.RayCastHit(); + } + createProxy(userData,aabb) { + return null; + } + destroyProxy(proxy) { + } + moveProxy(proxy,aabb,displacement) { + } + isOverlapping(proxy1,proxy2) { + if(proxy1._aabbMinX < proxy2._aabbMaxX && proxy1._aabbMaxX > proxy2._aabbMinX && proxy1._aabbMinY < proxy2._aabbMaxY && proxy1._aabbMaxY > proxy2._aabbMinY && proxy1._aabbMinZ < proxy2._aabbMaxZ) { + return proxy1._aabbMaxZ > proxy2._aabbMinZ; + } else { + return false; + } + } + collectPairs() { + } + getProxyPairList() { + return this._proxyPairList; + } + isIncremental() { + return this._incremental; + } + getTestCount() { + return this._testCount; + } + rayCast(begin,end,callback) { + } + convexCast(convex,begin,translation,callback) { + } + aabbTest(aabb,callback) { + } +} +if(!oimo.collision.geometry) oimo.collision.geometry = {}; +oimo.collision.geometry.Geometry = class oimo_collision_geometry_Geometry { + constructor(type) { + this._type = type; + this._volume = 0; + } + _updateMass() { + } + _computeAabb(aabb,tf) { + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + return false; + } + getType() { + return this._type; + } + getVolume() { + return this._volume; + } + rayCast(begin,end,transform,hit) { + let beginLocalX; + let beginLocalY; + let beginLocalZ; + let endLocalX; + let endLocalY; + let endLocalZ; + beginLocalX = begin.x; + beginLocalY = begin.y; + beginLocalZ = begin.z; + endLocalX = end.x; + endLocalY = end.y; + endLocalZ = end.z; + beginLocalX -= transform._positionX; + beginLocalY -= transform._positionY; + beginLocalZ -= transform._positionZ; + endLocalX -= transform._positionX; + endLocalY -= transform._positionY; + endLocalZ -= transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = transform._rotation00 * beginLocalX + transform._rotation10 * beginLocalY + transform._rotation20 * beginLocalZ; + __tmp__Y = transform._rotation01 * beginLocalX + transform._rotation11 * beginLocalY + transform._rotation21 * beginLocalZ; + __tmp__Z = transform._rotation02 * beginLocalX + transform._rotation12 * beginLocalY + transform._rotation22 * beginLocalZ; + beginLocalX = __tmp__X; + beginLocalY = __tmp__Y; + beginLocalZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = transform._rotation00 * endLocalX + transform._rotation10 * endLocalY + transform._rotation20 * endLocalZ; + __tmp__Y1 = transform._rotation01 * endLocalX + transform._rotation11 * endLocalY + transform._rotation21 * endLocalZ; + __tmp__Z1 = transform._rotation02 * endLocalX + transform._rotation12 * endLocalY + transform._rotation22 * endLocalZ; + endLocalX = __tmp__X1; + endLocalY = __tmp__Y1; + endLocalZ = __tmp__Z1; + if(this._rayCastLocal(beginLocalX,beginLocalY,beginLocalZ,endLocalX,endLocalY,endLocalZ,hit)) { + let localPosX; + let localPosY; + let localPosZ; + let localNormalX; + let localNormalY; + let localNormalZ; + let v = hit.position; + localPosX = v.x; + localPosY = v.y; + localPosZ = v.z; + let v1 = hit.normal; + localNormalX = v1.x; + localNormalY = v1.y; + localNormalZ = v1.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = transform._rotation00 * localPosX + transform._rotation01 * localPosY + transform._rotation02 * localPosZ; + __tmp__Y = transform._rotation10 * localPosX + transform._rotation11 * localPosY + transform._rotation12 * localPosZ; + __tmp__Z = transform._rotation20 * localPosX + transform._rotation21 * localPosY + transform._rotation22 * localPosZ; + localPosX = __tmp__X; + localPosY = __tmp__Y; + localPosZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = transform._rotation00 * localNormalX + transform._rotation01 * localNormalY + transform._rotation02 * localNormalZ; + __tmp__Y1 = transform._rotation10 * localNormalX + transform._rotation11 * localNormalY + transform._rotation12 * localNormalZ; + __tmp__Z1 = transform._rotation20 * localNormalX + transform._rotation21 * localNormalY + transform._rotation22 * localNormalZ; + localNormalX = __tmp__X1; + localNormalY = __tmp__Y1; + localNormalZ = __tmp__Z1; + localPosX += transform._positionX; + localPosY += transform._positionY; + localPosZ += transform._positionZ; + let v2 = hit.position; + v2.x = localPosX; + v2.y = localPosY; + v2.z = localPosZ; + let v3 = hit.normal; + v3.x = localNormalX; + v3.y = localNormalY; + v3.z = localNormalZ; + return true; + } + return false; + } +} +oimo.collision.geometry.ConvexGeometry = class oimo_collision_geometry_ConvexGeometry extends oimo.collision.geometry.Geometry { + constructor(type) { + super(type); + this._gjkMargin = oimo.common.Setting.defaultGJKMargin; + this._useGjkRayCast = false; + } + getGjkMergin() { + return this._gjkMargin; + } + setGjkMergin(gjkMergin) { + if(gjkMergin < 0) { + gjkMergin = 0; + } + this._gjkMargin = gjkMergin; + } + computeLocalSupportingVertex(dir,out) { + } + rayCast(begin,end,transform,hit) { + if(this._useGjkRayCast) { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance.rayCast(this,transform,begin,end,hit); + } else { + return super.rayCast(begin,end,transform,hit); + } + } +} +if(!oimo.collision.broadphase._BroadPhase) oimo.collision.broadphase._BroadPhase = {}; +oimo.collision.broadphase._BroadPhase.ConvexSweepGeometry = class oimo_collision_broadphase__$BroadPhase_ConvexSweepGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor() { + super(-1); + } + init(c,transform,translation) { + this.c = c; + let trX; + let trY; + let trZ; + trX = translation.x; + trY = translation.y; + trZ = translation.z; + let localTrX; + let localTrY; + let localTrZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = transform._rotation00 * trX + transform._rotation10 * trY + transform._rotation20 * trZ; + __tmp__Y = transform._rotation01 * trX + transform._rotation11 * trY + transform._rotation21 * trZ; + __tmp__Z = transform._rotation02 * trX + transform._rotation12 * trY + transform._rotation22 * trZ; + localTrX = __tmp__X; + localTrY = __tmp__Y; + localTrZ = __tmp__Z; + this.localTranslation = new oimo.common.Vec3(); + let v = this.localTranslation; + v.x = localTrX; + v.y = localTrY; + v.z = localTrZ; + this._gjkMargin = c._gjkMargin; + } + computeLocalSupportingVertex(dir,out) { + this.c.computeLocalSupportingVertex(dir,out); + let v = this.localTranslation; + if(dir.x * v.x + dir.y * v.y + dir.z * v.z > 0) { + let v = this.localTranslation; + out.x += v.x; + out.y += v.y; + out.z += v.z; + } + } +} +oimo.collision.broadphase._BroadPhase.AabbGeometry = class oimo_collision_broadphase__$BroadPhase_AabbGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor() { + super(-1); + this.min = new oimo.common.Vec3(); + this.max = new oimo.common.Vec3(); + } + computeLocalSupportingVertex(dir,out) { + out.x = dir.x > 0 ? this.max.x : this.min.x; + out.y = dir.y > 0 ? this.max.y : this.min.y; + out.z = dir.z > 0 ? this.max.z : this.min.z; + } +} +oimo.collision.broadphase.BroadPhaseProxyCallback = class oimo_collision_broadphase_BroadPhaseProxyCallback { + constructor() { + } + process(proxy) { + } +} +oimo.collision.broadphase.BroadPhaseType = class oimo_collision_broadphase_BroadPhaseType { +} +oimo.collision.broadphase.Proxy = class oimo_collision_broadphase_Proxy { + constructor(userData,id) { + this.userData = userData; + this._id = id; + this._prev = null; + this._next = null; + this._aabbMinX = 0; + this._aabbMinY = 0; + this._aabbMinZ = 0; + this._aabbMaxX = 0; + this._aabbMaxY = 0; + this._aabbMaxZ = 0; + } + getId() { + return this._id; + } + getFatAabb() { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._aabbMinX; + aabb._minY = this._aabbMinY; + aabb._minZ = this._aabbMinZ; + aabb._maxX = this._aabbMaxX; + aabb._maxY = this._aabbMaxY; + aabb._maxZ = this._aabbMaxZ; + return aabb; + } + getFatAabbTo(aabb) { + aabb._minX = this._aabbMinX; + aabb._minY = this._aabbMinY; + aabb._minZ = this._aabbMinZ; + aabb._maxX = this._aabbMaxX; + aabb._maxY = this._aabbMaxY; + aabb._maxZ = this._aabbMaxZ; + } +} +oimo.collision.broadphase.ProxyPair = class oimo_collision_broadphase_ProxyPair { + constructor() { + this._p1 = null; + this._p2 = null; + } + getProxy1() { + return this._p1; + } + getProxy2() { + return this._p2; + } + getNext() { + return this._next; + } +} +if(!oimo.collision.broadphase.bruteforce) oimo.collision.broadphase.bruteforce = {}; +oimo.collision.broadphase.bruteforce.BruteForceBroadPhase = class oimo_collision_broadphase_bruteforce_BruteForceBroadPhase extends oimo.collision.broadphase.BroadPhase { + constructor() { + super(1); + this._incremental = false; + } + createProxy(userData,aabb) { + let proxy = new oimo.collision.broadphase.Proxy(userData,this._idCount++); + this._numProxies++; + if(this._proxyList == null) { + this._proxyList = proxy; + this._proxyListLast = proxy; + } else { + this._proxyListLast._next = proxy; + proxy._prev = this._proxyListLast; + this._proxyListLast = proxy; + } + proxy._aabbMinX = aabb._minX; + proxy._aabbMinY = aabb._minY; + proxy._aabbMinZ = aabb._minZ; + proxy._aabbMaxX = aabb._maxX; + proxy._aabbMaxY = aabb._maxY; + proxy._aabbMaxZ = aabb._maxZ; + return proxy; + } + destroyProxy(proxy) { + this._numProxies--; + let prev = proxy._prev; + let next = proxy._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(proxy == this._proxyList) { + this._proxyList = this._proxyList._next; + } + if(proxy == this._proxyListLast) { + this._proxyListLast = this._proxyListLast._prev; + } + proxy._next = null; + proxy._prev = null; + proxy.userData = null; + } + moveProxy(proxy,aabb,dislacement) { + proxy._aabbMinX = aabb._minX; + proxy._aabbMinY = aabb._minY; + proxy._aabbMinZ = aabb._minZ; + proxy._aabbMaxX = aabb._maxX; + proxy._aabbMaxY = aabb._maxY; + proxy._aabbMaxZ = aabb._maxZ; + } + collectPairs() { + let p = this._proxyPairList; + if(p != null) { + while(true) { + p._p1 = null; + p._p2 = null; + p = p._next; + if(!(p != null)) { + break; + } + } + this._proxyPairList._next = this._proxyPairPool; + this._proxyPairPool = this._proxyPairList; + this._proxyPairList = null; + } + this._testCount = 0; + let p1 = this._proxyList; + while(p1 != null) { + let n = p1._next; + let p2 = p1._next; + while(p2 != null) { + let n = p2._next; + this._testCount++; + if(p1._aabbMinX < p2._aabbMaxX && p1._aabbMaxX > p2._aabbMinX && p1._aabbMinY < p2._aabbMaxY && p1._aabbMaxY > p2._aabbMinY && p1._aabbMinZ < p2._aabbMaxZ && p1._aabbMaxZ > p2._aabbMinZ) { + let first = this._proxyPairPool; + if(first != null) { + this._proxyPairPool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.ProxyPair(); + } + let pp = first; + if(this._proxyPairList == null) { + this._proxyPairList = pp; + } else { + pp._next = this._proxyPairList; + this._proxyPairList = pp; + } + pp._p1 = p1; + pp._p2 = p2; + } + p2 = n; + } + p1 = n; + } + } + rayCast(begin,end,callback) { + let p1X; + let p1Y; + let p1Z; + let p2X; + let p2Y; + let p2Z; + p1X = begin.x; + p1Y = begin.y; + p1Z = begin.z; + p2X = end.x; + p2Y = end.y; + p2Z = end.z; + let p = this._proxyList; + while(p != null) { + let n = p._next; + let x1 = p1X; + let y1 = p1Y; + let z1 = p1Z; + let x2 = p2X; + let y2 = p2Y; + let z2 = p2Z; + let pminx = p._aabbMinX; + let pminy = p._aabbMinY; + let pminz = p._aabbMinZ; + let pmaxx = p._aabbMaxX; + let pmaxy = p._aabbMaxY; + let pmaxz = p._aabbMaxZ; + let tmp; + if(pminx > (x1 > x2 ? x1 : x2) || pmaxx < (x1 < x2 ? x1 : x2) || pminy > (y1 > y2 ? y1 : y2) || pmaxy < (y1 < y2 ? y1 : y2) || pminz > (z1 > z2 ? z1 : z2) || pmaxz < (z1 < z2 ? z1 : z2)) { + tmp = false; + } else { + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + let adx = dx < 0 ? -dx : dx; + let ady = dy < 0 ? -dy : dy; + let adz = dz < 0 ? -dz : dz; + let pextx = (pmaxx - pminx) * 0.5; + let pexty = (pmaxy - pminy) * 0.5; + let pextz = (pmaxz - pminz) * 0.5; + let cpx = x1 - (pmaxx + pminx) * 0.5; + let cpy = y1 - (pmaxy + pminy) * 0.5; + let cpz = z1 - (pmaxz + pminz) * 0.5; + let tmp1; + let tmp2; + let x = cpy * dz - cpz * dy; + if(!((x < 0 ? -x : x) - (pexty * adz + pextz * ady) > 0)) { + let x = cpz * dx - cpx * dz; + tmp2 = (x < 0 ? -x : x) - (pextz * adx + pextx * adz) > 0; + } else { + tmp2 = true; + } + if(!tmp2) { + let x = cpx * dy - cpy * dx; + tmp1 = (x < 0 ? -x : x) - (pextx * ady + pexty * adx) > 0; + } else { + tmp1 = true; + } + tmp = tmp1 ? false : true; + } + if(tmp) { + callback.process(p); + } + p = n; + } + } + convexCast(convex,begin,translation,callback) { + let p = this._proxyList; + while(p != null) { + let n = p._next; + let v = this._aabb.min; + v.x = p._aabbMinX; + v.y = p._aabbMinY; + v.z = p._aabbMinZ; + let v1 = this._aabb.max; + v1.x = p._aabbMaxX; + v1.y = p._aabbMaxY; + v1.z = p._aabbMaxZ; + this._convexSweep.init(convex,begin,translation); + let gjkEpa = oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + if(gjkEpa.computeClosestPointsImpl(this._convexSweep,this._aabb,begin,this.identity,null,false) == 0 && gjkEpa.distance <= 0) { + callback.process(p); + } + p = n; + } + } + aabbTest(aabb,callback) { + let p = this._proxyList; + while(p != null) { + let n = p._next; + if(aabb._minX < p._aabbMaxX && aabb._maxX > p._aabbMinX && aabb._minY < p._aabbMaxY && aabb._maxY > p._aabbMinY && aabb._minZ < p._aabbMaxZ && aabb._maxZ > p._aabbMinZ) { + callback.process(p); + } + p = n; + } + } +} +if(!oimo.collision.broadphase.bvh) oimo.collision.broadphase.bvh = {}; +oimo.collision.broadphase.bvh.BvhBroadPhase = class oimo_collision_broadphase_bvh_BvhBroadPhase extends oimo.collision.broadphase.BroadPhase { + constructor() { + super(2); + this._incremental = true; + this._tree = new oimo.collision.broadphase.bvh.BvhTree(); + this.movedProxies = new Array(1024); + this.numMovedProxies = 0; + } + collide(n1,n2) { + this._testCount++; + let l1 = n1._height == 0; + let l2 = n2._height == 0; + if(n1 == n2) { + if(l1) { + return; + } + this.collide(n1._children[0],n2); + this.collide(n1._children[1],n2); + return; + } + if(!(n1._aabbMinX < n2._aabbMaxX && n1._aabbMaxX > n2._aabbMinX && n1._aabbMinY < n2._aabbMaxY && n1._aabbMaxY > n2._aabbMinY && n1._aabbMinZ < n2._aabbMaxZ && n1._aabbMaxZ > n2._aabbMinZ)) { + return; + } + if(l1 && l2) { + let first = this._proxyPairPool; + if(first != null) { + this._proxyPairPool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.ProxyPair(); + } + let pp = first; + if(this._proxyPairList == null) { + this._proxyPairList = pp; + } else { + pp._next = this._proxyPairList; + this._proxyPairList = pp; + } + pp._p1 = n1._proxy; + pp._p2 = n2._proxy; + return; + } + if(l2 || n1._height > n2._height) { + this.collide(n1._children[0],n2); + this.collide(n1._children[1],n2); + } else { + this.collide(n2._children[0],n1); + this.collide(n2._children[1],n1); + } + } + rayCastRecursive(node,_p1X,_p1Y,_p1Z,_p2X,_p2Y,_p2Z,callback) { + let x1 = _p1X; + let y1 = _p1Y; + let z1 = _p1Z; + let x2 = _p2X; + let y2 = _p2Y; + let z2 = _p2Z; + let pminx = node._aabbMinX; + let pminy = node._aabbMinY; + let pminz = node._aabbMinZ; + let pmaxx = node._aabbMaxX; + let pmaxy = node._aabbMaxY; + let pmaxz = node._aabbMaxZ; + let tmp; + if(pminx > (x1 > x2 ? x1 : x2) || pmaxx < (x1 < x2 ? x1 : x2) || pminy > (y1 > y2 ? y1 : y2) || pmaxy < (y1 < y2 ? y1 : y2) || pminz > (z1 > z2 ? z1 : z2) || pmaxz < (z1 < z2 ? z1 : z2)) { + tmp = false; + } else { + let dx = x2 - x1; + let dy = y2 - y1; + let dz = z2 - z1; + let adx = dx < 0 ? -dx : dx; + let ady = dy < 0 ? -dy : dy; + let adz = dz < 0 ? -dz : dz; + let pextx = (pmaxx - pminx) * 0.5; + let pexty = (pmaxy - pminy) * 0.5; + let pextz = (pmaxz - pminz) * 0.5; + let cpx = x1 - (pmaxx + pminx) * 0.5; + let cpy = y1 - (pmaxy + pminy) * 0.5; + let cpz = z1 - (pmaxz + pminz) * 0.5; + let tmp1; + let tmp2; + let x = cpy * dz - cpz * dy; + if(!((x < 0 ? -x : x) - (pexty * adz + pextz * ady) > 0)) { + let x = cpz * dx - cpx * dz; + tmp2 = (x < 0 ? -x : x) - (pextz * adx + pextx * adz) > 0; + } else { + tmp2 = true; + } + if(!tmp2) { + let x = cpx * dy - cpy * dx; + tmp1 = (x < 0 ? -x : x) - (pextx * ady + pexty * adx) > 0; + } else { + tmp1 = true; + } + tmp = tmp1 ? false : true; + } + if(!tmp) { + return; + } + if(node._height == 0) { + callback.process(node._proxy); + return; + } + this.rayCastRecursive(node._children[0],_p1X,_p1Y,_p1Z,_p2X,_p2Y,_p2Z,callback); + this.rayCastRecursive(node._children[1],_p1X,_p1Y,_p1Z,_p2X,_p2Y,_p2Z,callback); + } + convexCastRecursive(node,convex,begin,translation,callback) { + let v = this._aabb.min; + v.x = node._aabbMinX; + v.y = node._aabbMinY; + v.z = node._aabbMinZ; + let v1 = this._aabb.max; + v1.x = node._aabbMaxX; + v1.y = node._aabbMaxY; + v1.z = node._aabbMaxZ; + this._convexSweep.init(convex,begin,translation); + let gjkEpa = oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + if(!(gjkEpa.computeClosestPointsImpl(this._convexSweep,this._aabb,begin,this.identity,null,false) == 0 && gjkEpa.distance <= 0)) { + return; + } + if(node._height == 0) { + callback.process(node._proxy); + return; + } + this.convexCastRecursive(node._children[0],convex,begin,translation,callback); + this.convexCastRecursive(node._children[1],convex,begin,translation,callback); + } + aabbTestRecursive(node,aabb,callback) { + if(!(node._aabbMinX < aabb._maxX && node._aabbMaxX > aabb._minX && node._aabbMinY < aabb._maxY && node._aabbMaxY > aabb._minY && node._aabbMinZ < aabb._maxZ && node._aabbMaxZ > aabb._minZ)) { + return; + } + if(node._height == 0) { + callback.process(node._proxy); + return; + } + this.aabbTestRecursive(node._children[0],aabb,callback); + this.aabbTestRecursive(node._children[1],aabb,callback); + } + createProxy(userData,aabb) { + let p = new oimo.collision.broadphase.bvh.BvhProxy(userData,this._idCount++); + this._numProxies++; + if(this._proxyList == null) { + this._proxyList = p; + this._proxyListLast = p; + } else { + this._proxyListLast._next = p; + p._prev = this._proxyListLast; + this._proxyListLast = p; + } + p._aabbMinX = aabb._minX; + p._aabbMinY = aabb._minY; + p._aabbMinZ = aabb._minZ; + p._aabbMaxX = aabb._maxX; + p._aabbMaxY = aabb._maxY; + p._aabbMaxZ = aabb._maxZ; + let padding = oimo.common.Setting.bvhProxyPadding; + p._aabbMinX -= padding; + p._aabbMinY -= padding; + p._aabbMinZ -= padding; + p._aabbMaxX += padding; + p._aabbMaxY += padding; + p._aabbMaxZ += padding; + let _this = this._tree; + let first = _this._nodePool; + if(first != null) { + _this._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let leaf = first; + leaf._proxy = p; + p._leaf = leaf; + leaf._aabbMinX = p._aabbMinX; + leaf._aabbMinY = p._aabbMinY; + leaf._aabbMinZ = p._aabbMinZ; + leaf._aabbMaxX = p._aabbMaxX; + leaf._aabbMaxY = p._aabbMaxY; + leaf._aabbMaxZ = p._aabbMaxZ; + _this._numLeaves++; + if(_this.leafList == null) { + _this.leafList = leaf; + _this.leafListLast = leaf; + } else { + _this.leafListLast._nextLeaf = leaf; + leaf._prevLeaf = _this.leafListLast; + _this.leafListLast = leaf; + } + if(_this._root == null) { + _this._root = leaf; + } else { + let sibling = _this._root; + while(sibling._height > 0) { + let nextStep = _this._strategy._decideInsertion(sibling,leaf); + if(nextStep == -1) { + break; + } else { + sibling = sibling._children[nextStep]; + } + } + let parent = sibling._parent; + let first = _this._nodePool; + if(first != null) { + _this._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let node = first; + if(parent == null) { + _this._root = node; + } else { + let index = sibling._childIndex; + parent._children[index] = node; + node._parent = parent; + node._childIndex = index; + } + let index = sibling._childIndex; + node._children[index] = sibling; + sibling._parent = node; + sibling._childIndex = index; + let index1 = sibling._childIndex ^ 1; + node._children[index1] = leaf; + leaf._parent = node; + leaf._childIndex = index1; + while(node != null) { + if(_this._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + if(!p._moved) { + p._moved = true; + if(this.movedProxies.length == this.numMovedProxies) { + let newArray = new Array(this.numMovedProxies << 1); + let _g = 0; + let _g1 = this.numMovedProxies; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.movedProxies[i]; + this.movedProxies[i] = null; + } + this.movedProxies = newArray; + } + this.movedProxies[this.numMovedProxies++] = p; + } + return p; + } + destroyProxy(proxy) { + this._numProxies--; + let prev = proxy._prev; + let next = proxy._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(proxy == this._proxyList) { + this._proxyList = this._proxyList._next; + } + if(proxy == this._proxyListLast) { + this._proxyListLast = this._proxyListLast._prev; + } + proxy._next = null; + proxy._prev = null; + let bvhProxy = proxy; + let _this = this._tree; + let leaf = bvhProxy._leaf; + _this._numLeaves--; + let prev1 = leaf._prevLeaf; + let next1 = leaf._nextLeaf; + if(prev1 != null) { + prev1._nextLeaf = next1; + } + if(next1 != null) { + next1._prevLeaf = prev1; + } + if(leaf == _this.leafList) { + _this.leafList = _this.leafList._nextLeaf; + } + if(leaf == _this.leafListLast) { + _this.leafListLast = _this.leafListLast._prevLeaf; + } + leaf._nextLeaf = null; + leaf._prevLeaf = null; + if(_this._root == leaf) { + _this._root = null; + } else { + let parent = leaf._parent; + let sibling = parent._children[leaf._childIndex ^ 1]; + let grandParent = parent._parent; + if(grandParent == null) { + sibling._parent = null; + sibling._childIndex = 0; + _this._root = sibling; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + } else { + sibling._parent = grandParent; + let index = parent._childIndex; + grandParent._children[index] = sibling; + sibling._parent = grandParent; + sibling._childIndex = index; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + let node = grandParent; + while(node != null) { + if(_this._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + } + bvhProxy._leaf = null; + leaf._next = null; + leaf._childIndex = 0; + leaf._children[0] = null; + leaf._children[1] = null; + leaf._childIndex = 0; + leaf._parent = null; + leaf._height = 0; + leaf._proxy = null; + leaf._next = _this._nodePool; + _this._nodePool = leaf; + bvhProxy.userData = null; + bvhProxy._next = null; + bvhProxy._prev = null; + if(bvhProxy._moved) { + bvhProxy._moved = false; + } + } + moveProxy(proxy,aabb,displacement) { + let p = proxy; + if(p._aabbMinX <= aabb._minX && p._aabbMaxX >= aabb._maxX && p._aabbMinY <= aabb._minY && p._aabbMaxY >= aabb._maxY && p._aabbMinZ <= aabb._minZ && p._aabbMaxZ >= aabb._maxZ) { + return; + } + p._aabbMinX = aabb._minX; + p._aabbMinY = aabb._minY; + p._aabbMinZ = aabb._minZ; + p._aabbMaxX = aabb._maxX; + p._aabbMaxY = aabb._maxY; + p._aabbMaxZ = aabb._maxZ; + let padding = oimo.common.Setting.bvhProxyPadding; + p._aabbMinX -= padding; + p._aabbMinY -= padding; + p._aabbMinZ -= padding; + p._aabbMaxX += padding; + p._aabbMaxY += padding; + p._aabbMaxZ += padding; + if(displacement != null) { + let dX; + let dY; + let dZ; + let zeroX; + let zeroY; + let zeroZ; + let addToMinX; + let addToMinY; + let addToMinZ; + let addToMaxX; + let addToMaxY; + let addToMaxZ; + zeroX = 0; + zeroY = 0; + zeroZ = 0; + dX = displacement.x; + dY = displacement.y; + dZ = displacement.z; + addToMinX = zeroX < dX ? zeroX : dX; + addToMinY = zeroY < dY ? zeroY : dY; + addToMinZ = zeroZ < dZ ? zeroZ : dZ; + addToMaxX = zeroX > dX ? zeroX : dX; + addToMaxY = zeroY > dY ? zeroY : dY; + addToMaxZ = zeroZ > dZ ? zeroZ : dZ; + p._aabbMinX += addToMinX; + p._aabbMinY += addToMinY; + p._aabbMinZ += addToMinZ; + p._aabbMaxX += addToMaxX; + p._aabbMaxY += addToMaxY; + p._aabbMaxZ += addToMaxZ; + } + if(!p._moved) { + p._moved = true; + if(this.movedProxies.length == this.numMovedProxies) { + let newArray = new Array(this.numMovedProxies << 1); + let _g = 0; + let _g1 = this.numMovedProxies; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.movedProxies[i]; + this.movedProxies[i] = null; + } + this.movedProxies = newArray; + } + this.movedProxies[this.numMovedProxies++] = p; + } + } + collectPairs() { + let p = this._proxyPairList; + if(p != null) { + while(true) { + p._p1 = null; + p._p2 = null; + p = p._next; + if(!(p != null)) { + break; + } + } + this._proxyPairList._next = this._proxyPairPool; + this._proxyPairPool = this._proxyPairList; + this._proxyPairList = null; + } + this._testCount = 0; + if(this._numProxies < 2) { + return; + } + let incrementalCollision = this.numMovedProxies / this._numProxies < oimo.common.Setting.bvhIncrementalCollisionThreshold; + let _g = 0; + let _g1 = this.numMovedProxies; + while(_g < _g1) { + let i = _g++; + let p = this.movedProxies[i]; + if(p._moved) { + let _this = this._tree; + let leaf = p._leaf; + _this._numLeaves--; + let prev = leaf._prevLeaf; + let next = leaf._nextLeaf; + if(prev != null) { + prev._nextLeaf = next; + } + if(next != null) { + next._prevLeaf = prev; + } + if(leaf == _this.leafList) { + _this.leafList = _this.leafList._nextLeaf; + } + if(leaf == _this.leafListLast) { + _this.leafListLast = _this.leafListLast._prevLeaf; + } + leaf._nextLeaf = null; + leaf._prevLeaf = null; + if(_this._root == leaf) { + _this._root = null; + } else { + let parent = leaf._parent; + let sibling = parent._children[leaf._childIndex ^ 1]; + let grandParent = parent._parent; + if(grandParent == null) { + sibling._parent = null; + sibling._childIndex = 0; + _this._root = sibling; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + } else { + sibling._parent = grandParent; + let index = parent._childIndex; + grandParent._children[index] = sibling; + sibling._parent = grandParent; + sibling._childIndex = index; + parent._next = null; + parent._childIndex = 0; + parent._children[0] = null; + parent._children[1] = null; + parent._childIndex = 0; + parent._parent = null; + parent._height = 0; + parent._proxy = null; + parent._next = _this._nodePool; + _this._nodePool = parent; + let node = grandParent; + while(node != null) { + if(_this._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + } + p._leaf = null; + leaf._next = null; + leaf._childIndex = 0; + leaf._children[0] = null; + leaf._children[1] = null; + leaf._childIndex = 0; + leaf._parent = null; + leaf._height = 0; + leaf._proxy = null; + leaf._next = _this._nodePool; + _this._nodePool = leaf; + let _this1 = this._tree; + let first = _this1._nodePool; + if(first != null) { + _this1._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let leaf1 = first; + leaf1._proxy = p; + p._leaf = leaf1; + leaf1._aabbMinX = p._aabbMinX; + leaf1._aabbMinY = p._aabbMinY; + leaf1._aabbMinZ = p._aabbMinZ; + leaf1._aabbMaxX = p._aabbMaxX; + leaf1._aabbMaxY = p._aabbMaxY; + leaf1._aabbMaxZ = p._aabbMaxZ; + _this1._numLeaves++; + if(_this1.leafList == null) { + _this1.leafList = leaf1; + _this1.leafListLast = leaf1; + } else { + _this1.leafListLast._nextLeaf = leaf1; + leaf1._prevLeaf = _this1.leafListLast; + _this1.leafListLast = leaf1; + } + if(_this1._root == null) { + _this1._root = leaf1; + } else { + let sibling = _this1._root; + while(sibling._height > 0) { + let nextStep = _this1._strategy._decideInsertion(sibling,leaf1); + if(nextStep == -1) { + break; + } else { + sibling = sibling._children[nextStep]; + } + } + let parent = sibling._parent; + let first = _this1._nodePool; + if(first != null) { + _this1._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let node = first; + if(parent == null) { + _this1._root = node; + } else { + let index = sibling._childIndex; + parent._children[index] = node; + node._parent = parent; + node._childIndex = index; + } + let index = sibling._childIndex; + node._children[index] = sibling; + sibling._parent = node; + sibling._childIndex = index; + let index1 = sibling._childIndex ^ 1; + node._children[index1] = leaf1; + leaf1._parent = node; + leaf1._childIndex = index1; + while(node != null) { + if(_this1._strategy._balancingEnabled) { + if(node._height >= 2) { + let p = node._parent; + let l = node._children[0]; + let r = node._children[1]; + let balance = l._height - r._height; + let nodeIndex = node._childIndex; + if(balance > 1) { + let ll = l._children[0]; + let lr = l._children[1]; + if(ll._height > lr._height) { + l._children[1] = node; + node._parent = l; + node._childIndex = 1; + node._children[0] = lr; + lr._parent = node; + lr._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + l._children[0] = node; + node._parent = l; + node._childIndex = 0; + node._children[0] = ll; + ll._parent = node; + ll._childIndex = 0; + let c1 = l._children[0]; + let c2 = l._children[1]; + l._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + l._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + l._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + l._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + l._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + l._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = l._children[0]._height; + let h2 = l._children[1]._height; + l._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = l; + l._parent = p; + l._childIndex = nodeIndex; + } else { + _this1._root = l; + l._parent = null; + } + node = l; + } else if(balance < -1) { + let rl = r._children[0]; + let rr = r._children[1]; + if(rl._height > rr._height) { + r._children[1] = node; + node._parent = r; + node._childIndex = 1; + node._children[1] = rr; + rr._parent = node; + rr._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } else { + r._children[0] = node; + node._parent = r; + node._childIndex = 0; + node._children[1] = rl; + rl._parent = node; + rl._childIndex = 1; + let c1 = r._children[0]; + let c2 = r._children[1]; + r._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + r._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + r._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + r._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + r._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + r._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = r._children[0]._height; + let h2 = r._children[1]._height; + r._height = (h1 > h2 ? h1 : h2) + 1; + let c11 = node._children[0]; + let c21 = node._children[1]; + node._aabbMinX = c11._aabbMinX < c21._aabbMinX ? c11._aabbMinX : c21._aabbMinX; + node._aabbMinY = c11._aabbMinY < c21._aabbMinY ? c11._aabbMinY : c21._aabbMinY; + node._aabbMinZ = c11._aabbMinZ < c21._aabbMinZ ? c11._aabbMinZ : c21._aabbMinZ; + node._aabbMaxX = c11._aabbMaxX > c21._aabbMaxX ? c11._aabbMaxX : c21._aabbMaxX; + node._aabbMaxY = c11._aabbMaxY > c21._aabbMaxY ? c11._aabbMaxY : c21._aabbMaxY; + node._aabbMaxZ = c11._aabbMaxZ > c21._aabbMaxZ ? c11._aabbMaxZ : c21._aabbMaxZ; + let h11 = node._children[0]._height; + let h21 = node._children[1]._height; + node._height = (h11 > h21 ? h11 : h21) + 1; + } + if(p != null) { + p._children[nodeIndex] = r; + r._parent = p; + r._childIndex = nodeIndex; + } else { + _this1._root = r; + r._parent = null; + } + node = r; + } + } + } + let h1 = node._children[0]._height; + let h2 = node._children[1]._height; + node._height = (h1 > h2 ? h1 : h2) + 1; + let c1 = node._children[0]; + let c2 = node._children[1]; + node._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + node._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + node._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + node._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + node._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + node._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + node = node._parent; + } + } + if(incrementalCollision) { + this.collide(this._tree._root,p._leaf); + } + p._moved = false; + } + this.movedProxies[i] = null; + } + if(!incrementalCollision) { + this.collide(this._tree._root,this._tree._root); + } + this.numMovedProxies = 0; + } + rayCast(begin,end,callback) { + if(this._tree._root == null) { + return; + } + let p1X; + let p1Y; + let p1Z; + let p2X; + let p2Y; + let p2Z; + p1X = begin.x; + p1Y = begin.y; + p1Z = begin.z; + p2X = end.x; + p2Y = end.y; + p2Z = end.z; + this.rayCastRecursive(this._tree._root,p1X,p1Y,p1Z,p2X,p2Y,p2Z,callback); + } + convexCast(convex,begin,translation,callback) { + if(this._tree._root == null) { + return; + } + this.convexCastRecursive(this._tree._root,convex,begin,translation,callback); + } + aabbTest(aabb,callback) { + if(this._tree._root == null) { + return; + } + this.aabbTestRecursive(this._tree._root,aabb,callback); + } + getTreeBalance() { + return this._tree._getBalance(); + } +} +oimo.collision.broadphase.bvh.BvhInsertionStrategy = class oimo_collision_broadphase_bvh_BvhInsertionStrategy { +} +oimo.collision.broadphase.bvh.BvhNode = class oimo_collision_broadphase_bvh_BvhNode { + constructor() { + this._next = null; + this._prevLeaf = null; + this._nextLeaf = null; + this._children = new Array(2); + this._childIndex = 0; + this._parent = null; + this._height = 0; + this._proxy = null; + this._aabbMinX = 0; + this._aabbMinY = 0; + this._aabbMinZ = 0; + this._aabbMaxX = 0; + this._aabbMaxY = 0; + this._aabbMaxZ = 0; + } +} +oimo.collision.broadphase.bvh.BvhProxy = class oimo_collision_broadphase_bvh_BvhProxy extends oimo.collision.broadphase.Proxy { + constructor(userData,id) { + super(userData,id); + this._leaf = null; + this._moved = false; + } +} +oimo.collision.broadphase.bvh.BvhStrategy = class oimo_collision_broadphase_bvh_BvhStrategy { + constructor() { + this._insertionStrategy = 0; + this._balancingEnabled = false; + } + _decideInsertion(currentNode,leaf) { + switch(this._insertionStrategy) { + case 0: + let centerX; + let centerY; + let centerZ; + centerX = leaf._aabbMinX + leaf._aabbMaxX; + centerY = leaf._aabbMinY + leaf._aabbMaxY; + centerZ = leaf._aabbMinZ + leaf._aabbMaxZ; + let c1 = currentNode._children[0]; + let c2 = currentNode._children[1]; + let diff1X; + let diff1Y; + let diff1Z; + let diff2X; + let diff2Y; + let diff2Z; + diff1X = c1._aabbMinX + c1._aabbMaxX; + diff1Y = c1._aabbMinY + c1._aabbMaxY; + diff1Z = c1._aabbMinZ + c1._aabbMaxZ; + diff2X = c2._aabbMinX + c2._aabbMaxX; + diff2Y = c2._aabbMinY + c2._aabbMaxY; + diff2Z = c2._aabbMinZ + c2._aabbMaxZ; + diff1X -= centerX; + diff1Y -= centerY; + diff1Z -= centerZ; + diff2X -= centerX; + diff2Y -= centerY; + diff2Z -= centerZ; + if(diff1X * diff1X + diff1Y * diff1Y + diff1Z * diff1Z < diff2X * diff2X + diff2Y * diff2Y + diff2Z * diff2Z) { + return 0; + } else { + return 1; + } + break; + case 1: + let c11 = currentNode._children[0]; + let c21 = currentNode._children[1]; + let ey = currentNode._aabbMaxY - currentNode._aabbMinY; + let ez = currentNode._aabbMaxZ - currentNode._aabbMinZ; + let combinedMinX; + let combinedMinY; + let combinedMinZ; + let combinedMaxX; + let combinedMaxY; + let combinedMaxZ; + combinedMinX = currentNode._aabbMinX < leaf._aabbMinX ? currentNode._aabbMinX : leaf._aabbMinX; + combinedMinY = currentNode._aabbMinY < leaf._aabbMinY ? currentNode._aabbMinY : leaf._aabbMinY; + combinedMinZ = currentNode._aabbMinZ < leaf._aabbMinZ ? currentNode._aabbMinZ : leaf._aabbMinZ; + combinedMaxX = currentNode._aabbMaxX > leaf._aabbMaxX ? currentNode._aabbMaxX : leaf._aabbMaxX; + combinedMaxY = currentNode._aabbMaxY > leaf._aabbMaxY ? currentNode._aabbMaxY : leaf._aabbMaxY; + combinedMaxZ = currentNode._aabbMaxZ > leaf._aabbMaxZ ? currentNode._aabbMaxZ : leaf._aabbMaxZ; + let ey1 = combinedMaxY - combinedMinY; + let ez1 = combinedMaxZ - combinedMinZ; + let newArea = ((combinedMaxX - combinedMinX) * (ey1 + ez1) + ey1 * ez1) * 2; + let creatingCost = newArea * 2; + let incrementalCost = (newArea - ((currentNode._aabbMaxX - currentNode._aabbMinX) * (ey + ez) + ey * ez) * 2) * 2; + let descendingCost1; + combinedMinX = c11._aabbMinX < leaf._aabbMinX ? c11._aabbMinX : leaf._aabbMinX; + combinedMinY = c11._aabbMinY < leaf._aabbMinY ? c11._aabbMinY : leaf._aabbMinY; + combinedMinZ = c11._aabbMinZ < leaf._aabbMinZ ? c11._aabbMinZ : leaf._aabbMinZ; + combinedMaxX = c11._aabbMaxX > leaf._aabbMaxX ? c11._aabbMaxX : leaf._aabbMaxX; + combinedMaxY = c11._aabbMaxY > leaf._aabbMaxY ? c11._aabbMaxY : leaf._aabbMaxY; + combinedMaxZ = c11._aabbMaxZ > leaf._aabbMaxZ ? c11._aabbMaxZ : leaf._aabbMaxZ; + if(c11._height == 0) { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + descendingCost1 = incrementalCost + ((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2; + } else { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + let ey1 = c11._aabbMaxY - c11._aabbMinY; + let ez1 = c11._aabbMaxZ - c11._aabbMinZ; + descendingCost1 = incrementalCost + (((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2 - ((c11._aabbMaxX - c11._aabbMinX) * (ey1 + ez1) + ey1 * ez1) * 2); + } + let descendingCost2; + combinedMinX = c21._aabbMinX < leaf._aabbMinX ? c21._aabbMinX : leaf._aabbMinX; + combinedMinY = c21._aabbMinY < leaf._aabbMinY ? c21._aabbMinY : leaf._aabbMinY; + combinedMinZ = c21._aabbMinZ < leaf._aabbMinZ ? c21._aabbMinZ : leaf._aabbMinZ; + combinedMaxX = c21._aabbMaxX > leaf._aabbMaxX ? c21._aabbMaxX : leaf._aabbMaxX; + combinedMaxY = c21._aabbMaxY > leaf._aabbMaxY ? c21._aabbMaxY : leaf._aabbMaxY; + combinedMaxZ = c21._aabbMaxZ > leaf._aabbMaxZ ? c21._aabbMaxZ : leaf._aabbMaxZ; + if(c21._height == 0) { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + descendingCost2 = incrementalCost + ((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2; + } else { + let ey = combinedMaxY - combinedMinY; + let ez = combinedMaxZ - combinedMinZ; + let ey1 = c21._aabbMaxY - c21._aabbMinY; + let ez1 = c21._aabbMaxZ - c21._aabbMinZ; + descendingCost2 = incrementalCost + (((combinedMaxX - combinedMinX) * (ey + ez) + ey * ez) * 2 - ((c21._aabbMaxX - c21._aabbMinX) * (ey1 + ez1) + ey1 * ez1) * 2); + } + if(creatingCost < descendingCost1) { + if(creatingCost < descendingCost2) { + return -1; + } else { + return 1; + } + } else if(descendingCost1 < descendingCost2) { + return 0; + } else { + return 1; + } + break; + default: + console.log("src/oimo/collision/broadphase/bvh/BvhStrategy.hx:37:","invalid BVH insertion strategy: " + this._insertionStrategy); + return -1; + } + } + _splitLeaves(leaves,from,until) { + let invN = 1.0 / (until - from); + let centerMeanX; + let centerMeanY; + let centerMeanZ; + centerMeanX = 0; + centerMeanY = 0; + centerMeanZ = 0; + let _g = from; + while(_g < until) { + let leaf = leaves[_g++]; + leaf._tmpX = leaf._aabbMaxX + leaf._aabbMinX; + leaf._tmpY = leaf._aabbMaxY + leaf._aabbMinY; + leaf._tmpZ = leaf._aabbMaxZ + leaf._aabbMinZ; + centerMeanX += leaf._tmpX; + centerMeanY += leaf._tmpY; + centerMeanZ += leaf._tmpZ; + } + centerMeanX *= invN; + centerMeanY *= invN; + centerMeanZ *= invN; + let varianceX; + let varianceY; + let varianceZ; + varianceX = 0; + varianceY = 0; + varianceZ = 0; + let _g1 = from; + while(_g1 < until) { + let leaf = leaves[_g1++]; + let diffX; + let diffY; + let diffZ; + diffX = leaf._tmpX - centerMeanX; + diffY = leaf._tmpY - centerMeanY; + diffZ = leaf._tmpZ - centerMeanZ; + diffX *= diffX; + diffY *= diffY; + diffZ *= diffZ; + varianceX += diffX; + varianceY += diffY; + varianceZ += diffZ; + } + let varX = varianceX; + let varY = varianceY; + let varZ = varianceZ; + let l = from; + let r = until - 1; + if(varX > varY) { + if(varX > varZ) { + let mean = centerMeanX; + while(true) { + while(!(leaves[l]._tmpX <= mean)) ++l; + while(!(leaves[r]._tmpX >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } else { + let mean = centerMeanZ; + while(true) { + while(!(leaves[l]._tmpZ <= mean)) ++l; + while(!(leaves[r]._tmpZ >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } + } else if(varY > varZ) { + let mean = centerMeanY; + while(true) { + while(!(leaves[l]._tmpY <= mean)) ++l; + while(!(leaves[r]._tmpY >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } else { + let mean = centerMeanZ; + while(true) { + while(!(leaves[l]._tmpZ <= mean)) ++l; + while(!(leaves[r]._tmpZ >= mean)) --r; + if(l >= r) { + break; + } + let tmp = leaves[l]; + leaves[l] = leaves[r]; + leaves[r] = tmp; + ++l; + --r; + } + } + return l; + } +} +oimo.collision.broadphase.bvh.BvhTree = class oimo_collision_broadphase_bvh_BvhTree { + constructor() { + this._root = null; + this._numLeaves = 0; + this._strategy = new oimo.collision.broadphase.bvh.BvhStrategy(); + this._nodePool = null; + this.leafList = null; + this.leafListLast = null; + this.tmp = new Array(1024); + } + _print(root,indent) { + if(indent == null) { + indent = ""; + } + if(root == null) { + return; + } + if(root._height == 0) { + console.log("src/oimo/collision/broadphase/bvh/BvhTree.hx:39:",indent + root._proxy._id); + } else { + this._print(root._children[0],indent + " "); + let tmp; + let sizeX; + let sizeY; + let sizeZ; + sizeX = root._aabbMaxX - root._aabbMinX; + sizeY = root._aabbMaxY - root._aabbMinY; + sizeZ = root._aabbMaxZ - root._aabbMinZ; + let y = sizeY; + let z = sizeZ; + if(sizeX * (y + z) + y * z > 0) { + let sizeX; + let sizeY; + let sizeZ; + sizeX = root._aabbMaxX - root._aabbMinX; + sizeY = root._aabbMaxY - root._aabbMinY; + sizeZ = root._aabbMaxZ - root._aabbMinZ; + let y = sizeY; + let z = sizeZ; + tmp = ((sizeX * (y + z) + y * z) * 1000 + 0.5 | 0) / 1000; + } else { + let sizeX; + let sizeY; + let sizeZ; + sizeX = root._aabbMaxX - root._aabbMinX; + sizeY = root._aabbMaxY - root._aabbMinY; + sizeZ = root._aabbMaxZ - root._aabbMinZ; + let y = sizeY; + let z = sizeZ; + tmp = ((sizeX * (y + z) + y * z) * 1000 - 0.5 | 0) / 1000; + } + console.log("src/oimo/collision/broadphase/bvh/BvhTree.hx:42:",indent + "#" + root._height + ", " + tmp); + this._print(root._children[1],indent + " "); + } + } + _getBalance() { + return this.getBalanceRecursive(this._root); + } + deleteRecursive(root) { + if(root._height == 0) { + let prev = root._prevLeaf; + let next = root._nextLeaf; + if(prev != null) { + prev._nextLeaf = next; + } + if(next != null) { + next._prevLeaf = prev; + } + if(root == this.leafList) { + this.leafList = this.leafList._nextLeaf; + } + if(root == this.leafListLast) { + this.leafListLast = this.leafListLast._prevLeaf; + } + root._nextLeaf = null; + root._prevLeaf = null; + root._proxy._leaf = null; + root._next = null; + root._childIndex = 0; + root._children[0] = null; + root._children[1] = null; + root._childIndex = 0; + root._parent = null; + root._height = 0; + root._proxy = null; + root._next = this._nodePool; + this._nodePool = root; + return; + } + this.deleteRecursive(root._children[0]); + this.deleteRecursive(root._children[1]); + root._next = null; + root._childIndex = 0; + root._children[0] = null; + root._children[1] = null; + root._childIndex = 0; + root._parent = null; + root._height = 0; + root._proxy = null; + root._next = this._nodePool; + this._nodePool = root; + } + decomposeRecursive(root) { + if(root._height == 0) { + root._childIndex = 0; + root._parent = null; + return; + } + this.decomposeRecursive(root._children[0]); + this.decomposeRecursive(root._children[1]); + root._next = null; + root._childIndex = 0; + root._children[0] = null; + root._children[1] = null; + root._childIndex = 0; + root._parent = null; + root._height = 0; + root._proxy = null; + root._next = this._nodePool; + this._nodePool = root; + } + buildTopDownRecursive(leaves,from,until) { + if(until - from == 1) { + let leaf = leaves[from]; + let proxy = leaf._proxy; + leaf._aabbMinX = proxy._aabbMinX; + leaf._aabbMinY = proxy._aabbMinY; + leaf._aabbMinZ = proxy._aabbMinZ; + leaf._aabbMaxX = proxy._aabbMaxX; + leaf._aabbMaxY = proxy._aabbMaxY; + leaf._aabbMaxZ = proxy._aabbMaxZ; + return leaf; + } + let splitAt = this._strategy._splitLeaves(leaves,from,until); + let child1 = this.buildTopDownRecursive(leaves,from,splitAt); + let child2 = this.buildTopDownRecursive(leaves,splitAt,until); + let first = this._nodePool; + if(first != null) { + this._nodePool = first._next; + first._next = null; + } else { + first = new oimo.collision.broadphase.bvh.BvhNode(); + } + let parent = first; + parent._children[0] = child1; + child1._parent = parent; + child1._childIndex = 0; + parent._children[1] = child2; + child2._parent = parent; + child2._childIndex = 1; + let c1 = parent._children[0]; + let c2 = parent._children[1]; + parent._aabbMinX = c1._aabbMinX < c2._aabbMinX ? c1._aabbMinX : c2._aabbMinX; + parent._aabbMinY = c1._aabbMinY < c2._aabbMinY ? c1._aabbMinY : c2._aabbMinY; + parent._aabbMinZ = c1._aabbMinZ < c2._aabbMinZ ? c1._aabbMinZ : c2._aabbMinZ; + parent._aabbMaxX = c1._aabbMaxX > c2._aabbMaxX ? c1._aabbMaxX : c2._aabbMaxX; + parent._aabbMaxY = c1._aabbMaxY > c2._aabbMaxY ? c1._aabbMaxY : c2._aabbMaxY; + parent._aabbMaxZ = c1._aabbMaxZ > c2._aabbMaxZ ? c1._aabbMaxZ : c2._aabbMaxZ; + let h1 = parent._children[0]._height; + let h2 = parent._children[1]._height; + parent._height = (h1 > h2 ? h1 : h2) + 1; + return parent; + } + getBalanceRecursive(root) { + if(root == null || root._height == 0) { + return 0; + } + let balance = root._children[0]._height - root._children[1]._height; + if(balance < 0) { + balance = -balance; + } + return balance + this.getBalanceRecursive(root._children[0]) + this.getBalanceRecursive(root._children[1]); + } +} +oimo.collision.geometry.Aabb = class oimo_collision_geometry_Aabb { + constructor() { + this._minX = 0; + this._minY = 0; + this._minZ = 0; + this._maxX = 0; + this._maxY = 0; + this._maxZ = 0; + } + init(min,max) { + this._minX = min.x; + this._minY = min.y; + this._minZ = min.z; + this._maxX = max.x; + this._maxY = max.y; + this._maxZ = max.z; + return this; + } + getMin() { + let min = new oimo.common.Vec3(); + min.x = this._minX; + min.y = this._minY; + min.z = this._minZ; + return min; + } + getMinTo(min) { + min.x = this._minX; + min.y = this._minY; + min.z = this._minZ; + } + setMin(min) { + this._minX = min.x; + this._minY = min.y; + this._minZ = min.z; + return this; + } + getMax() { + let max = new oimo.common.Vec3(); + max.x = this._maxX; + max.y = this._maxY; + max.z = this._maxZ; + return max; + } + getMaxTo(max) { + max.x = this._maxX; + max.y = this._maxY; + max.z = this._maxZ; + } + setMax(max) { + this._maxX = max.x; + this._maxY = max.y; + this._maxZ = max.z; + return this; + } + getCenter() { + let v = new oimo.common.Vec3(); + let cX; + let cY; + let cZ; + cX = this._minX + this._maxX; + cY = this._minY + this._maxY; + cZ = this._minZ + this._maxZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + v.x = cX; + v.y = cY; + v.z = cZ; + return v; + } + getCenterTo(center) { + let cX; + let cY; + let cZ; + cX = this._minX + this._maxX; + cY = this._minY + this._maxY; + cZ = this._minZ + this._maxZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + center.x = cX; + center.y = cY; + center.z = cZ; + } + getExtents() { + let v = new oimo.common.Vec3(); + let cX; + let cY; + let cZ; + cX = this._maxX - this._minX; + cY = this._maxY - this._minY; + cZ = this._maxZ - this._minZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + v.x = cX; + v.y = cY; + v.z = cZ; + return v; + } + getExtentsTo(halfExtents) { + let cX; + let cY; + let cZ; + cX = this._maxX - this._minX; + cY = this._maxY - this._minY; + cZ = this._maxZ - this._minZ; + cX *= 0.5; + cY *= 0.5; + cZ *= 0.5; + halfExtents.x = cX; + halfExtents.y = cY; + halfExtents.z = cZ; + } + combine(other) { + this._minX = this._minX < other._minX ? this._minX : other._minX; + this._minY = this._minY < other._minY ? this._minY : other._minY; + this._minZ = this._minZ < other._minZ ? this._minZ : other._minZ; + this._maxX = this._maxX > other._maxX ? this._maxX : other._maxX; + this._maxY = this._maxY > other._maxY ? this._maxY : other._maxY; + this._maxZ = this._maxZ > other._maxZ ? this._maxZ : other._maxZ; + return this; + } + combined(other) { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._minX < other._minX ? this._minX : other._minX; + aabb._minY = this._minY < other._minY ? this._minY : other._minY; + aabb._minZ = this._minZ < other._minZ ? this._minZ : other._minZ; + aabb._maxX = this._maxX > other._maxX ? this._maxX : other._maxX; + aabb._maxY = this._maxY > other._maxY ? this._maxY : other._maxY; + aabb._maxZ = this._maxZ > other._maxZ ? this._maxZ : other._maxZ; + return aabb; + } + overlap(other) { + if(this._minX < other._maxX && this._maxX > other._minX && this._minY < other._maxY && this._maxY > other._minY && this._minZ < other._maxZ) { + return this._maxZ > other._minZ; + } else { + return false; + } + } + getIntersection(other) { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._minX > other._minX ? this._minX : other._minX; + aabb._minY = this._minY > other._minY ? this._minY : other._minY; + aabb._minZ = this._minZ > other._minZ ? this._minZ : other._minZ; + aabb._maxX = this._maxX < other._maxX ? this._maxX : other._maxX; + aabb._maxY = this._maxY < other._maxY ? this._maxY : other._maxY; + aabb._maxZ = this._maxZ < other._maxZ ? this._maxZ : other._maxZ; + return aabb; + } + getIntersectionTo(other,intersection) { + intersection._minX = this._minX > other._minX ? this._minX : other._minX; + intersection._minY = this._minY > other._minY ? this._minY : other._minY; + intersection._minZ = this._minZ > other._minZ ? this._minZ : other._minZ; + intersection._maxX = this._maxX < other._maxX ? this._maxX : other._maxX; + intersection._maxY = this._maxY < other._maxY ? this._maxY : other._maxY; + intersection._maxZ = this._maxZ < other._maxZ ? this._maxZ : other._maxZ; + } + copyFrom(aabb) { + this._minX = aabb._minX; + this._minY = aabb._minY; + this._minZ = aabb._minZ; + this._maxX = aabb._maxX; + this._maxY = aabb._maxY; + this._maxZ = aabb._maxZ; + return this; + } + clone() { + let aabb = new oimo.collision.geometry.Aabb(); + aabb._minX = this._minX; + aabb._minY = this._minY; + aabb._minZ = this._minZ; + aabb._maxX = this._maxX; + aabb._maxY = this._maxY; + aabb._maxZ = this._maxZ; + return aabb; + } +} +oimo.collision.geometry.BoxGeometry = class oimo_collision_geometry_BoxGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(halfExtents) { + super(1); + this._halfExtentsX = halfExtents.x; + this._halfExtentsY = halfExtents.y; + this._halfExtentsZ = halfExtents.z; + this._halfAxisXX = halfExtents.x; + this._halfAxisXY = 0; + this._halfAxisXZ = 0; + this._halfAxisYX = 0; + this._halfAxisYY = halfExtents.y; + this._halfAxisYZ = 0; + this._halfAxisZX = 0; + this._halfAxisZY = 0; + this._halfAxisZZ = halfExtents.z; + this._updateMass(); + let minHalfExtents = halfExtents.x < halfExtents.y ? halfExtents.z < halfExtents.x ? halfExtents.z : halfExtents.x : halfExtents.z < halfExtents.y ? halfExtents.z : halfExtents.y; + if(this._gjkMargin > minHalfExtents * 0.2) { + this._gjkMargin = minHalfExtents * 0.2; + } + } + getHalfExtents() { + let v = new oimo.common.Vec3(); + v.x = this._halfExtentsX; + v.y = this._halfExtentsY; + v.z = this._halfExtentsZ; + return v; + } + getHalfExtentsTo(halfExtents) { + halfExtents.x = this._halfExtentsX; + halfExtents.y = this._halfExtentsY; + halfExtents.z = this._halfExtentsZ; + } + _updateMass() { + this._volume = 8 * (this._halfExtentsX * this._halfExtentsY * this._halfExtentsZ); + let sqX; + let sqY; + let sqZ; + sqX = this._halfExtentsX * this._halfExtentsX; + sqY = this._halfExtentsY * this._halfExtentsY; + sqZ = this._halfExtentsZ * this._halfExtentsZ; + this._inertiaCoeff00 = 0.33333333333333331 * (sqY + sqZ); + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.33333333333333331 * (sqZ + sqX); + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.33333333333333331 * (sqX + sqY); + } + _computeAabb(aabb,tf) { + let tfxX; + let tfxY; + let tfxZ; + let tfyX; + let tfyY; + let tfyZ; + let tfzX; + let tfzY; + let tfzZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * this._halfAxisXX + tf._rotation01 * this._halfAxisXY + tf._rotation02 * this._halfAxisXZ; + __tmp__Y = tf._rotation10 * this._halfAxisXX + tf._rotation11 * this._halfAxisXY + tf._rotation12 * this._halfAxisXZ; + __tmp__Z = tf._rotation20 * this._halfAxisXX + tf._rotation21 * this._halfAxisXY + tf._rotation22 * this._halfAxisXZ; + tfxX = __tmp__X; + tfxY = __tmp__Y; + tfxZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf._rotation00 * this._halfAxisYX + tf._rotation01 * this._halfAxisYY + tf._rotation02 * this._halfAxisYZ; + __tmp__Y1 = tf._rotation10 * this._halfAxisYX + tf._rotation11 * this._halfAxisYY + tf._rotation12 * this._halfAxisYZ; + __tmp__Z1 = tf._rotation20 * this._halfAxisYX + tf._rotation21 * this._halfAxisYY + tf._rotation22 * this._halfAxisYZ; + tfyX = __tmp__X1; + tfyY = __tmp__Y1; + tfyZ = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf._rotation00 * this._halfAxisZX + tf._rotation01 * this._halfAxisZY + tf._rotation02 * this._halfAxisZZ; + __tmp__Y2 = tf._rotation10 * this._halfAxisZX + tf._rotation11 * this._halfAxisZY + tf._rotation12 * this._halfAxisZZ; + __tmp__Z2 = tf._rotation20 * this._halfAxisZX + tf._rotation21 * this._halfAxisZY + tf._rotation22 * this._halfAxisZZ; + tfzX = __tmp__X2; + tfzY = __tmp__Y2; + tfzZ = __tmp__Z2; + if(tfxX < 0) { + tfxX = -tfxX; + } + if(tfxY < 0) { + tfxY = -tfxY; + } + if(tfxZ < 0) { + tfxZ = -tfxZ; + } + if(tfyX < 0) { + tfyX = -tfyX; + } + if(tfyY < 0) { + tfyY = -tfyY; + } + if(tfyZ < 0) { + tfyZ = -tfyZ; + } + if(tfzX < 0) { + tfzX = -tfzX; + } + if(tfzY < 0) { + tfzY = -tfzY; + } + if(tfzZ < 0) { + tfzZ = -tfzZ; + } + let tfsX; + let tfsY; + let tfsZ; + tfsX = tfxX + tfyX; + tfsY = tfxY + tfyY; + tfsZ = tfxZ + tfyZ; + tfsX += tfzX; + tfsY += tfzY; + tfsZ += tfzZ; + aabb._minX = tf._positionX - tfsX; + aabb._minY = tf._positionY - tfsY; + aabb._minZ = tf._positionZ - tfsZ; + aabb._maxX = tf._positionX + tfsX; + aabb._maxY = tf._positionY + tfsY; + aabb._maxZ = tf._positionZ + tfsZ; + } + computeLocalSupportingVertex(dir,out) { + let gjkMarginsX; + let gjkMarginsY; + let gjkMarginsZ; + let coreExtentsX; + let coreExtentsY; + let coreExtentsZ; + gjkMarginsX = this._gjkMargin; + gjkMarginsY = this._gjkMargin; + gjkMarginsZ = this._gjkMargin; + if(!(gjkMarginsX < this._halfExtentsX)) { + gjkMarginsX = this._halfExtentsX; + } + if(!(gjkMarginsY < this._halfExtentsY)) { + gjkMarginsY = this._halfExtentsY; + } + if(!(gjkMarginsZ < this._halfExtentsZ)) { + gjkMarginsZ = this._halfExtentsZ; + } + coreExtentsX = this._halfExtentsX - gjkMarginsX; + coreExtentsY = this._halfExtentsY - gjkMarginsY; + coreExtentsZ = this._halfExtentsZ - gjkMarginsZ; + out.x = dir.x > 0 ? coreExtentsX : -coreExtentsX; + out.y = dir.y > 0 ? coreExtentsY : -coreExtentsY; + out.z = dir.z > 0 ? coreExtentsZ : -coreExtentsZ; + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let halfW = this._halfExtentsX; + let halfH = this._halfExtentsY; + let halfD = this._halfExtentsZ; + let dx = endX - beginX; + let dy = endY - beginY; + let dz = endZ - beginZ; + let tminx = 0; + let tminy = 0; + let tminz = 0; + let tmaxx = 1; + let tmaxy = 1; + let tmaxz = 1; + if(dx > -1e-6 && dx < 1e-6) { + if(beginX <= -halfW || beginX >= halfW) { + return false; + } + } else { + let invDx = 1 / dx; + let t1 = (-halfW - beginX) * invDx; + let t2 = (halfW - beginX) * invDx; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminx = t1; + } + if(t2 < 1) { + tmaxx = t2; + } + } + if(dy > -1e-6 && dy < 1e-6) { + if(beginY <= -halfH || beginY >= halfH) { + return false; + } + } else { + let invDy = 1 / dy; + let t1 = (-halfH - beginY) * invDy; + let t2 = (halfH - beginY) * invDy; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminy = t1; + } + if(t2 < 1) { + tmaxy = t2; + } + } + if(dz > -1e-6 && dz < 1e-6) { + if(beginZ <= -halfD || beginZ >= halfD) { + return false; + } + } else { + let invDz = 1 / dz; + let t1 = (-halfD - beginZ) * invDz; + let t2 = (halfD - beginZ) * invDz; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminz = t1; + } + if(t2 < 1) { + tmaxz = t2; + } + } + if(tminx >= 1 || tminy >= 1 || tminz >= 1 || tmaxx <= 0 || tmaxy <= 0 || tmaxz <= 0) { + return false; + } + let min = tminx; + let max = tmaxx; + let hitDirection = 0; + if(tminy > min) { + min = tminy; + hitDirection = 1; + } + if(tminz > min) { + min = tminz; + hitDirection = 2; + } + if(tmaxy < max) { + max = tmaxy; + } + if(tmaxz < max) { + max = tmaxz; + } + if(min > max) { + return false; + } + if(min == 0) { + return false; + } + switch(hitDirection) { + case 0: + hit.normal.init(dx > 0 ? -1 : 1,0,0); + break; + case 1: + hit.normal.init(0,dy > 0 ? -1 : 1,0); + break; + case 2: + hit.normal.init(0,0,dz > 0 ? -1 : 1); + break; + } + hit.position.init(beginX + min * dx,beginY + min * dy,beginZ + min * dz); + hit.fraction = min; + return true; + } +} +oimo.collision.geometry.CapsuleGeometry = class oimo_collision_geometry_CapsuleGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius,halfHeight) { + super(4); + this._radius = radius; + this._halfHeight = halfHeight; + this._gjkMargin = this._radius; + this._updateMass(); + } + getRadius() { + return this._radius; + } + getHalfHeight() { + return this._halfHeight; + } + _updateMass() { + let r2 = this._radius * this._radius; + let hh2 = this._halfHeight * this._halfHeight; + let cylinderVolume = 6.28318530717958 * r2 * this._halfHeight; + let sphereVolume = 3.14159265358979 * r2 * this._radius * 4 / 3; + this._volume = cylinderVolume + sphereVolume; + let invVolume = this._volume == 0 ? 0 : 1 / this._volume; + let inertiaXZ = invVolume * (cylinderVolume * (r2 * 0.25 + hh2 / 3) + sphereVolume * (r2 * 0.4 + this._halfHeight * this._radius * 0.75 + hh2)); + this._inertiaCoeff00 = inertiaXZ; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = invVolume * (cylinderVolume * r2 * 0.5 + sphereVolume * r2 * 0.4); + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = inertiaXZ; + } + _computeAabb(aabb,tf) { + let radVecX; + let radVecY; + let radVecZ; + radVecX = this._radius; + radVecY = this._radius; + radVecZ = this._radius; + let axisX; + let axisY; + let axisZ; + axisX = tf._rotation01; + axisY = tf._rotation11; + axisZ = tf._rotation21; + if(axisX < 0) { + axisX = -axisX; + } + if(axisY < 0) { + axisY = -axisY; + } + if(axisZ < 0) { + axisZ = -axisZ; + } + axisX *= this._halfHeight; + axisY *= this._halfHeight; + axisZ *= this._halfHeight; + radVecX += axisX; + radVecY += axisY; + radVecZ += axisZ; + aabb._minX = tf._positionX - radVecX; + aabb._minY = tf._positionY - radVecY; + aabb._minZ = tf._positionZ - radVecZ; + aabb._maxX = tf._positionX + radVecX; + aabb._maxY = tf._positionY + radVecY; + aabb._maxZ = tf._positionZ + radVecZ; + } + computeLocalSupportingVertex(dir,out) { + if(dir.y > 0) { + out.init(0,this._halfHeight,0); + } else { + out.init(0,-this._halfHeight,0); + } + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let halfH = this._halfHeight; + let dx = endX - beginX; + let dz = endZ - beginZ; + let tminxz = 0; + let tmaxxz; + let a = dx * dx + dz * dz; + let b = beginX * dx + beginZ * dz; + let c = beginX * beginX + beginZ * beginZ - this._radius * this._radius; + let D = b * b - a * c; + if(D < 0) { + return false; + } + if(a > 0) { + let sqrtD = Math.sqrt(D); + tminxz = (-b - sqrtD) / a; + tmaxxz = (-b + sqrtD) / a; + if(tminxz >= 1 || tmaxxz <= 0) { + return false; + } + } else { + if(c >= 0) { + return false; + } + tminxz = 0; + } + let crossY = beginY + (endY - beginY) * tminxz; + let min; + if(crossY > -halfH && crossY < halfH) { + if(tminxz > 0) { + min = tminxz; + let _this = hit.normal.init(beginX + dx * min,0,beginZ + dz * min); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + hit.position.init(beginX + min * dx,crossY,beginZ + min * dz); + hit.fraction = min; + return true; + } + return false; + } + let spherePosX; + let spherePosY; + let spherePosZ; + let sphereToBeginX; + let sphereToBeginY; + let sphereToBeginZ; + spherePosX = 0; + spherePosY = crossY < 0 ? -halfH : halfH; + spherePosZ = 0; + sphereToBeginX = beginX - spherePosX; + sphereToBeginY = beginY - spherePosY; + sphereToBeginZ = beginZ - spherePosZ; + let dX; + let dY; + let dZ; + dX = endX - beginX; + dY = endY - beginY; + dZ = endZ - beginZ; + a = dX * dX + dY * dY + dZ * dZ; + b = sphereToBeginX * dX + sphereToBeginY * dY + sphereToBeginZ * dZ; + c = sphereToBeginX * sphereToBeginX + sphereToBeginY * sphereToBeginY + sphereToBeginZ * sphereToBeginZ - this._radius * this._radius; + D = b * b - a * c; + if(D < 0) { + return false; + } + let t = (-b - Math.sqrt(D)) / a; + if(t < 0 || t > 1) { + return false; + } + let hitPosX; + let hitPosY; + let hitPosZ; + let hitNormalX; + let hitNormalY; + let hitNormalZ; + hitPosX = sphereToBeginX + dX * t; + hitPosY = sphereToBeginY + dY * t; + hitPosZ = sphereToBeginZ + dZ * t; + let l = hitPosX * hitPosX + hitPosY * hitPosY + hitPosZ * hitPosZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + hitNormalX = hitPosX * l; + hitNormalY = hitPosY * l; + hitNormalZ = hitPosZ * l; + hitPosX += spherePosX; + hitPosY += spherePosY; + hitPosZ += spherePosZ; + let v = hit.position; + v.x = hitPosX; + v.y = hitPosY; + v.z = hitPosZ; + let v1 = hit.normal; + v1.x = hitNormalX; + v1.y = hitNormalY; + v1.z = hitNormalZ; + hit.fraction = t; + return true; + } +} +oimo.collision.geometry.ConeGeometry = class oimo_collision_geometry_ConeGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius,halfHeight) { + super(3); + this._radius = radius; + this._halfHeight = halfHeight; + this.sinTheta = radius / Math.sqrt(radius * radius + 4 * halfHeight * halfHeight); + this.cosTheta = 2 * halfHeight / Math.sqrt(radius * radius + 4 * halfHeight * halfHeight); + this._updateMass(); + } + getRadius() { + return this._radius; + } + getHalfHeight() { + return this._halfHeight; + } + _updateMass() { + let r2 = this._radius * this._radius; + let h2 = this._halfHeight * this._halfHeight * 4; + this._volume = 3.14159265358979 * r2 * this._halfHeight * 2 / 3; + this._inertiaCoeff00 = 0.05 * (3 * r2 + 2 * h2); + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.3 * r2; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.05 * (3 * r2 + 2 * h2); + } + _computeAabb(aabb,tf) { + let axisX; + let axisY; + let axisZ; + let axis2X; + let axis2Y; + let axis2Z; + let ehX; + let ehY; + let ehZ; + let erX; + let erY; + let erZ; + axisX = tf._rotation01; + axisY = tf._rotation11; + axisZ = tf._rotation21; + axis2X = axisX * axisX; + axis2Y = axisY * axisY; + axis2Z = axisZ * axisZ; + erX = Math.sqrt(1 - axis2X); + erY = Math.sqrt(1 - axis2Y); + erZ = Math.sqrt(1 - axis2Z); + erX *= this._radius; + erY *= this._radius; + erZ *= this._radius; + ehX = axisX * this._halfHeight; + ehY = axisY * this._halfHeight; + ehZ = axisZ * this._halfHeight; + let rminX; + let rminY; + let rminZ; + let rmaxX; + let rmaxY; + let rmaxZ; + rminX = -ehX; + rminY = -ehY; + rminZ = -ehZ; + rminX -= erX; + rminY -= erY; + rminZ -= erZ; + rmaxX = -ehX; + rmaxY = -ehY; + rmaxZ = -ehZ; + rmaxX += erX; + rmaxY += erY; + rmaxZ += erZ; + let maxX; + let maxY; + let maxZ; + let minX; + let minY; + let minZ; + maxX = rminX > rmaxX ? rminX : rmaxX; + maxY = rminY > rmaxY ? rminY : rmaxY; + maxZ = rminZ > rmaxZ ? rminZ : rmaxZ; + if(!(maxX > ehX)) { + maxX = ehX; + } + if(!(maxY > ehY)) { + maxY = ehY; + } + if(!(maxZ > ehZ)) { + maxZ = ehZ; + } + minX = rminX < rmaxX ? rminX : rmaxX; + minY = rminY < rmaxY ? rminY : rmaxY; + minZ = rminZ < rmaxZ ? rminZ : rmaxZ; + if(!(minX < ehX)) { + minX = ehX; + } + if(!(minY < ehY)) { + minY = ehY; + } + if(!(minZ < ehZ)) { + minZ = ehZ; + } + aabb._minX = tf._positionX + minX; + aabb._minY = tf._positionY + minY; + aabb._minZ = tf._positionZ + minZ; + aabb._maxX = tf._positionX + maxX; + aabb._maxY = tf._positionY + maxY; + aabb._maxZ = tf._positionZ + maxZ; + } + computeLocalSupportingVertex(dir,out) { + let dx = dir.x; + let dy = dir.y; + let dz = dir.z; + if(dy > 0 && dy * dy > this.sinTheta * this.sinTheta * (dx * dx + dy * dy + dz * dz)) { + out.init(0,this._halfHeight - this._gjkMargin / this.sinTheta,0); + if(out.y < 0) { + out.y = 0; + } + return; + } + let rx = dir.x; + let rz = dir.z; + let len = rx * rx + rz * rz; + let height = 2 * this._halfHeight; + let coreRadius = (height - this._gjkMargin) / height * this._radius - this._gjkMargin / this.cosTheta; + if(coreRadius < 0) { + coreRadius = 0; + } + let invLen = len > 0 ? coreRadius / Math.sqrt(len) : 0; + let coreHalfHeight = this._halfHeight - this._gjkMargin; + if(coreHalfHeight < 0) { + coreHalfHeight = 0; + } + out.x = rx * invLen; + out.y = -coreHalfHeight; + out.z = rz * invLen; + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let p1y; + let halfH = this._halfHeight; + let dx = endX - beginX; + let dy = endY - beginY; + let dz = endZ - beginZ; + let tminy = 0; + let tmaxy = 1; + if(dy > -1e-6 && dy < 1e-6) { + if(beginY <= -halfH || beginY >= halfH) { + return false; + } + } else { + let invDy = 1 / dy; + let t1 = (-halfH - beginY) * invDy; + let t2 = (halfH - beginY) * invDy; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminy = t1; + } + if(t2 < 1) { + tmaxy = t2; + } + } + if(tminy >= 1 || tmaxy <= 0) { + return false; + } + let tminxz = 0; + let tmaxxz = 0; + p1y = beginY - halfH; + let cos2 = this.cosTheta * this.cosTheta; + let a = cos2 * (dx * dx + dy * dy + dz * dz) - dy * dy; + let b = cos2 * (beginX * dx + p1y * dy + beginZ * dz) - p1y * dy; + let c = cos2 * (beginX * beginX + p1y * p1y + beginZ * beginZ) - p1y * p1y; + let D = b * b - a * c; + if(a != 0) { + if(D < 0) { + return false; + } + let sqrtD = Math.sqrt(D); + if(a < 0) { + if(dy > 0) { + tminxz = 0; + tmaxxz = (-b + sqrtD) / a; + if(tmaxxz <= 0) { + return false; + } + } else { + tminxz = (-b - sqrtD) / a; + tmaxxz = 1; + if(tminxz >= 1) { + return false; + } + } + } else { + tminxz = (-b - sqrtD) / a; + tmaxxz = (-b + sqrtD) / a; + if(tminxz >= 1 || tmaxxz <= 0) { + return false; + } + } + } else { + let t = -c / (2 * b); + if(b > 0) { + tminxz = 0; + tmaxxz = t; + if(t <= 0) { + return false; + } + } else { + tminxz = t; + tmaxxz = 1; + if(t >= 1) { + return false; + } + } + } + p1y += halfH; + let min; + if(tmaxxz <= tminy || tmaxy <= tminxz) { + return false; + } + if(tminxz < tminy) { + min = tminy; + if(min == 0) { + return false; + } + hit.normal.init(0,dy > 0 ? -1 : 1,0); + } else { + min = tminxz; + if(min == 0) { + return false; + } + let _this = hit.normal.init(beginX + dx * min,0,beginZ + dz * min); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let s = this.cosTheta; + _this.x *= s; + _this.y *= s; + _this.z *= s; + hit.normal.y += this.sinTheta; + } + hit.position.init(beginX + min * dx,p1y + min * dy,beginZ + min * dz); + hit.fraction = min; + return true; + } +} +oimo.collision.geometry.ConvexHullGeometry = class oimo_collision_geometry_ConvexHullGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(vertices) { + super(5); + this._numVertices = vertices.length; + this._vertices = new Array(this._numVertices); + this._tmpVertices = new Array(this._numVertices); + let _g = 0; + let _g1 = this._numVertices; + while(_g < _g1) { + let i = _g++; + this._vertices[i] = vertices[i]; + this._tmpVertices[i] = new oimo.common.Vec3(); + } + this._useGjkRayCast = true; + this._updateMass(); + } + getVertices() { + return this._vertices; + } + _updateMass() { + this._volume = 1; + this._inertiaCoeff00 = 1; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 1; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 1; + let minx = this._vertices[0].x; + let miny = this._vertices[0].y; + let minz = this._vertices[0].z; + let maxx = this._vertices[0].x; + let maxy = this._vertices[0].y; + let maxz = this._vertices[0].z; + let _g = 1; + let _g1 = this._numVertices; + while(_g < _g1) { + let i = _g++; + let vx = this._vertices[i].x; + let vy = this._vertices[i].y; + let vz = this._vertices[i].z; + if(vx < minx) { + minx = vx; + } else if(vx > maxx) { + maxx = vx; + } + if(vy < miny) { + miny = vy; + } else if(vy > maxy) { + maxy = vy; + } + if(vz < minz) { + minz = vz; + } else if(vz > maxz) { + maxz = vz; + } + } + let sizex = maxx - minx; + let sizey = maxy - miny; + let sizez = maxz - minz; + this._volume = sizex * sizey * sizez; + let diffCog = ((minx + maxx) * (minx + maxx) + (miny + maxy) * (miny + maxy) + (minz + maxz) * (minz + maxz)) * 0.25; + sizex = sizex * sizex * 0.25; + sizey = sizey * sizey * 0.25; + sizez = sizez * sizez * 0.25; + this._inertiaCoeff00 = 0.33333333333333331 * (sizey + sizez) + diffCog; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.33333333333333331 * (sizez + sizex) + diffCog; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.33333333333333331 * (sizex + sizey) + diffCog; + } + _computeAabb(aabb,tf) { + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + let marginX; + let marginY; + let marginZ; + marginX = this._gjkMargin; + marginY = this._gjkMargin; + marginZ = this._gjkMargin; + let localVX; + let localVY; + let localVZ; + let v = this._vertices[0]; + localVX = v.x; + localVY = v.y; + localVZ = v.z; + let worldVX; + let worldVY; + let worldVZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * localVX + tf._rotation01 * localVY + tf._rotation02 * localVZ; + __tmp__Y = tf._rotation10 * localVX + tf._rotation11 * localVY + tf._rotation12 * localVZ; + __tmp__Z = tf._rotation20 * localVX + tf._rotation21 * localVY + tf._rotation22 * localVZ; + worldVX = __tmp__X; + worldVY = __tmp__Y; + worldVZ = __tmp__Z; + worldVX += tf._positionX; + worldVY += tf._positionY; + worldVZ += tf._positionZ; + minX = worldVX; + minY = worldVY; + minZ = worldVZ; + maxX = worldVX; + maxY = worldVY; + maxZ = worldVZ; + let _g = 1; + let _g1 = this._numVertices; + while(_g < _g1) { + let v = this._vertices[_g++]; + localVX = v.x; + localVY = v.y; + localVZ = v.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * localVX + tf._rotation01 * localVY + tf._rotation02 * localVZ; + __tmp__Y = tf._rotation10 * localVX + tf._rotation11 * localVY + tf._rotation12 * localVZ; + __tmp__Z = tf._rotation20 * localVX + tf._rotation21 * localVY + tf._rotation22 * localVZ; + worldVX = __tmp__X; + worldVY = __tmp__Y; + worldVZ = __tmp__Z; + worldVX += tf._positionX; + worldVY += tf._positionY; + worldVZ += tf._positionZ; + if(!(minX < worldVX)) { + minX = worldVX; + } + if(!(minY < worldVY)) { + minY = worldVY; + } + if(!(minZ < worldVZ)) { + minZ = worldVZ; + } + if(!(maxX > worldVX)) { + maxX = worldVX; + } + if(!(maxY > worldVY)) { + maxY = worldVY; + } + if(!(maxZ > worldVZ)) { + maxZ = worldVZ; + } + } + aabb._minX = minX - marginX; + aabb._minY = minY - marginY; + aabb._minZ = minZ - marginZ; + aabb._maxX = maxX + marginX; + aabb._maxY = maxY + marginY; + aabb._maxZ = maxZ + marginZ; + } + computeLocalSupportingVertex(dir,out) { + let _this = this._vertices[0]; + let maxDot = _this.x * dir.x + _this.y * dir.y + _this.z * dir.z; + let maxIndex = 0; + let _g = 1; + let _g1 = this._numVertices; + while(_g < _g1) { + let i = _g++; + let _this = this._vertices[i]; + let dot = _this.x * dir.x + _this.y * dir.y + _this.z * dir.z; + if(dot > maxDot) { + maxDot = dot; + maxIndex = i; + } + } + let v = this._vertices[maxIndex]; + out.x = v.x; + out.y = v.y; + out.z = v.z; + } +} +oimo.collision.geometry.CylinderGeometry = class oimo_collision_geometry_CylinderGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius,halfHeight) { + super(2); + this._radius = radius; + this._halfHeight = halfHeight; + this._updateMass(); + } + getRadius() { + return this._radius; + } + getHalfHeight() { + return this._halfHeight; + } + _updateMass() { + let r2 = this._radius * this._radius; + let h2 = this._halfHeight * this._halfHeight * 4; + this._volume = 3.14159265358979 * r2 * this._halfHeight * 2; + this._inertiaCoeff00 = 0.083333333333333329 * (3 * r2 + h2); + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.5 * r2; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.083333333333333329 * (3 * r2 + h2); + } + _computeAabb(aabb,tf) { + let axisX; + let axisY; + let axisZ; + let axis2X; + let axis2Y; + let axis2Z; + let ehX; + let ehY; + let ehZ; + let erX; + let erY; + let erZ; + axisX = tf._rotation01; + axisY = tf._rotation11; + axisZ = tf._rotation21; + if(axisX < 0) { + axisX = -axisX; + } + if(axisY < 0) { + axisY = -axisY; + } + if(axisZ < 0) { + axisZ = -axisZ; + } + axis2X = axisX * axisX; + axis2Y = axisY * axisY; + axis2Z = axisZ * axisZ; + erX = Math.sqrt(1 - axis2X); + erY = Math.sqrt(1 - axis2Y); + erZ = Math.sqrt(1 - axis2Z); + erX *= this._radius; + erY *= this._radius; + erZ *= this._radius; + ehX = axisX * this._halfHeight; + ehY = axisY * this._halfHeight; + ehZ = axisZ * this._halfHeight; + let maxX; + let maxY; + let maxZ; + maxX = erX + ehX; + maxY = erY + ehY; + maxZ = erZ + ehZ; + aabb._minX = tf._positionX - maxX; + aabb._minY = tf._positionY - maxY; + aabb._minZ = tf._positionZ - maxZ; + aabb._maxX = tf._positionX + maxX; + aabb._maxY = tf._positionY + maxY; + aabb._maxZ = tf._positionZ + maxZ; + } + computeLocalSupportingVertex(dir,out) { + let rx = dir.x; + let rz = dir.z; + let len = rx * rx + rz * rz; + let coreRadius = this._radius - this._gjkMargin; + if(coreRadius < 0) { + coreRadius = 0; + } + let invLen = len > 0 ? coreRadius / Math.sqrt(len) : 0; + let coreHeight = this._halfHeight - this._gjkMargin; + if(coreHeight < 0) { + coreHeight = 0; + } + out.x = rx * invLen; + out.y = dir.y > 0 ? coreHeight : -coreHeight; + out.z = rz * invLen; + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let halfH = this._halfHeight; + let dx = endX - beginX; + let dy = endY - beginY; + let dz = endZ - beginZ; + let tminy = 0; + let tmaxy = 1; + if(dy > -1e-6 && dy < 1e-6) { + if(beginY <= -halfH || beginY >= halfH) { + return false; + } + } else { + let invDy = 1 / dy; + let t1 = (-halfH - beginY) * invDy; + let t2 = (halfH - beginY) * invDy; + if(t1 > t2) { + let tmp = t1; + t1 = t2; + t2 = tmp; + } + if(t1 > 0) { + tminy = t1; + } + if(t2 < 1) { + tmaxy = t2; + } + } + if(tminy >= 1 || tmaxy <= 0) { + return false; + } + let tminxz = 0; + let tmaxxz; + let a = dx * dx + dz * dz; + let b = beginX * dx + beginZ * dz; + let c = beginX * beginX + beginZ * beginZ - this._radius * this._radius; + let D = b * b - a * c; + if(D < 0) { + return false; + } + if(a > 0) { + let sqrtD = Math.sqrt(D); + tminxz = (-b - sqrtD) / a; + tmaxxz = (-b + sqrtD) / a; + if(tminxz >= 1 || tmaxxz <= 0) { + return false; + } + } else { + if(c >= 0) { + return false; + } + tminxz = 0; + tmaxxz = 1; + } + let min; + if(tmaxxz <= tminy || tmaxy <= tminxz) { + return false; + } + if(tminxz < tminy) { + min = tminy; + if(min == 0) { + return false; + } + hit.normal.init(0,dy > 0 ? -1 : 1,0); + } else { + min = tminxz; + if(min == 0) { + return false; + } + let _this = hit.normal.init(beginX + dx * min,0,beginZ + dz * min); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + } + hit.position.init(beginX + min * dx,beginY + min * dy,beginZ + min * dz); + hit.fraction = min; + return true; + } +} +oimo.collision.geometry.GeometryType = class oimo_collision_geometry_GeometryType { +} +oimo.collision.geometry.RayCastHit = class oimo_collision_geometry_RayCastHit { + constructor() { + this.position = new oimo.common.Vec3(); + this.normal = new oimo.common.Vec3(); + this.fraction = 0; + } +} +oimo.collision.geometry.SphereGeometry = class oimo_collision_geometry_SphereGeometry extends oimo.collision.geometry.ConvexGeometry { + constructor(radius) { + super(0); + this._radius = radius; + this._gjkMargin = this._radius; + this._updateMass(); + } + getRadius() { + return this._radius; + } + _updateMass() { + this._volume = 4.1887902047863861 * this._radius * this._radius * this._radius; + this._inertiaCoeff00 = 0.4 * this._radius * this._radius; + this._inertiaCoeff01 = 0; + this._inertiaCoeff02 = 0; + this._inertiaCoeff10 = 0; + this._inertiaCoeff11 = 0.4 * this._radius * this._radius; + this._inertiaCoeff12 = 0; + this._inertiaCoeff20 = 0; + this._inertiaCoeff21 = 0; + this._inertiaCoeff22 = 0.4 * this._radius * this._radius; + } + _computeAabb(aabb,tf) { + let radVecX; + let radVecY; + let radVecZ; + radVecX = this._radius; + radVecY = this._radius; + radVecZ = this._radius; + aabb._minX = tf._positionX - radVecX; + aabb._minY = tf._positionY - radVecY; + aabb._minZ = tf._positionZ - radVecZ; + aabb._maxX = tf._positionX + radVecX; + aabb._maxY = tf._positionY + radVecY; + aabb._maxZ = tf._positionZ + radVecZ; + } + computeLocalSupportingVertex(dir,out) { + out.zero(); + } + _rayCastLocal(beginX,beginY,beginZ,endX,endY,endZ,hit) { + let dX; + let dY; + let dZ; + dX = endX - beginX; + dY = endY - beginY; + dZ = endZ - beginZ; + let a = dX * dX + dY * dY + dZ * dZ; + let b = beginX * dX + beginY * dY + beginZ * dZ; + let D = b * b - a * (beginX * beginX + beginY * beginY + beginZ * beginZ - this._radius * this._radius); + if(D < 0) { + return false; + } + let t = (-b - Math.sqrt(D)) / a; + if(t < 0 || t > 1) { + return false; + } + let hitPosX; + let hitPosY; + let hitPosZ; + let hitNormalX; + let hitNormalY; + let hitNormalZ; + hitPosX = beginX + dX * t; + hitPosY = beginY + dY * t; + hitPosZ = beginZ + dZ * t; + let l = hitPosX * hitPosX + hitPosY * hitPosY + hitPosZ * hitPosZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + hitNormalX = hitPosX * l; + hitNormalY = hitPosY * l; + hitNormalZ = hitPosZ * l; + let v = hit.position; + v.x = hitPosX; + v.y = hitPosY; + v.z = hitPosZ; + let v1 = hit.normal; + v1.x = hitNormalX; + v1.y = hitNormalY; + v1.z = hitNormalZ; + hit.fraction = t; + return true; + } +} +if(!oimo.collision.narrowphase) oimo.collision.narrowphase = {}; +oimo.collision.narrowphase.CollisionMatrix = class oimo_collision_narrowphase_CollisionMatrix { + constructor() { + this.detectors = new Array(8); + this.detectors[0] = new Array(8); + this.detectors[1] = new Array(8); + this.detectors[2] = new Array(8); + this.detectors[3] = new Array(8); + this.detectors[4] = new Array(8); + this.detectors[5] = new Array(8); + let gjkEpaDetector = new oimo.collision.narrowphase.detector.GjkEpaDetector(); + this.detectors[0][0] = new oimo.collision.narrowphase.detector.SphereSphereDetector(); + this.detectors[0][1] = new oimo.collision.narrowphase.detector.SphereBoxDetector(false); + this.detectors[0][2] = gjkEpaDetector; + this.detectors[0][3] = gjkEpaDetector; + this.detectors[0][4] = new oimo.collision.narrowphase.detector.SphereCapsuleDetector(false); + this.detectors[0][5] = gjkEpaDetector; + this.detectors[1][0] = new oimo.collision.narrowphase.detector.SphereBoxDetector(true); + this.detectors[1][1] = new oimo.collision.narrowphase.detector.BoxBoxDetector(); + this.detectors[1][2] = gjkEpaDetector; + this.detectors[1][3] = gjkEpaDetector; + this.detectors[1][4] = gjkEpaDetector; + this.detectors[1][5] = gjkEpaDetector; + this.detectors[2][0] = gjkEpaDetector; + this.detectors[2][1] = gjkEpaDetector; + this.detectors[2][2] = gjkEpaDetector; + this.detectors[2][3] = gjkEpaDetector; + this.detectors[2][4] = gjkEpaDetector; + this.detectors[2][5] = gjkEpaDetector; + this.detectors[3][0] = gjkEpaDetector; + this.detectors[3][1] = gjkEpaDetector; + this.detectors[3][2] = gjkEpaDetector; + this.detectors[3][3] = gjkEpaDetector; + this.detectors[3][4] = gjkEpaDetector; + this.detectors[3][5] = gjkEpaDetector; + this.detectors[4][0] = new oimo.collision.narrowphase.detector.SphereCapsuleDetector(true); + this.detectors[4][1] = gjkEpaDetector; + this.detectors[4][2] = gjkEpaDetector; + this.detectors[4][3] = gjkEpaDetector; + this.detectors[4][4] = new oimo.collision.narrowphase.detector.CapsuleCapsuleDetector(); + this.detectors[4][5] = gjkEpaDetector; + this.detectors[5][0] = gjkEpaDetector; + this.detectors[5][1] = gjkEpaDetector; + this.detectors[5][2] = gjkEpaDetector; + this.detectors[5][3] = gjkEpaDetector; + this.detectors[5][4] = gjkEpaDetector; + this.detectors[5][5] = gjkEpaDetector; + } + getDetector(geomType1,geomType2) { + return this.detectors[geomType1][geomType2]; + } +} +oimo.collision.narrowphase.DetectorResult = class oimo_collision_narrowphase_DetectorResult { + constructor() { + this.numPoints = 0; + this.normal = new oimo.common.Vec3(); + this.points = new Array(oimo.common.Setting.maxManifoldPoints); + this.incremental = false; + let _g = 0; + let _g1 = oimo.common.Setting.maxManifoldPoints; + while(_g < _g1) this.points[_g++] = new oimo.collision.narrowphase.DetectorResultPoint(); + } + getMaxDepth() { + let max = 0; + let _g = 0; + let _g1 = this.numPoints; + while(_g < _g1) { + let i = _g++; + if(this.points[i].depth > max) { + max = this.points[i].depth; + } + } + return max; + } + clear() { + this.numPoints = 0; + let _g = 0; + let _g1 = this.points; + while(_g < _g1.length) { + let p = _g1[_g]; + ++_g; + p.position1.zero(); + p.position2.zero(); + p.depth = 0; + p.id = 0; + } + this.normal.zero(); + } +} +oimo.collision.narrowphase.DetectorResultPoint = class oimo_collision_narrowphase_DetectorResultPoint { + constructor() { + this.position1 = new oimo.common.Vec3(); + this.position2 = new oimo.common.Vec3(); + this.depth = 0; + this.id = 0; + } +} +if(!oimo.collision.narrowphase.detector) oimo.collision.narrowphase.detector = {}; +oimo.collision.narrowphase.detector.Detector = class oimo_collision_narrowphase_detector_Detector { + constructor(swapped) { + this.swapped = swapped; + } + setNormal(result,nX,nY,nZ) { + let v = result.normal; + v.x = nX; + v.y = nY; + v.z = nZ; + if(this.swapped) { + let _this = result.normal; + _this.x = -_this.x; + _this.y = -_this.y; + _this.z = -_this.z; + } + } + addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,depth,id) { + let p = result.points[result.numPoints++]; + p.depth = depth; + p.id = id; + if(this.swapped) { + let v = p.position1; + v.x = pos2X; + v.y = pos2Y; + v.z = pos2Z; + let v1 = p.position2; + v1.x = pos1X; + v1.y = pos1Y; + v1.z = pos1Z; + } else { + let v = p.position1; + v.x = pos1X; + v.y = pos1Y; + v.z = pos1Z; + let v1 = p.position2; + v1.x = pos2X; + v1.y = pos2Y; + v1.z = pos2Z; + } + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + } + detect(result,geom1,geom2,transform1,transform2,cachedData) { + result.numPoints = 0; + let _g = 0; + let _g1 = result.points; + while(_g < _g1.length) { + let p = _g1[_g]; + ++_g; + p.position1.zero(); + p.position2.zero(); + p.depth = 0; + p.id = 0; + } + result.normal.zero(); + if(this.swapped) { + this.detectImpl(result,geom2,geom1,transform2,transform1,cachedData); + } else { + this.detectImpl(result,geom1,geom2,transform1,transform2,cachedData); + } + } +} +oimo.collision.narrowphase.detector.BoxBoxDetector = class oimo_collision_narrowphase_detector_BoxBoxDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + this.clipper = new oimo.collision.narrowphase.detector._BoxBoxDetector.FaceClipper(); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let b1 = geom1; + let b2 = geom2; + result.incremental = false; + let c1X; + let c1Y; + let c1Z; + let c2X; + let c2Y; + let c2Z; + let c12X; + let c12Y; + let c12Z; + c1X = tf1._positionX; + c1Y = tf1._positionY; + c1Z = tf1._positionZ; + c2X = tf2._positionX; + c2Y = tf2._positionY; + c2Z = tf2._positionZ; + c12X = c2X - c1X; + c12Y = c2Y - c1Y; + c12Z = c2Z - c1Z; + let x1X; + let x1Y; + let x1Z; + let y1X; + let y1Y; + let y1Z; + let z1X; + let z1Y; + let z1Z; + let x2X; + let x2Y; + let x2Z; + let y2X; + let y2Y; + let y2Z; + let z2X; + let z2Y; + let z2Z; + x1X = tf1._rotation00; + x1Y = tf1._rotation10; + x1Z = tf1._rotation20; + y1X = tf1._rotation01; + y1Y = tf1._rotation11; + y1Z = tf1._rotation21; + z1X = tf1._rotation02; + z1Y = tf1._rotation12; + z1Z = tf1._rotation22; + x2X = tf2._rotation00; + x2Y = tf2._rotation10; + x2Z = tf2._rotation20; + y2X = tf2._rotation01; + y2Y = tf2._rotation11; + y2Z = tf2._rotation21; + z2X = tf2._rotation02; + z2Y = tf2._rotation12; + z2Z = tf2._rotation22; + let w1 = b1._halfExtentsX; + let h1 = b1._halfExtentsY; + let d1 = b1._halfExtentsZ; + let w2 = b2._halfExtentsX; + let h2 = b2._halfExtentsY; + let d2 = b2._halfExtentsZ; + let sx1X; + let sx1Y; + let sx1Z; + let sy1X; + let sy1Y; + let sy1Z; + let sz1X; + let sz1Y; + let sz1Z; + let sx2X; + let sx2Y; + let sx2Z; + let sy2X; + let sy2Y; + let sy2Z; + let sz2X; + let sz2Y; + let sz2Z; + sx1X = x1X * w1; + sx1Y = x1Y * w1; + sx1Z = x1Z * w1; + sy1X = y1X * h1; + sy1Y = y1Y * h1; + sy1Z = y1Z * h1; + sz1X = z1X * d1; + sz1Y = z1Y * d1; + sz1Z = z1Z * d1; + sx2X = x2X * w2; + sx2Y = x2Y * w2; + sx2Z = x2Z * w2; + sy2X = y2X * h2; + sy2Y = y2Y * h2; + sy2Z = y2Z * h2; + sz2X = z2X * d2; + sz2Y = z2Y * d2; + sz2Z = z2Z * d2; + let mDepth = 1e65536; + let mId = -1; + let mSign = 0; + let mAxisX; + let mAxisY; + let mAxisZ; + mAxisX = 0; + mAxisY = 0; + mAxisZ = 0; + let proj1 = w1; + let dx = x1X * sx2X + x1Y * sx2Y + x1Z * sx2Z; + let dy = x1X * sy2X + x1Y * sy2Y + x1Z * sy2Z; + let dz = x1X * sz2X + x1Y * sz2Y + x1Z * sz2Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + if(dz < 0) { + dz = -dz; + } + let proj2 = dx + dy + dz; + let projC12 = x1X * c12X + x1Y * c12Y + x1Z * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < 1e65536) { + mDepth = depth; + mId = 0; + mAxisX = x1X; + mAxisY = x1Y; + mAxisZ = x1Z; + mSign = neg ? -1 : 1; + } + } else { + return; + } + proj1 = h1; + let dx1 = y1X * sx2X + y1Y * sx2Y + y1Z * sx2Z; + let dy1 = y1X * sy2X + y1Y * sy2Y + y1Z * sy2Z; + let dz1 = y1X * sz2X + y1Y * sz2Y + y1Z * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + if(dz1 < 0) { + dz1 = -dz1; + } + proj2 = dx1 + dy1 + dz1; + projC12 = y1X * c12X + y1Y * c12Y + y1Z * c12Z; + let sum1 = proj1 + proj2; + let neg1 = projC12 < 0; + let abs1 = neg1 ? -projC12 : projC12; + if(abs1 < sum1) { + let depth = sum1 - abs1; + if(depth < mDepth) { + mDepth = depth; + mId = 1; + mAxisX = y1X; + mAxisY = y1Y; + mAxisZ = y1Z; + mSign = neg1 ? -1 : 1; + } + } else { + return; + } + proj1 = d1; + let dx2 = z1X * sx2X + z1Y * sx2Y + z1Z * sx2Z; + let dy2 = z1X * sy2X + z1Y * sy2Y + z1Z * sy2Z; + let dz2 = z1X * sz2X + z1Y * sz2Y + z1Z * sz2Z; + if(dx2 < 0) { + dx2 = -dx2; + } + if(dy2 < 0) { + dy2 = -dy2; + } + if(dz2 < 0) { + dz2 = -dz2; + } + proj2 = dx2 + dy2 + dz2; + projC12 = z1X * c12X + z1Y * c12Y + z1Z * c12Z; + let sum2 = proj1 + proj2; + let neg2 = projC12 < 0; + let abs2 = neg2 ? -projC12 : projC12; + if(abs2 < sum2) { + let depth = sum2 - abs2; + if(depth < mDepth) { + mDepth = depth; + mId = 2; + mAxisX = z1X; + mAxisY = z1Y; + mAxisZ = z1Z; + mSign = neg2 ? -1 : 1; + } + } else { + return; + } + if(mDepth > oimo.common.Setting.linearSlop) { + mDepth -= oimo.common.Setting.linearSlop; + } else { + mDepth = 0; + } + let dx3 = x2X * sx1X + x2Y * sx1Y + x2Z * sx1Z; + let dy3 = x2X * sy1X + x2Y * sy1Y + x2Z * sy1Z; + let dz3 = x2X * sz1X + x2Y * sz1Y + x2Z * sz1Z; + if(dx3 < 0) { + dx3 = -dx3; + } + if(dy3 < 0) { + dy3 = -dy3; + } + if(dz3 < 0) { + dz3 = -dz3; + } + proj1 = dx3 + dy3 + dz3; + proj2 = w2; + projC12 = x2X * c12X + x2Y * c12Y + x2Z * c12Z; + let sum3 = proj1 + proj2; + let neg3 = projC12 < 0; + let abs3 = neg3 ? -projC12 : projC12; + if(abs3 < sum3) { + let depth = sum3 - abs3; + if(depth < mDepth) { + mDepth = depth; + mId = 3; + mAxisX = x2X; + mAxisY = x2Y; + mAxisZ = x2Z; + mSign = neg3 ? -1 : 1; + } + } else { + return; + } + let dx4 = y2X * sx1X + y2Y * sx1Y + y2Z * sx1Z; + let dy4 = y2X * sy1X + y2Y * sy1Y + y2Z * sy1Z; + let dz4 = y2X * sz1X + y2Y * sz1Y + y2Z * sz1Z; + if(dx4 < 0) { + dx4 = -dx4; + } + if(dy4 < 0) { + dy4 = -dy4; + } + if(dz4 < 0) { + dz4 = -dz4; + } + proj1 = dx4 + dy4 + dz4; + proj2 = h2; + projC12 = y2X * c12X + y2Y * c12Y + y2Z * c12Z; + let sum4 = proj1 + proj2; + let neg4 = projC12 < 0; + let abs4 = neg4 ? -projC12 : projC12; + if(abs4 < sum4) { + let depth = sum4 - abs4; + if(depth < mDepth) { + mDepth = depth; + mId = 4; + mAxisX = y2X; + mAxisY = y2Y; + mAxisZ = y2Z; + mSign = neg4 ? -1 : 1; + } + } else { + return; + } + let dx5 = z2X * sx1X + z2Y * sx1Y + z2Z * sx1Z; + let dy5 = z2X * sy1X + z2Y * sy1Y + z2Z * sy1Z; + let dz5 = z2X * sz1X + z2Y * sz1Y + z2Z * sz1Z; + if(dx5 < 0) { + dx5 = -dx5; + } + if(dy5 < 0) { + dy5 = -dy5; + } + if(dz5 < 0) { + dz5 = -dz5; + } + proj1 = dx5 + dy5 + dz5; + proj2 = d2; + projC12 = z2X * c12X + z2Y * c12Y + z2Z * c12Z; + let sum5 = proj1 + proj2; + let neg5 = projC12 < 0; + let abs5 = neg5 ? -projC12 : projC12; + if(abs5 < sum5) { + let depth = sum5 - abs5; + if(depth < mDepth) { + mDepth = depth; + mId = 5; + mAxisX = z2X; + mAxisY = z2Y; + mAxisZ = z2Z; + mSign = neg5 ? -1 : 1; + } + } else { + return; + } + if(mDepth > oimo.common.Setting.linearSlop) { + mDepth -= oimo.common.Setting.linearSlop; + } else { + mDepth = 0; + } + let edgeAxisX; + let edgeAxisY; + let edgeAxisZ; + edgeAxisX = x1Y * x2Z - x1Z * x2Y; + edgeAxisY = x1Z * x2X - x1X * x2Z; + edgeAxisZ = x1X * x2Y - x1Y * x2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 6; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = x1Y * y2Z - x1Z * y2Y; + edgeAxisY = x1Z * y2X - x1X * y2Z; + edgeAxisZ = x1X * y2Y - x1Y * y2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 7; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = x1Y * z2Z - x1Z * z2Y; + edgeAxisY = x1Z * z2X - x1X * z2Z; + edgeAxisZ = x1X * z2Y - x1Y * z2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 8; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = y1Y * x2Z - y1Z * x2Y; + edgeAxisY = y1Z * x2X - y1X * x2Z; + edgeAxisZ = y1X * x2Y - y1Y * x2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 9; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = y1Y * y2Z - y1Z * y2Y; + edgeAxisY = y1Z * y2X - y1X * y2Z; + edgeAxisZ = y1X * y2Y - y1Y * y2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 10; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = y1Y * z2Z - y1Z * z2Y; + edgeAxisY = y1Z * z2X - y1X * z2Z; + edgeAxisZ = y1X * z2Y - y1Y * z2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sz1X + edgeAxisY * sz1Y + edgeAxisZ * sz1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 11; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = z1Y * x2Z - z1Z * x2Y; + edgeAxisY = z1Z * x2X - z1X * x2Z; + edgeAxisZ = z1X * x2Y - z1Y * x2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 12; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = z1Y * y2Z - z1Z * y2Y; + edgeAxisY = z1Z * y2X - z1X * y2Z; + edgeAxisZ = z1X * y2Y - z1Y * y2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sz2X + edgeAxisY * sz2Y + edgeAxisZ * sz2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 13; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + edgeAxisX = z1Y * z2Z - z1Z * z2Y; + edgeAxisY = z1Z * z2X - z1X * z2Z; + edgeAxisZ = z1X * z2Y - z1Y * z2X; + if(!(edgeAxisX == 0 && edgeAxisY == 0 && edgeAxisZ == 0)) { + let l = edgeAxisX * edgeAxisX + edgeAxisY * edgeAxisY + edgeAxisZ * edgeAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + edgeAxisX *= l; + edgeAxisY *= l; + edgeAxisZ *= l; + let dx = edgeAxisX * sx1X + edgeAxisY * sx1Y + edgeAxisZ * sx1Z; + let dy = edgeAxisX * sy1X + edgeAxisY * sy1Y + edgeAxisZ * sy1Z; + if(dx < 0) { + dx = -dx; + } + if(dy < 0) { + dy = -dy; + } + proj1 = dx + dy; + let dx1 = edgeAxisX * sx2X + edgeAxisY * sx2Y + edgeAxisZ * sx2Z; + let dy1 = edgeAxisX * sy2X + edgeAxisY * sy2Y + edgeAxisZ * sy2Z; + if(dx1 < 0) { + dx1 = -dx1; + } + if(dy1 < 0) { + dy1 = -dy1; + } + proj2 = dx1 + dy1; + projC12 = edgeAxisX * c12X + edgeAxisY * c12Y + edgeAxisZ * c12Z; + let sum = proj1 + proj2; + let neg = projC12 < 0; + let abs = neg ? -projC12 : projC12; + if(abs < sum) { + let depth = sum - abs; + if(depth < mDepth) { + mDepth = depth; + mId = 14; + mAxisX = edgeAxisX; + mAxisY = edgeAxisY; + mAxisZ = edgeAxisZ; + mSign = neg ? -1 : 1; + } + } else { + return; + } + } + if(mId >= 6) { + mAxisX *= mSign; + mAxisY *= mSign; + mAxisZ *= mSign; + let id1 = (mId - 6) / 3 | 0; + let id2 = mId - 6 - id1 * 3; + let p1X; + let p1Y; + let p1Z; + let p2X; + let p2Y; + let p2Z; + let d1X; + let d1Y; + let d1Z; + let d2X; + let d2Y; + let d2Z; + switch(id1) { + case 0: + d1X = x1X; + d1Y = x1Y; + d1Z = x1Z; + let signY = sz1X * mAxisX + sz1Y * mAxisY + sz1Z * mAxisZ > 0; + if(sy1X * mAxisX + sy1Y * mAxisY + sy1Z * mAxisZ > 0) { + if(signY) { + p1X = sy1X + sz1X; + p1Y = sy1Y + sz1Y; + p1Z = sy1Z + sz1Z; + } else { + p1X = sy1X - sz1X; + p1Y = sy1Y - sz1Y; + p1Z = sy1Z - sz1Z; + } + } else if(signY) { + p1X = sz1X - sy1X; + p1Y = sz1Y - sy1Y; + p1Z = sz1Z - sy1Z; + } else { + p1X = sy1X + sz1X; + p1Y = sy1Y + sz1Y; + p1Z = sy1Z + sz1Z; + p1X = -p1X; + p1Y = -p1Y; + p1Z = -p1Z; + } + break; + case 1: + d1X = y1X; + d1Y = y1Y; + d1Z = y1Z; + let signY1 = sz1X * mAxisX + sz1Y * mAxisY + sz1Z * mAxisZ > 0; + if(sx1X * mAxisX + sx1Y * mAxisY + sx1Z * mAxisZ > 0) { + if(signY1) { + p1X = sx1X + sz1X; + p1Y = sx1Y + sz1Y; + p1Z = sx1Z + sz1Z; + } else { + p1X = sx1X - sz1X; + p1Y = sx1Y - sz1Y; + p1Z = sx1Z - sz1Z; + } + } else if(signY1) { + p1X = sz1X - sx1X; + p1Y = sz1Y - sx1Y; + p1Z = sz1Z - sx1Z; + } else { + p1X = sx1X + sz1X; + p1Y = sx1Y + sz1Y; + p1Z = sx1Z + sz1Z; + p1X = -p1X; + p1Y = -p1Y; + p1Z = -p1Z; + } + break; + default: + d1X = z1X; + d1Y = z1Y; + d1Z = z1Z; + let signY2 = sy1X * mAxisX + sy1Y * mAxisY + sy1Z * mAxisZ > 0; + if(sx1X * mAxisX + sx1Y * mAxisY + sx1Z * mAxisZ > 0) { + if(signY2) { + p1X = sx1X + sy1X; + p1Y = sx1Y + sy1Y; + p1Z = sx1Z + sy1Z; + } else { + p1X = sx1X - sy1X; + p1Y = sx1Y - sy1Y; + p1Z = sx1Z - sy1Z; + } + } else if(signY2) { + p1X = sy1X - sx1X; + p1Y = sy1Y - sx1Y; + p1Z = sy1Z - sx1Z; + } else { + p1X = sx1X + sy1X; + p1Y = sx1Y + sy1Y; + p1Z = sx1Z + sy1Z; + p1X = -p1X; + p1Y = -p1Y; + p1Z = -p1Z; + } + } + p1X = c1X + p1X; + p1Y = c1Y + p1Y; + p1Z = c1Z + p1Z; + switch(id2) { + case 0: + d2X = x2X; + d2Y = x2Y; + d2Z = x2Z; + let signY3 = sz2X * mAxisX + sz2Y * mAxisY + sz2Z * mAxisZ > 0; + if(sy2X * mAxisX + sy2Y * mAxisY + sy2Z * mAxisZ > 0) { + if(signY3) { + p2X = sy2X + sz2X; + p2Y = sy2Y + sz2Y; + p2Z = sy2Z + sz2Z; + } else { + p2X = sy2X - sz2X; + p2Y = sy2Y - sz2Y; + p2Z = sy2Z - sz2Z; + } + } else if(signY3) { + p2X = sz2X - sy2X; + p2Y = sz2Y - sy2Y; + p2Z = sz2Z - sy2Z; + } else { + p2X = sy2X + sz2X; + p2Y = sy2Y + sz2Y; + p2Z = sy2Z + sz2Z; + p2X = -p2X; + p2Y = -p2Y; + p2Z = -p2Z; + } + break; + case 1: + d2X = y2X; + d2Y = y2Y; + d2Z = y2Z; + let signY4 = sz2X * mAxisX + sz2Y * mAxisY + sz2Z * mAxisZ > 0; + if(sx2X * mAxisX + sx2Y * mAxisY + sx2Z * mAxisZ > 0) { + if(signY4) { + p2X = sx2X + sz2X; + p2Y = sx2Y + sz2Y; + p2Z = sx2Z + sz2Z; + } else { + p2X = sx2X - sz2X; + p2Y = sx2Y - sz2Y; + p2Z = sx2Z - sz2Z; + } + } else if(signY4) { + p2X = sz2X - sx2X; + p2Y = sz2Y - sx2Y; + p2Z = sz2Z - sx2Z; + } else { + p2X = sx2X + sz2X; + p2Y = sx2Y + sz2Y; + p2Z = sx2Z + sz2Z; + p2X = -p2X; + p2Y = -p2Y; + p2Z = -p2Z; + } + break; + default: + d2X = z2X; + d2Y = z2Y; + d2Z = z2Z; + let signY5 = sy2X * mAxisX + sy2Y * mAxisY + sy2Z * mAxisZ > 0; + if(sx2X * mAxisX + sx2Y * mAxisY + sx2Z * mAxisZ > 0) { + if(signY5) { + p2X = sx2X + sy2X; + p2Y = sx2Y + sy2Y; + p2Z = sx2Z + sy2Z; + } else { + p2X = sx2X - sy2X; + p2Y = sx2Y - sy2Y; + p2Z = sx2Z - sy2Z; + } + } else if(signY5) { + p2X = sy2X - sx2X; + p2Y = sy2Y - sx2Y; + p2Z = sy2Z - sx2Z; + } else { + p2X = sx2X + sy2X; + p2Y = sx2Y + sy2Y; + p2Z = sx2Z + sy2Z; + p2X = -p2X; + p2Y = -p2Y; + p2Z = -p2Z; + } + } + p2X = c2X - p2X; + p2Y = c2Y - p2Y; + p2Z = c2Z - p2Z; + let rX; + let rY; + let rZ; + rX = p1X - p2X; + rY = p1Y - p2Y; + rZ = p1Z - p2Z; + let dot12 = d1X * d2X + d1Y * d2Y + d1Z * d2Z; + let dot1r = d1X * rX + d1Y * rY + d1Z * rZ; + let dot2r = d2X * rX + d2Y * rY + d2Z * rZ; + let invDet = 1 / (1 - dot12 * dot12); + let t1 = (dot12 * dot2r - dot1r) * invDet; + let t2 = (dot2r - dot12 * dot1r) * invDet; + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + cp1X = p1X + d1X * t1; + cp1Y = p1Y + d1Y * t1; + cp1Z = p1Z + d1Z * t1; + cp2X = p2X + d2X * t2; + cp2Y = p2Y + d2Y * t2; + cp2Z = p2Z + d2Z * t2; + let normalX; + let normalY; + let normalZ; + normalX = -mAxisX; + normalY = -mAxisY; + normalZ = -mAxisZ; + this.setNormal(result,normalX,normalY,normalZ); + this.addPoint(result,cp1X,cp1Y,cp1Z,cp2X,cp2Y,cp2Z,mDepth,4); + return; + } + let tmpX; + let tmpY; + let tmpZ; + let swapped; + if(mId >= 3) { + mSign = -mSign; + c12X = -c12X; + c12Y = -c12Y; + c12Z = -c12Z; + + + + + w1 = w2; + + + h1 = h2; + + + d1 = d2; + + + + + c1X = c2X; + c1Y = c2Y; + c1Z = c2Z; + + + + tmpX = x1X; + tmpY = x1Y; + tmpZ = x1Z; + x1X = x2X; + x1Y = x2Y; + x1Z = x2Z; + x2X = tmpX; + x2Y = tmpY; + x2Z = tmpZ; + tmpX = y1X; + tmpY = y1Y; + tmpZ = y1Z; + y1X = y2X; + y1Y = y2Y; + y1Z = y2Z; + y2X = tmpX; + y2Y = tmpY; + y2Z = tmpZ; + tmpX = z1X; + tmpY = z1Y; + tmpZ = z1Z; + z1X = z2X; + z1Y = z2Y; + z1Z = z2Z; + z2X = tmpX; + z2Y = tmpY; + z2Z = tmpZ; + tmpX = sx1X; + tmpY = sx1Y; + tmpZ = sx1Z; + sx1X = sx2X; + sx1Y = sx2Y; + sx1Z = sx2Z; + sx2X = tmpX; + sx2Y = tmpY; + sx2Z = tmpZ; + tmpX = sy1X; + tmpY = sy1Y; + tmpZ = sy1Z; + sy1X = sy2X; + sy1Y = sy2Y; + sy1Z = sy2Z; + sy2X = tmpX; + sy2Y = tmpY; + sy2Z = tmpZ; + tmpX = sz1X; + tmpY = sz1Y; + tmpZ = sz1Z; + sz1X = sz2X; + sz1Y = sz2Y; + sz1Z = sz2Z; + sz2X = tmpX; + sz2Y = tmpY; + sz2Z = tmpZ; + mId -= 3; + swapped = true; + } else { + swapped = false; + } + let refCenterX; + let refCenterY; + let refCenterZ; + let refNormalX; + let refNormalY; + let refNormalZ; + let refXX; + let refXY; + let refXZ; + let refYX; + let refYY; + let refYZ; + let refW; + let refH; + switch(mId) { + case 0: + refCenterX = sx1X; + refCenterY = sx1Y; + refCenterZ = sx1Z; + refNormalX = x1X; + refNormalY = x1Y; + refNormalZ = x1Z; + refXX = y1X; + refXY = y1Y; + refXZ = y1Z; + refYX = z1X; + refYY = z1Y; + refYZ = z1Z; + refW = h1; + refH = d1; + break; + case 1: + refCenterX = sy1X; + refCenterY = sy1Y; + refCenterZ = sy1Z; + refNormalX = y1X; + refNormalY = y1Y; + refNormalZ = y1Z; + refXX = z1X; + refXY = z1Y; + refXZ = z1Z; + refYX = x1X; + refYY = x1Y; + refYZ = x1Z; + refW = d1; + refH = w1; + break; + default: + refCenterX = sz1X; + refCenterY = sz1Y; + refCenterZ = sz1Z; + refNormalX = z1X; + refNormalY = z1Y; + refNormalZ = z1Z; + refXX = x1X; + refXY = x1Y; + refXZ = x1Z; + refYX = y1X; + refYY = y1Y; + refYZ = y1Z; + refW = w1; + refH = h1; + } + if(mSign < 0) { + refCenterX = -refCenterX; + refCenterY = -refCenterY; + refCenterZ = -refCenterZ; + refNormalX = -refNormalX; + refNormalY = -refNormalY; + refNormalZ = -refNormalZ; + tmpX = refXX; + tmpY = refXY; + tmpZ = refXZ; + refXX = refYX; + refXY = refYY; + refXZ = refYZ; + refYX = tmpX; + refYY = tmpY; + refYZ = tmpZ; + let tmp = refW; + refW = refH; + refH = tmp; + } + refCenterX += c1X; + refCenterY += c1Y; + refCenterZ += c1Z; + let minIncDot = 1; + let incId = 0; + let incDot = refNormalX * x2X + refNormalY * x2Y + refNormalZ * x2Z; + if(incDot < minIncDot) { + minIncDot = incDot; + incId = 0; + } + if(-incDot < minIncDot) { + minIncDot = -incDot; + incId = 1; + } + incDot = refNormalX * y2X + refNormalY * y2Y + refNormalZ * y2Z; + if(incDot < minIncDot) { + minIncDot = incDot; + incId = 2; + } + if(-incDot < minIncDot) { + minIncDot = -incDot; + incId = 3; + } + incDot = refNormalX * z2X + refNormalY * z2Y + refNormalZ * z2Z; + if(incDot < minIncDot) { + minIncDot = incDot; + incId = 4; + } + if(-incDot < minIncDot) { + + incId = 5; + } + let incV1X; + let incV1Y; + let incV1Z; + let incV2X; + let incV2Y; + let incV2Z; + let incV3X; + let incV3Y; + let incV3Z; + let incV4X; + let incV4Y; + let incV4Z; + switch(incId) { + case 0: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sx2X - sy2X; + incV2Y = sx2Y - sy2Y; + incV2Z = sx2Z - sy2Z; + incV2X += sz2X; + incV2Y += sz2Y; + incV2Z += sz2Z; + incV3X = sx2X - sy2X; + incV3Y = sx2Y - sy2Y; + incV3Z = sx2Z - sy2Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sx2X + sy2X; + incV4Y = sx2Y + sy2Y; + incV4Z = sx2Z + sy2Z; + incV4X -= sz2X; + incV4Y -= sz2Y; + incV4Z -= sz2Z; + break; + case 1: + incV1X = sy2X - sx2X; + incV1Y = sy2Y - sx2Y; + incV1Z = sy2Z - sx2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sy2X - sx2X; + incV2Y = sy2Y - sx2Y; + incV2Z = sy2Z - sx2Z; + incV2X -= sz2X; + incV2Y -= sz2Y; + incV2Z -= sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sx2X + sy2X; + incV4Y = sx2Y + sy2Y; + incV4Z = sx2Z + sy2Z; + incV4X = -incV4X; + incV4Y = -incV4Y; + incV4Z = -incV4Z; + incV4X += sz2X; + incV4Y += sz2Y; + incV4Z += sz2Z; + break; + case 2: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sx2X + sy2X; + incV2Y = sx2Y + sy2Y; + incV2Z = sx2Z + sy2Z; + incV2X -= sz2X; + incV2Y -= sz2Y; + incV2Z -= sz2Z; + incV3X = sy2X - sx2X; + incV3Y = sy2Y - sx2Y; + incV3Z = sy2Z - sx2Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sy2X - sx2X; + incV4Y = sy2Y - sx2Y; + incV4Z = sy2Z - sx2Z; + incV4X += sz2X; + incV4Y += sz2Y; + incV4Z += sz2Z; + break; + case 3: + incV1X = sx2X - sy2X; + incV1Y = sx2Y - sy2Y; + incV1Z = sx2Z - sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sx2X + sy2X; + incV2Y = sx2Y + sy2Y; + incV2Z = sx2Z + sy2Z; + incV2X = -incV2X; + incV2Y = -incV2Y; + incV2Z = -incV2Z; + incV2X += sz2X; + incV2Y += sz2Y; + incV2Z += sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sx2X - sy2X; + incV4Y = sx2Y - sy2Y; + incV4Z = sx2Z - sy2Z; + incV4X -= sz2X; + incV4Y -= sz2Y; + incV4Z -= sz2Z; + break; + case 4: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X += sz2X; + incV1Y += sz2Y; + incV1Z += sz2Z; + incV2X = sy2X - sx2X; + incV2Y = sy2Y - sx2Y; + incV2Z = sy2Z - sx2Z; + incV2X += sz2X; + incV2Y += sz2Y; + incV2Z += sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X += sz2X; + incV3Y += sz2Y; + incV3Z += sz2Z; + incV4X = sx2X - sy2X; + incV4Y = sx2Y - sy2Y; + incV4Z = sx2Z - sy2Z; + incV4X += sz2X; + incV4Y += sz2Y; + incV4Z += sz2Z; + break; + default: + incV1X = sx2X + sy2X; + incV1Y = sx2Y + sy2Y; + incV1Z = sx2Z + sy2Z; + incV1X -= sz2X; + incV1Y -= sz2Y; + incV1Z -= sz2Z; + incV2X = sx2X - sy2X; + incV2Y = sx2Y - sy2Y; + incV2Z = sx2Z - sy2Z; + incV2X -= sz2X; + incV2Y -= sz2Y; + incV2Z -= sz2Z; + incV3X = sx2X + sy2X; + incV3Y = sx2Y + sy2Y; + incV3Z = sx2Z + sy2Z; + incV3X = -incV3X; + incV3Y = -incV3Y; + incV3Z = -incV3Z; + incV3X -= sz2X; + incV3Y -= sz2Y; + incV3Z -= sz2Z; + incV4X = sy2X - sx2X; + incV4Y = sy2Y - sx2Y; + incV4Z = sy2Z - sx2Z; + incV4X -= sz2X; + incV4Y -= sz2Y; + incV4Z -= sz2Z; + } + incV1X += c12X; + incV1Y += c12Y; + incV1Z += c12Z; + incV2X += c12X; + incV2Y += c12Y; + incV2Z += c12Z; + incV3X += c12X; + incV3Y += c12Y; + incV3Z += c12Z; + incV4X += c12X; + incV4Y += c12Y; + incV4Z += c12Z; + let _this = this.clipper; + _this.w = refW; + _this.h = refH; + _this.numVertices = 0; + _this.numTmpVertices = 0; + let _this1 = this.clipper; + let _this2 = _this1.vertices[_this1.numVertices++]; + _this2.x = incV1X * refXX + incV1Y * refXY + incV1Z * refXZ; + _this2.y = incV1X * refYX + incV1Y * refYY + incV1Z * refYZ; + _this2.wx = incV1X; + _this2.wy = incV1Y; + _this2.wz = incV1Z; + let _this3 = this.clipper; + let _this4 = _this3.vertices[_this3.numVertices++]; + _this4.x = incV2X * refXX + incV2Y * refXY + incV2Z * refXZ; + _this4.y = incV2X * refYX + incV2Y * refYY + incV2Z * refYZ; + _this4.wx = incV2X; + _this4.wy = incV2Y; + _this4.wz = incV2Z; + let _this5 = this.clipper; + let _this6 = _this5.vertices[_this5.numVertices++]; + _this6.x = incV3X * refXX + incV3Y * refXY + incV3Z * refXZ; + _this6.y = incV3X * refYX + incV3Y * refYY + incV3Z * refYZ; + _this6.wx = incV3X; + _this6.wy = incV3Y; + _this6.wz = incV3Z; + let _this7 = this.clipper; + let _this8 = _this7.vertices[_this7.numVertices++]; + _this8.x = incV4X * refXX + incV4Y * refXY + incV4Z * refXZ; + _this8.y = incV4X * refYX + incV4Y * refYY + incV4Z * refYZ; + _this8.wx = incV4X; + _this8.wy = incV4Y; + _this8.wz = incV4Z; + this.clipper.clip(); + this.clipper.reduce(); + let normalX; + let normalY; + let normalZ; + if(swapped) { + normalX = refNormalX; + normalY = refNormalY; + normalZ = refNormalZ; + } else { + normalX = -refNormalX; + normalY = -refNormalY; + normalZ = -refNormalZ; + } + this.setNormal(result,normalX,normalY,normalZ); + let _g = 0; + let _g1 = this.clipper.numVertices; + while(_g < _g1) { + let i = _g++; + let v = this.clipper.vertices[i]; + let clippedVertexX; + let clippedVertexY; + let clippedVertexZ; + clippedVertexX = v.wx; + clippedVertexY = v.wy; + clippedVertexZ = v.wz; + clippedVertexX += c1X; + clippedVertexY += c1Y; + clippedVertexZ += c1Z; + let clippedVertexToRefCenterX; + let clippedVertexToRefCenterY; + let clippedVertexToRefCenterZ; + clippedVertexToRefCenterX = refCenterX - clippedVertexX; + clippedVertexToRefCenterY = refCenterY - clippedVertexY; + clippedVertexToRefCenterZ = refCenterZ - clippedVertexZ; + let depth = clippedVertexToRefCenterX * refNormalX + clippedVertexToRefCenterY * refNormalY + clippedVertexToRefCenterZ * refNormalZ; + let clippedVertexOnRefFaceX; + let clippedVertexOnRefFaceY; + let clippedVertexOnRefFaceZ; + clippedVertexOnRefFaceX = clippedVertexX + refNormalX * depth; + clippedVertexOnRefFaceY = clippedVertexY + refNormalY * depth; + clippedVertexOnRefFaceZ = clippedVertexZ + refNormalZ * depth; + if(depth > -oimo.common.Setting.contactPersistenceThreshold) { + if(swapped) { + this.addPoint(result,clippedVertexX,clippedVertexY,clippedVertexZ,clippedVertexOnRefFaceX,clippedVertexOnRefFaceY,clippedVertexOnRefFaceZ,depth,i); + } else { + this.addPoint(result,clippedVertexOnRefFaceX,clippedVertexOnRefFaceY,clippedVertexOnRefFaceZ,clippedVertexX,clippedVertexY,clippedVertexZ,depth,i); + } + } + } + } +} +if(!oimo.collision.narrowphase.detector._BoxBoxDetector) oimo.collision.narrowphase.detector._BoxBoxDetector = {}; +oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex = class oimo_collision_narrowphase_detector__$BoxBoxDetector_IncidentVertex { + constructor() { + this.x = 0; + this.y = 0; + this.wx = 0; + this.wy = 0; + this.wz = 0; + } +} +oimo.collision.narrowphase.detector._BoxBoxDetector.FaceClipper = class oimo_collision_narrowphase_detector__$BoxBoxDetector_FaceClipper { + constructor() { + this.w = 0; + this.h = 0; + this.numVertices = 0; + this.numTmpVertices = 0; + this.vertices = new Array(8); + this.tmpVertices = new Array(8); + this.vertices[0] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[0] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[1] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[1] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[2] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[2] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[3] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[3] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[4] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[4] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[5] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[5] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[6] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[6] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.vertices[7] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + this.tmpVertices[7] = new oimo.collision.narrowphase.detector._BoxBoxDetector.IncidentVertex(); + } + clip() { + let _g = 0; + let _g1 = this.numVertices; + while(_g < _g1) { + let i = _g++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.w + v1.x; + let s2 = this.w + v2.x; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + let _g2 = 0; + let _g3 = this.numVertices; + while(_g2 < _g3) { + let i = _g2++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.w - v1.x; + let s2 = this.w - v2.x; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp1 = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp1; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + let _g4 = 0; + let _g5 = this.numVertices; + while(_g4 < _g5) { + let i = _g4++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.h + v1.y; + let s2 = this.h + v2.y; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp2 = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp2; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + let _g6 = 0; + let _g7 = this.numVertices; + while(_g6 < _g7) { + let i = _g6++; + let v1 = this.vertices[i]; + let v2 = this.vertices[(i + 1) % this.numVertices]; + let s1 = this.h - v1.y; + let s2 = this.h - v2.y; + if(s1 > 0 && s2 > 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + } else if(s1 > 0 && s2 <= 0) { + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x; + _this.y = v1.y; + _this.wx = v1.wx; + _this.wy = v1.wy; + _this.wz = v1.wz; + let t = s1 / (s1 - s2); + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = v1.x + (v2.x - v1.x) * t; + _this1.y = v1.y + (v2.y - v1.y) * t; + _this1.wx = v1.wx + (v2.wx - v1.wx) * t; + _this1.wy = v1.wy + (v2.wy - v1.wy) * t; + _this1.wz = v1.wz + (v2.wz - v1.wz) * t; + } else if(s1 <= 0 && s2 > 0) { + let t = s1 / (s1 - s2); + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = v1.x + (v2.x - v1.x) * t; + _this.y = v1.y + (v2.y - v1.y) * t; + _this.wx = v1.wx + (v2.wx - v1.wx) * t; + _this.wy = v1.wy + (v2.wy - v1.wy) * t; + _this.wz = v1.wz + (v2.wz - v1.wz) * t; + } + } + let tmp3 = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp3; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + } + reduce() { + if(this.numVertices < 4) { + return; + } + let max1 = -1e65536; + let min1 = 1e65536; + let max2 = -1e65536; + let min2 = 1e65536; + let max1V = null; + let min1V = null; + let max2V = null; + let min2V = null; + let e1x = 1; + let e1y = 1; + let e2x = -1; + let e2y = 1; + let _g = 0; + let _g1 = this.numVertices; + while(_g < _g1) { + let v = this.vertices[_g++]; + let dot1 = v.x * e1x + v.y * e1y; + let dot2 = v.x * e2x + v.y * e2y; + if(dot1 > max1) { + max1 = dot1; + max1V = v; + } + if(dot1 < min1) { + min1 = dot1; + min1V = v; + } + if(dot2 > max2) { + max2 = dot2; + max2V = v; + } + if(dot2 < min2) { + min2 = dot2; + min2V = v; + } + } + let _this = this.tmpVertices[this.numTmpVertices++]; + _this.x = max1V.x; + _this.y = max1V.y; + _this.wx = max1V.wx; + _this.wy = max1V.wy; + _this.wz = max1V.wz; + let _this1 = this.tmpVertices[this.numTmpVertices++]; + _this1.x = max2V.x; + _this1.y = max2V.y; + _this1.wx = max2V.wx; + _this1.wy = max2V.wy; + _this1.wz = max2V.wz; + let _this2 = this.tmpVertices[this.numTmpVertices++]; + _this2.x = min1V.x; + _this2.y = min1V.y; + _this2.wx = min1V.wx; + _this2.wy = min1V.wy; + _this2.wz = min1V.wz; + let _this3 = this.tmpVertices[this.numTmpVertices++]; + _this3.x = min2V.x; + _this3.y = min2V.y; + _this3.wx = min2V.wx; + _this3.wy = min2V.wy; + _this3.wz = min2V.wz; + let tmp = this.vertices; + this.vertices = this.tmpVertices; + this.tmpVertices = tmp; + this.numVertices = this.numTmpVertices; + this.numTmpVertices = 0; + } +} +oimo.collision.narrowphase.detector.BoxBoxDetectorMacro = class oimo_collision_narrowphase_detector_BoxBoxDetectorMacro { +} +oimo.collision.narrowphase.detector.CachedDetectorData = class oimo_collision_narrowphase_detector_CachedDetectorData { + constructor() { + } + _clear() { + if(this._gjkCache != null) { + this._gjkCache.clear(); + } + } +} +oimo.collision.narrowphase.detector.CapsuleCapsuleDetector = class oimo_collision_narrowphase_detector_CapsuleCapsuleDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let c1 = geom1; + let c2 = geom2; + result.incremental = false; + let axis1X; + let axis1Y; + let axis1Z; + let axis2X; + let axis2Y; + let axis2Z; + axis1X = tf1._rotation01; + axis1Y = tf1._rotation11; + axis1Z = tf1._rotation21; + axis2X = tf2._rotation01; + axis2Y = tf2._rotation11; + axis2Z = tf2._rotation21; + let hh1 = c1._halfHeight; + let hh2 = c2._halfHeight; + let r1 = c1._radius; + let r2 = c2._radius; + let p1X; + let p1Y; + let p1Z; + let q1X; + let q1Y; + let q1Z; + let p2X; + let p2Y; + let p2Z; + let q2X; + let q2Y; + let q2Z; + p1X = tf1._positionX + axis1X * -hh1; + p1Y = tf1._positionY + axis1Y * -hh1; + p1Z = tf1._positionZ + axis1Z * -hh1; + q1X = tf1._positionX + axis1X * hh1; + q1Y = tf1._positionY + axis1Y * hh1; + q1Z = tf1._positionZ + axis1Z * hh1; + p2X = tf2._positionX + axis2X * -hh2; + p2Y = tf2._positionY + axis2Y * -hh2; + p2Z = tf2._positionZ + axis2Z * -hh2; + q2X = tf2._positionX + axis2X * hh2; + q2Y = tf2._positionY + axis2Y * hh2; + q2Z = tf2._positionZ + axis2Z * hh2; + let p12X; + let p12Y; + let p12Z; + p12X = p1X - p2X; + p12Y = p1Y - p2Y; + p12Z = p1Z - p2Z; + let d1X; + let d1Y; + let d1Z; + let d2X; + let d2Y; + let d2Z; + d1X = q1X - p1X; + d1Y = q1Y - p1Y; + d1Z = q1Z - p1Z; + d2X = q2X - p2X; + d2Y = q2Y - p2Y; + d2Z = q2Z - p2Z; + let p21d1 = -(p12X * d1X + p12Y * d1Y + p12Z * d1Z); + let p12d2 = p12X * d2X + p12Y * d2Y + p12Z * d2Z; + let d11 = hh1 * hh1 * 4; + let d12 = d1X * d2X + d1Y * d2Y + d1Z * d2Z; + let d22 = hh2 * hh2 * 4; + let t1; + let t2; + if(d11 == 0 && d22 == 0) { + t1 = 0; + t2 = 0; + } else if(d11 == 0) { + t1 = 0; + + if(p12d2 < 0) { + t2 = 0; + } else if(p12d2 > d22) { + t2 = 1; + } else { + t2 = p12d2 / d22; + } + } else if(d22 == 0) { + t2 = 0; + + if(p21d1 < 0) { + t1 = 0; + } else if(p21d1 > d11) { + t1 = 1; + } else { + t1 = p21d1 / d11; + } + } else { + let det = d11 * d22 - d12 * d12; + if(det == 0) { + t1 = 0; + } else { + t1 = d12 * p12d2 + d22 * p21d1; + if(t1 < 0) { + t1 = 0; + } else if(t1 > det) { + t1 = 1; + } else { + t1 /= det; + } + } + t2 = t1 * d12 + p12d2; + if(t2 < 0) { + t2 = 0; + + if(p21d1 < 0) { + t1 = 0; + } else if(p21d1 > d11) { + t1 = 1; + } else { + t1 = p21d1 / d11; + } + } else if(t2 > d22) { + t2 = 1; + t1 = d12 + p21d1; + if(t1 < 0) { + t1 = 0; + } else if(t1 > d11) { + t1 = 1; + } else { + t1 /= d11; + } + } else { + t2 /= d22; + } + } + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + cp1X = p1X + d1X * t1; + cp1Y = p1Y + d1Y * t1; + cp1Z = p1Z + d1Z * t1; + cp2X = p2X + d2X * t2; + cp2Y = p2Y + d2Y * t2; + cp2Z = p2Z + d2Z * t2; + let dX; + let dY; + let dZ; + dX = cp1X - cp2X; + dY = cp1Y - cp2Y; + dZ = cp1Z - cp2Z; + let len2 = dX * dX + dY * dY + dZ * dZ; + if(len2 >= (r1 + r2) * (r1 + r2)) { + return; + } + let len = Math.sqrt(len2); + let nX; + let nY; + let nZ; + if(len > 0) { + nX = dX * (1 / len); + nY = dY * (1 / len); + nZ = dZ * (1 / len); + } else { + nX = 1; + nY = 0; + nZ = 0; + } + this.setNormal(result,nX,nY,nZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = cp1X + nX * -r1; + pos1Y = cp1Y + nY * -r1; + pos1Z = cp1Z + nZ * -r1; + pos2X = cp2X + nX * r2; + pos2Y = cp2Y + nY * r2; + pos2Z = cp2Z + nZ * r2; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r1 + r2 - len,0); + } +} +oimo.collision.narrowphase.detector.GjkEpaDetector = class oimo_collision_narrowphase_detector_GjkEpaDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let gjkEpa = oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + let g1 = geom1; + let g2 = geom2; + let status = gjkEpa.computeClosestPointsImpl(g1,g2,tf1,tf2,oimo.common.Setting.enableGJKCaching ? cachedData : null,true); + result.incremental = true; + if(status != oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.SUCCEEDED) { + console.log("src/oimo/collision/narrowphase/detector/GjkEpaDetector.hx:28:","GJK/EPA failed: status=" + status); + return; + } + if(gjkEpa.distance > g1._gjkMargin + g2._gjkMargin) { + return; + } + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + let v = gjkEpa.closestPoint1; + pos1X = v.x; + pos1Y = v.y; + pos1Z = v.z; + let v1 = gjkEpa.closestPoint2; + pos2X = v1.x; + pos2Y = v1.y; + pos2Z = v1.z; + let normalX; + let normalY; + let normalZ; + normalX = pos1X - pos2X; + normalY = pos1Y - pos2Y; + normalZ = pos1Z - pos2Z; + if(normalX * normalX + normalY * normalY + normalZ * normalZ == 0) { + return; + } + if(gjkEpa.distance < 0) { + normalX = -normalX; + normalY = -normalY; + normalZ = -normalZ; + } + let l = normalX * normalX + normalY * normalY + normalZ * normalZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + normalX *= l; + normalY *= l; + normalZ *= l; + this.setNormal(result,normalX,normalY,normalZ); + pos1X += normalX * -g1._gjkMargin; + pos1Y += normalY * -g1._gjkMargin; + pos1Z += normalZ * -g1._gjkMargin; + pos2X += normalX * g2._gjkMargin; + pos2Y += normalY * g2._gjkMargin; + pos2Z += normalZ * g2._gjkMargin; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,g1._gjkMargin + g2._gjkMargin - gjkEpa.distance,0); + } +} +oimo.collision.narrowphase.detector.SphereBoxDetector = class oimo_collision_narrowphase_detector_SphereBoxDetector extends oimo.collision.narrowphase.detector.Detector { + constructor(swapped) { + super(swapped); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let b = geom2; + result.incremental = false; + let halfExtX; + let halfExtY; + let halfExtZ; + let negHalfExtX; + let negHalfExtY; + let negHalfExtZ; + halfExtX = b._halfExtentsX; + halfExtY = b._halfExtentsY; + halfExtZ = b._halfExtentsZ; + negHalfExtX = -halfExtX; + negHalfExtY = -halfExtY; + negHalfExtZ = -halfExtZ; + let r = geom1._radius; + let boxToSphereX; + let boxToSphereY; + let boxToSphereZ; + boxToSphereX = tf1._positionX - tf2._positionX; + boxToSphereY = tf1._positionY - tf2._positionY; + boxToSphereZ = tf1._positionZ - tf2._positionZ; + let boxToSphereInBoxX; + let boxToSphereInBoxY; + let boxToSphereInBoxZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf2._rotation00 * boxToSphereX + tf2._rotation10 * boxToSphereY + tf2._rotation20 * boxToSphereZ; + __tmp__Y = tf2._rotation01 * boxToSphereX + tf2._rotation11 * boxToSphereY + tf2._rotation21 * boxToSphereZ; + __tmp__Z = tf2._rotation02 * boxToSphereX + tf2._rotation12 * boxToSphereY + tf2._rotation22 * boxToSphereZ; + boxToSphereInBoxX = __tmp__X; + boxToSphereInBoxY = __tmp__Y; + boxToSphereInBoxZ = __tmp__Z; + if(negHalfExtX < boxToSphereInBoxX && halfExtX > boxToSphereInBoxX && negHalfExtY < boxToSphereInBoxY && halfExtY > boxToSphereInBoxY && negHalfExtZ < boxToSphereInBoxZ && halfExtZ > boxToSphereInBoxZ) { + let sphereToBoxSurfaceX; + let sphereToBoxSurfaceY; + let sphereToBoxSurfaceZ; + sphereToBoxSurfaceX = boxToSphereInBoxX < 0 ? -boxToSphereInBoxX : boxToSphereInBoxX; + sphereToBoxSurfaceY = boxToSphereInBoxY < 0 ? -boxToSphereInBoxY : boxToSphereInBoxY; + sphereToBoxSurfaceZ = boxToSphereInBoxZ < 0 ? -boxToSphereInBoxZ : boxToSphereInBoxZ; + sphereToBoxSurfaceX = halfExtX - sphereToBoxSurfaceX; + sphereToBoxSurfaceY = halfExtY - sphereToBoxSurfaceY; + sphereToBoxSurfaceZ = halfExtZ - sphereToBoxSurfaceZ; + let normalInBoxX; + let normalInBoxY; + let normalInBoxZ; + let distX = sphereToBoxSurfaceX; + let distY = sphereToBoxSurfaceY; + let distZ = sphereToBoxSurfaceZ; + let depth; + let projectionMaskX; + let projectionMaskY; + let projectionMaskZ; + if(distX < distY) { + if(distX < distZ) { + if(boxToSphereInBoxX > 0) { + normalInBoxX = 1; + normalInBoxY = 0; + normalInBoxZ = 0; + } else { + normalInBoxX = -1; + normalInBoxY = 0; + normalInBoxZ = 0; + } + projectionMaskX = 0; + projectionMaskY = 1; + projectionMaskZ = 1; + depth = distX; + } else { + if(boxToSphereInBoxZ > 0) { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = 1; + } else { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = -1; + } + projectionMaskX = 1; + projectionMaskY = 1; + projectionMaskZ = 0; + depth = distZ; + } + } else if(distY < distZ) { + if(boxToSphereInBoxY > 0) { + normalInBoxX = 0; + normalInBoxY = 1; + normalInBoxZ = 0; + } else { + normalInBoxX = 0; + normalInBoxY = -1; + normalInBoxZ = 0; + } + projectionMaskX = 1; + projectionMaskY = 0; + projectionMaskZ = 1; + depth = distY; + } else { + if(boxToSphereInBoxZ > 0) { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = 1; + } else { + normalInBoxX = 0; + normalInBoxY = 0; + normalInBoxZ = -1; + } + projectionMaskX = 1; + projectionMaskY = 1; + projectionMaskZ = 0; + depth = distZ; + } + let baseX; + let baseY; + let baseZ; + baseX = projectionMaskX * boxToSphereInBoxX; + baseY = projectionMaskY * boxToSphereInBoxY; + baseZ = projectionMaskZ * boxToSphereInBoxZ; + let boxToClosestPointInBoxX; + let boxToClosestPointInBoxY; + let boxToClosestPointInBoxZ; + boxToClosestPointInBoxX = normalInBoxX * halfExtX; + boxToClosestPointInBoxY = normalInBoxY * halfExtY; + boxToClosestPointInBoxZ = normalInBoxZ * halfExtZ; + boxToClosestPointInBoxX += baseX; + boxToClosestPointInBoxY += baseY; + boxToClosestPointInBoxZ += baseZ; + let boxToClosestPointX; + let boxToClosestPointY; + let boxToClosestPointZ; + let normalX; + let normalY; + let normalZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf2._rotation00 * boxToClosestPointInBoxX + tf2._rotation01 * boxToClosestPointInBoxY + tf2._rotation02 * boxToClosestPointInBoxZ; + __tmp__Y = tf2._rotation10 * boxToClosestPointInBoxX + tf2._rotation11 * boxToClosestPointInBoxY + tf2._rotation12 * boxToClosestPointInBoxZ; + __tmp__Z = tf2._rotation20 * boxToClosestPointInBoxX + tf2._rotation21 * boxToClosestPointInBoxY + tf2._rotation22 * boxToClosestPointInBoxZ; + boxToClosestPointX = __tmp__X; + boxToClosestPointY = __tmp__Y; + boxToClosestPointZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * normalInBoxX + tf2._rotation01 * normalInBoxY + tf2._rotation02 * normalInBoxZ; + __tmp__Y1 = tf2._rotation10 * normalInBoxX + tf2._rotation11 * normalInBoxY + tf2._rotation12 * normalInBoxZ; + __tmp__Z1 = tf2._rotation20 * normalInBoxX + tf2._rotation21 * normalInBoxY + tf2._rotation22 * normalInBoxZ; + normalX = __tmp__X1; + normalY = __tmp__Y1; + normalZ = __tmp__Z1; + this.setNormal(result,normalX,normalY,normalZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = tf1._positionX + normalX * -r; + pos1Y = tf1._positionY + normalY * -r; + pos1Z = tf1._positionZ + normalZ * -r; + pos2X = tf2._positionX + boxToClosestPointX; + pos2Y = tf2._positionY + boxToClosestPointY; + pos2Z = tf2._positionZ + boxToClosestPointZ; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,depth,0); + return; + } + let boxToClosestPointInBoxX; + let boxToClosestPointInBoxY; + let boxToClosestPointInBoxZ; + halfExtX -= 1e-9; + halfExtY -= 1e-9; + halfExtZ -= 1e-9; + negHalfExtX += 1e-9; + negHalfExtY += 1e-9; + negHalfExtZ += 1e-9; + boxToClosestPointInBoxX = boxToSphereInBoxX < halfExtX ? boxToSphereInBoxX : halfExtX; + boxToClosestPointInBoxY = boxToSphereInBoxY < halfExtY ? boxToSphereInBoxY : halfExtY; + boxToClosestPointInBoxZ = boxToSphereInBoxZ < halfExtZ ? boxToSphereInBoxZ : halfExtZ; + if(!(boxToClosestPointInBoxX > negHalfExtX)) { + boxToClosestPointInBoxX = negHalfExtX; + } + if(!(boxToClosestPointInBoxY > negHalfExtY)) { + boxToClosestPointInBoxY = negHalfExtY; + } + if(!(boxToClosestPointInBoxZ > negHalfExtZ)) { + boxToClosestPointInBoxZ = negHalfExtZ; + } + let closestPointToSphereInBoxX; + let closestPointToSphereInBoxY; + let closestPointToSphereInBoxZ; + closestPointToSphereInBoxX = boxToSphereInBoxX - boxToClosestPointInBoxX; + closestPointToSphereInBoxY = boxToSphereInBoxY - boxToClosestPointInBoxY; + closestPointToSphereInBoxZ = boxToSphereInBoxZ - boxToClosestPointInBoxZ; + let dist = closestPointToSphereInBoxX * closestPointToSphereInBoxX + closestPointToSphereInBoxY * closestPointToSphereInBoxY + closestPointToSphereInBoxZ * closestPointToSphereInBoxZ; + if(dist >= r * r) { + return; + } + dist = Math.sqrt(dist); + let boxToClosestPointX; + let boxToClosestPointY; + let boxToClosestPointZ; + let closestPointToSphereX; + let closestPointToSphereY; + let closestPointToSphereZ; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * boxToClosestPointInBoxX + tf2._rotation01 * boxToClosestPointInBoxY + tf2._rotation02 * boxToClosestPointInBoxZ; + __tmp__Y1 = tf2._rotation10 * boxToClosestPointInBoxX + tf2._rotation11 * boxToClosestPointInBoxY + tf2._rotation12 * boxToClosestPointInBoxZ; + __tmp__Z1 = tf2._rotation20 * boxToClosestPointInBoxX + tf2._rotation21 * boxToClosestPointInBoxY + tf2._rotation22 * boxToClosestPointInBoxZ; + boxToClosestPointX = __tmp__X1; + boxToClosestPointY = __tmp__Y1; + boxToClosestPointZ = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf2._rotation00 * closestPointToSphereInBoxX + tf2._rotation01 * closestPointToSphereInBoxY + tf2._rotation02 * closestPointToSphereInBoxZ; + __tmp__Y2 = tf2._rotation10 * closestPointToSphereInBoxX + tf2._rotation11 * closestPointToSphereInBoxY + tf2._rotation12 * closestPointToSphereInBoxZ; + __tmp__Z2 = tf2._rotation20 * closestPointToSphereInBoxX + tf2._rotation21 * closestPointToSphereInBoxY + tf2._rotation22 * closestPointToSphereInBoxZ; + closestPointToSphereX = __tmp__X2; + closestPointToSphereY = __tmp__Y2; + closestPointToSphereZ = __tmp__Z2; + let normalX; + let normalY; + let normalZ; + let l = closestPointToSphereX * closestPointToSphereX + closestPointToSphereY * closestPointToSphereY + closestPointToSphereZ * closestPointToSphereZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + normalX = closestPointToSphereX * l; + normalY = closestPointToSphereY * l; + normalZ = closestPointToSphereZ * l; + this.setNormal(result,normalX,normalY,normalZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = tf1._positionX + normalX * -r; + pos1Y = tf1._positionY + normalY * -r; + pos1Z = tf1._positionZ + normalZ * -r; + pos2X = tf2._positionX + boxToClosestPointX; + pos2Y = tf2._positionY + boxToClosestPointY; + pos2Z = tf2._positionZ + boxToClosestPointZ; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r - dist,0); + } +} +oimo.collision.narrowphase.detector.SphereCapsuleDetector = class oimo_collision_narrowphase_detector_SphereCapsuleDetector extends oimo.collision.narrowphase.detector.Detector { + constructor(swapped) { + super(swapped); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + let c2 = geom2; + result.incremental = false; + let hh2 = c2._halfHeight; + let r1 = geom1._radius; + let r2 = c2._radius; + let axis2X; + let axis2Y; + let axis2Z; + axis2X = tf2._rotation01; + axis2Y = tf2._rotation11; + axis2Z = tf2._rotation21; + let cp1X; + let cp1Y; + let cp1Z; + cp1X = tf1._positionX; + cp1Y = tf1._positionY; + cp1Z = tf1._positionZ; + let p2X; + let p2Y; + let p2Z; + let q2X; + let q2Y; + let q2Z; + p2X = tf2._positionX + axis2X * -hh2; + p2Y = tf2._positionY + axis2Y * -hh2; + p2Z = tf2._positionZ + axis2Z * -hh2; + q2X = tf2._positionX + axis2X * hh2; + q2Y = tf2._positionY + axis2Y * hh2; + q2Z = tf2._positionZ + axis2Z * hh2; + let p12X; + let p12Y; + let p12Z; + p12X = cp1X - p2X; + p12Y = cp1Y - p2Y; + p12Z = cp1Z - p2Z; + let d2X; + let d2Y; + let d2Z; + d2X = q2X - p2X; + d2Y = q2Y - p2Y; + d2Z = q2Z - p2Z; + let d22 = hh2 * hh2 * 4; + let t = p12X * d2X + p12Y * d2Y + p12Z * d2Z; + if(t < 0) { + t = 0; + } else if(t > d22) { + t = 1; + } else { + t /= d22; + } + let cp2X; + let cp2Y; + let cp2Z; + cp2X = p2X + d2X * t; + cp2Y = p2Y + d2Y * t; + cp2Z = p2Z + d2Z * t; + let dX; + let dY; + let dZ; + dX = cp1X - cp2X; + dY = cp1Y - cp2Y; + dZ = cp1Z - cp2Z; + let len2 = dX * dX + dY * dY + dZ * dZ; + if(len2 >= (r1 + r2) * (r1 + r2)) { + return; + } + let len = Math.sqrt(len2); + let nX; + let nY; + let nZ; + if(len > 0) { + nX = dX * (1 / len); + nY = dY * (1 / len); + nZ = dZ * (1 / len); + } else { + nX = 1; + nY = 0; + nZ = 0; + } + this.setNormal(result,nX,nY,nZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = cp1X + nX * -r1; + pos1Y = cp1Y + nY * -r1; + pos1Z = cp1Z + nZ * -r1; + pos2X = cp2X + nX * r2; + pos2Y = cp2Y + nY * r2; + pos2Z = cp2Z + nZ * r2; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r1 + r2 - len,0); + } +} +oimo.collision.narrowphase.detector.SphereSphereDetector = class oimo_collision_narrowphase_detector_SphereSphereDetector extends oimo.collision.narrowphase.detector.Detector { + constructor() { + super(false); + } + detectImpl(result,geom1,geom2,tf1,tf2,cachedData) { + result.incremental = false; + let dX; + let dY; + let dZ; + dX = tf1._positionX - tf2._positionX; + dY = tf1._positionY - tf2._positionY; + dZ = tf1._positionZ - tf2._positionZ; + let r1 = geom1._radius; + let r2 = geom2._radius; + let len2 = dX * dX + dY * dY + dZ * dZ; + if(len2 >= (r1 + r2) * (r1 + r2)) { + return; + } + let len = Math.sqrt(len2); + let nX; + let nY; + let nZ; + if(len > 0) { + nX = dX * (1 / len); + nY = dY * (1 / len); + nZ = dZ * (1 / len); + } else { + nX = 1; + nY = 0; + nZ = 0; + } + this.setNormal(result,nX,nY,nZ); + let pos1X; + let pos1Y; + let pos1Z; + let pos2X; + let pos2Y; + let pos2Z; + pos1X = tf1._positionX + nX * -r1; + pos1Y = tf1._positionY + nY * -r1; + pos1Z = tf1._positionZ + nZ * -r1; + pos2X = tf2._positionX + nX * r2; + pos2Y = tf2._positionY + nY * r2; + pos2Z = tf2._positionZ + nZ * r2; + this.addPoint(result,pos1X,pos1Y,pos1Z,pos2X,pos2Y,pos2Z,r1 + r2 - len,0); + } +} +if(!oimo.collision.narrowphase.detector.gjkepa) oimo.collision.narrowphase.detector.gjkepa = {}; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedron = class oimo_collision_narrowphase_detector_gjkepa_EpaPolyhedron { + constructor() { + this._vertices = new Array(oimo.common.Setting.maxEPAVertices); + this._center = new oimo.common.Vec3(); + this._numVertices = 0; + this._triangleList = null; + this._triangleListLast = null; + this._numTriangles = 0; + this._trianglePool = null; + this._vertexPool = null; + } + dumpHoleEdge(first) { + } + validate() { + let t = this._triangleList; + while(t != null) { + t._vertices[0]._tmpEdgeLoopOuterTriangle = null; + t._vertices[0]._tmpEdgeLoopNext = null; + if(t._adjacentPairIndex[0] == -1) { + this._status = 2; + return false; + } + if(t._adjacentTriangles[0] == null) { + this._status = 3; + return false; + } + t._vertices[1]._tmpEdgeLoopOuterTriangle = null; + t._vertices[1]._tmpEdgeLoopNext = null; + if(t._adjacentPairIndex[1] == -1) { + this._status = 2; + return false; + } + if(t._adjacentTriangles[1] == null) { + this._status = 3; + return false; + } + t._vertices[2]._tmpEdgeLoopOuterTriangle = null; + t._vertices[2]._tmpEdgeLoopNext = null; + if(t._adjacentPairIndex[2] == -1) { + this._status = 2; + return false; + } + if(t._adjacentTriangles[2] == null) { + this._status = 3; + return false; + } + t = t._next; + } + return true; + } + findEdgeLoop(id,base,from) { + if(base._tmpDfsId == id) { + return; + } + base._tmpDfsId = id; + let _this = base.tmp; + _this.x = from.x; + _this.y = from.y; + _this.z = from.z; + let v = base._vertices[0].v; + _this.x -= v.x; + _this.y -= v.y; + _this.z -= v.z; + let _this1 = base.tmp; + let v1 = base._normal; + base._tmpDfsVisible = _this1.x * v1.x + _this1.y * v1.y + _this1.z * v1.z > 0; + if(!base._tmpDfsVisible) { + this._status = 6; + return; + } + let _g = 0; + while(_g < 3) { + let i = _g++; + let t = base._adjacentTriangles[i]; + if(t == null) { + continue; + } + let _this = t.tmp; + _this.x = from.x; + _this.y = from.y; + _this.z = from.z; + let v = t._vertices[0].v; + _this.x -= v.x; + _this.y -= v.y; + _this.z -= v.z; + let _this1 = t.tmp; + let v1 = t._normal; + t._tmpDfsVisible = _this1.x * v1.x + _this1.y * v1.y + _this1.z * v1.z > 0; + if(t._tmpDfsVisible) { + this.findEdgeLoop(id,t,from); + } else { + let v1 = base._vertices[i]; + v1._tmpEdgeLoopNext = base._vertices[base._nextIndex[i]]; + v1._tmpEdgeLoopOuterTriangle = t; + } + } + let triangle = base._adjacentTriangles[0]; + if(triangle != null) { + let pairIndex = base._adjacentPairIndex[0]; + triangle._adjacentTriangles[pairIndex] = null; + triangle._adjacentPairIndex[pairIndex] = -1; + base._adjacentTriangles[0] = null; + base._adjacentPairIndex[0] = -1; + } + let triangle1 = base._adjacentTriangles[1]; + if(triangle1 != null) { + let pairIndex = base._adjacentPairIndex[1]; + triangle1._adjacentTriangles[pairIndex] = null; + triangle1._adjacentPairIndex[pairIndex] = -1; + base._adjacentTriangles[1] = null; + base._adjacentPairIndex[1] = -1; + } + let triangle2 = base._adjacentTriangles[2]; + if(triangle2 != null) { + let pairIndex = base._adjacentPairIndex[2]; + triangle2._adjacentTriangles[pairIndex] = null; + triangle2._adjacentPairIndex[pairIndex] = -1; + base._adjacentTriangles[2] = null; + base._adjacentPairIndex[2] = -1; + } + this._numTriangles--; + let prev = base._prev; + let next = base._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(base == this._triangleList) { + this._triangleList = this._triangleList._next; + } + if(base == this._triangleListLast) { + this._triangleListLast = this._triangleListLast._prev; + } + base._next = null; + base._prev = null; + base.removeReferences(); + base._next = this._trianglePool; + this._trianglePool = base; + } + _init(v1,v2,v3,v4) { + this._status = 0; + this._numVertices = 4; + this._vertices[0] = v1; + this._vertices[1] = v2; + this._vertices[2] = v3; + this._vertices[3] = v4; + let _this = this._center; + let v = v1.v; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let v5 = v2.v; + _this.x += v5.x; + _this.y += v5.y; + _this.z += v5.z; + let v6 = v3.v; + _this.x += v6.x; + _this.y += v6.y; + _this.z += v6.z; + let v7 = v4.v; + _this.x += v7.x; + _this.y += v7.y; + _this.z += v7.z; + _this.x *= 0.25; + _this.y *= 0.25; + _this.z *= 0.25; + let first = this._trianglePool; + if(first != null) { + this._trianglePool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t1 = first; + let first1 = this._trianglePool; + if(first1 != null) { + this._trianglePool = first1._next; + first1._next = null; + } else { + first1 = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t2 = first1; + let first2 = this._trianglePool; + if(first2 != null) { + this._trianglePool = first2._next; + first2._next = null; + } else { + first2 = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t3 = first2; + let first3 = this._trianglePool; + if(first3 != null) { + this._trianglePool = first3._next; + first3._next = null; + } else { + first3 = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t4 = first3; + if(!t1.init(v1,v2,v3,this._center,true)) { + this._status = 1; + } + if(!t2.init(v1,v2,v4,this._center,true)) { + this._status = 1; + } + if(!t3.init(v1,v3,v4,this._center,true)) { + this._status = 1; + } + if(!t4.init(v2,v3,v4,this._center,true)) { + this._status = 1; + } + if(!t1.setAdjacentTriangle(t2)) { + this._status = 1; + } + if(!t1.setAdjacentTriangle(t3)) { + this._status = 1; + } + if(!t1.setAdjacentTriangle(t4)) { + this._status = 1; + } + if(!t2.setAdjacentTriangle(t3)) { + this._status = 1; + } + if(!t2.setAdjacentTriangle(t4)) { + this._status = 1; + } + if(!t3.setAdjacentTriangle(t4)) { + this._status = 1; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t1; + this._triangleListLast = t1; + } else { + this._triangleListLast._next = t1; + t1._prev = this._triangleListLast; + this._triangleListLast = t1; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t2; + this._triangleListLast = t2; + } else { + this._triangleListLast._next = t2; + t2._prev = this._triangleListLast; + this._triangleListLast = t2; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t3; + this._triangleListLast = t3; + } else { + this._triangleListLast._next = t3; + t3._prev = this._triangleListLast; + this._triangleListLast = t3; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t4; + this._triangleListLast = t4; + } else { + this._triangleListLast._next = t4; + t4._prev = this._triangleListLast; + this._triangleListLast = t4; + } + return this._status == 0; + } + _addVertex(vertex,base) { + this._vertices[this._numVertices++] = vertex; + let v1 = base._vertices[0]; + this.findEdgeLoop(this._numVertices,base,vertex.v); + if(this._status != 0) { + return false; + } + let v = v1; + let prevT = null; + let firstT = null; + while(true) { + if(v._tmpEdgeLoopNext == null) { + this._dumpAsObjModel(); + this._status = 4; + return false; + } + if(v._tmpEdgeLoopOuterTriangle == null) { + this._status = 5; + return false; + } + let first = this._trianglePool; + if(first != null) { + this._trianglePool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaTriangle(); + } + let t = first; + if(firstT == null) { + firstT = t; + } + if(!t.init(v,v._tmpEdgeLoopNext,vertex,this._center,false)) { + this._status = 1; + } + if(this._status != 0) { + return false; + } + this._numTriangles++; + if(this._triangleList == null) { + this._triangleList = t; + this._triangleListLast = t; + } else { + this._triangleListLast._next = t; + t._prev = this._triangleListLast; + this._triangleListLast = t; + } + if(!t.setAdjacentTriangle(v._tmpEdgeLoopOuterTriangle)) { + this._status = 1; + } + if(prevT != null) { + if(!t.setAdjacentTriangle(prevT)) { + this._status = 1; + } + } + prevT = t; + v = v._tmpEdgeLoopNext; + if(!(v != v1)) { + break; + } + } + if(!prevT.setAdjacentTriangle(firstT)) { + this._status = 1; + } + if(this._status == 0) { + return this.validate(); + } else { + return false; + } + } + _dumpAsObjModel() { + } +} +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState = class oimo_collision_narrowphase_detector_gjkepa_EpaPolyhedronState { +} +oimo.collision.narrowphase.detector.gjkepa.EpaTriangle = class oimo_collision_narrowphase_detector_gjkepa_EpaTriangle { + constructor() { + this.id = ++oimo.collision.narrowphase.detector.gjkepa.EpaTriangle.count; + this._next = null; + this._prev = null; + this._normal = new oimo.common.Vec3(); + this._distanceSq = 0; + this._tmpDfsId = 0; + this._tmpDfsVisible = false; + this._vertices = new Array(3); + this._adjacentTriangles = new Array(3); + this._adjacentPairIndex = new Array(3); + this.tmp = new oimo.common.Vec3(); + this._nextIndex = new Array(3); + this._nextIndex[0] = 1; + this._nextIndex[1] = 2; + this._nextIndex[2] = 0; + } + init(vertex1,vertex2,vertex3,center,autoCheck) { + if(autoCheck == null) { + autoCheck = false; + } + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let vcX; + let vcY; + let vcZ; + let v = vertex1.v; + v1X = v.x; + v1Y = v.y; + v1Z = v.z; + let v1 = vertex2.v; + v2X = v1.x; + v2Y = v1.y; + v2Z = v1.z; + let v2 = vertex3.v; + v3X = v2.x; + v3Y = v2.y; + v3Z = v2.z; + vcX = center.x; + vcY = center.y; + vcZ = center.z; + let v12X; + let v12Y; + let v12Z; + let v13X; + let v13Y; + let v13Z; + let vc1X; + let vc1Y; + let vc1Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v13X = v3X - v1X; + v13Y = v3Y - v1Y; + v13Z = v3Z - v1Z; + vc1X = v1X - vcX; + vc1Y = v1Y - vcY; + vc1Z = v1Z - vcZ; + let inorX; + let inorY; + let inorZ; + inorX = v12Y * v13Z - v12Z * v13Y; + inorY = v12Z * v13X - v12X * v13Z; + inorZ = v12X * v13Y - v12Y * v13X; + let inverted = false; + if(vc1X * inorX + vc1Y * inorY + vc1Z * inorZ < 0) { + if(autoCheck) { + let tmp = vertex2; + vertex2 = vertex3; + vertex3 = tmp; + inorX *= -1; + inorY *= -1; + inorZ *= -1; + } else { + inverted = true; + } + } + this._vertices[0] = vertex1; + this._vertices[1] = vertex2; + this._vertices[2] = vertex3; + let v3 = this._normal; + v3.x = inorX; + v3.y = inorY; + v3.z = inorZ; + let vec1 = vertex1.v; + let vec2 = vertex2.v; + let vec3 = vertex3.v; + let out = this.tmp; + let v1X1; + let v1Y1; + let v1Z1; + let v2X1; + let v2Y1; + let v2Z1; + let v3X1; + let v3Y1; + let v3Z1; + let v12X1; + let v12Y1; + let v12Z1; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X1 = vec1.x; + v1Y1 = vec1.y; + v1Z1 = vec1.z; + v2X1 = vec2.x; + v2Y1 = vec2.y; + v2Z1 = vec2.z; + v3X1 = vec3.x; + v3Y1 = vec3.y; + v3Z1 = vec3.z; + v12X1 = v2X1 - v1X1; + v12Y1 = v2Y1 - v1Y1; + v12Z1 = v2Z1 - v1Z1; + v23X = v3X1 - v2X1; + v23Y = v3Y1 - v2Y1; + v23Z = v3Z1 - v2Z1; + v31X = v1X1 - v3X1; + v31Y = v1Y1 - v3Y1; + v31Z = v1Z1 - v3Z1; + let nX; + let nY; + let nZ; + nX = v12Y1 * v23Z - v12Z1 * v23Y; + nY = v12Z1 * v23X - v12X1 * v23Z; + nZ = v12X1 * v23Y - v12Y1 * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y1 * nZ - v12Z1 * nY; + n12Y = v12Z1 * nX - v12X1 * nZ; + n12Z = v12X1 * nY - v12Y1 * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X1 * n12X + v1Y1 * n12Y + v1Z1 * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + } + mind = out.x * out.x + out.y * out.y + out.z * out.z; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + if(v2X1 * n23X + v2Y1 * n23Y + v2Z1 * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(v3X1 * n31X + v3Y1 * n31Y + v3Z1 * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(mind > 0) { + out.x = minvX; + out.y = minvY; + out.z = minvZ; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X1 * nX + v1Y1 * nY + v1Z1 * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + out.x = minvX; + out.y = minvY; + out.z = minvZ; + } + let _this = this.tmp; + this._distanceSq = _this.x * _this.x + _this.y * _this.y + _this.z * _this.z; + this._adjacentTriangles[0] = null; + this._adjacentTriangles[1] = null; + this._adjacentTriangles[2] = null; + this._adjacentPairIndex[0] = -1; + this._adjacentPairIndex[1] = -1; + this._adjacentPairIndex[2] = -1; + return !inverted; + } + setAdjacentTriangle(triangle) { + let count = 0; + if(this._vertices[0] == triangle._vertices[this._nextIndex[0]] && this._vertices[this._nextIndex[0]] == triangle._vertices[0]) { + this._adjacentTriangles[0] = triangle; + this._adjacentPairIndex[0] = 0; + triangle._adjacentTriangles[0] = this; + triangle._adjacentPairIndex[0] = 0; + count = 1; + } + if(this._vertices[0] == triangle._vertices[this._nextIndex[1]] && this._vertices[this._nextIndex[0]] == triangle._vertices[1]) { + this._adjacentTriangles[0] = triangle; + this._adjacentPairIndex[0] = 1; + triangle._adjacentTriangles[1] = this; + triangle._adjacentPairIndex[1] = 0; + ++count; + } + if(this._vertices[0] == triangle._vertices[this._nextIndex[2]] && this._vertices[this._nextIndex[0]] == triangle._vertices[2]) { + this._adjacentTriangles[0] = triangle; + this._adjacentPairIndex[0] = 2; + triangle._adjacentTriangles[2] = this; + triangle._adjacentPairIndex[2] = 0; + ++count; + } + if(this._vertices[1] == triangle._vertices[this._nextIndex[0]] && this._vertices[this._nextIndex[1]] == triangle._vertices[0]) { + this._adjacentTriangles[1] = triangle; + this._adjacentPairIndex[1] = 0; + triangle._adjacentTriangles[0] = this; + triangle._adjacentPairIndex[0] = 1; + ++count; + } + if(this._vertices[1] == triangle._vertices[this._nextIndex[1]] && this._vertices[this._nextIndex[1]] == triangle._vertices[1]) { + this._adjacentTriangles[1] = triangle; + this._adjacentPairIndex[1] = 1; + triangle._adjacentTriangles[1] = this; + triangle._adjacentPairIndex[1] = 1; + ++count; + } + if(this._vertices[1] == triangle._vertices[this._nextIndex[2]] && this._vertices[this._nextIndex[1]] == triangle._vertices[2]) { + this._adjacentTriangles[1] = triangle; + this._adjacentPairIndex[1] = 2; + triangle._adjacentTriangles[2] = this; + triangle._adjacentPairIndex[2] = 1; + ++count; + } + if(this._vertices[2] == triangle._vertices[this._nextIndex[0]] && this._vertices[this._nextIndex[2]] == triangle._vertices[0]) { + this._adjacentTriangles[2] = triangle; + this._adjacentPairIndex[2] = 0; + triangle._adjacentTriangles[0] = this; + triangle._adjacentPairIndex[0] = 2; + ++count; + } + if(this._vertices[2] == triangle._vertices[this._nextIndex[1]] && this._vertices[this._nextIndex[2]] == triangle._vertices[1]) { + this._adjacentTriangles[2] = triangle; + this._adjacentPairIndex[2] = 1; + triangle._adjacentTriangles[1] = this; + triangle._adjacentPairIndex[1] = 2; + ++count; + } + if(this._vertices[2] == triangle._vertices[this._nextIndex[2]] && this._vertices[this._nextIndex[2]] == triangle._vertices[2]) { + this._adjacentTriangles[2] = triangle; + this._adjacentPairIndex[2] = 2; + triangle._adjacentTriangles[2] = this; + triangle._adjacentPairIndex[2] = 2; + ++count; + } + if(count != 1) { + return false; + } + return true; + } + removeAdjacentTriangles() { + let triangle = this._adjacentTriangles[0]; + if(triangle != null) { + let pairIndex = this._adjacentPairIndex[0]; + triangle._adjacentTriangles[pairIndex] = null; + triangle._adjacentPairIndex[pairIndex] = -1; + this._adjacentTriangles[0] = null; + this._adjacentPairIndex[0] = -1; + } + let triangle1 = this._adjacentTriangles[1]; + if(triangle1 != null) { + let pairIndex = this._adjacentPairIndex[1]; + triangle1._adjacentTriangles[pairIndex] = null; + triangle1._adjacentPairIndex[pairIndex] = -1; + this._adjacentTriangles[1] = null; + this._adjacentPairIndex[1] = -1; + } + let triangle2 = this._adjacentTriangles[2]; + if(triangle2 != null) { + let pairIndex = this._adjacentPairIndex[2]; + triangle2._adjacentTriangles[pairIndex] = null; + triangle2._adjacentPairIndex[pairIndex] = -1; + this._adjacentTriangles[2] = null; + this._adjacentPairIndex[2] = -1; + } + } + removeReferences() { + this._next = null; + this._prev = null; + this._tmpDfsId = 0; + this._tmpDfsVisible = false; + this._distanceSq = 0; + this._vertices[0] = null; + this._vertices[1] = null; + this._vertices[2] = null; + this._adjacentTriangles[0] = null; + this._adjacentTriangles[1] = null; + this._adjacentTriangles[2] = null; + this._adjacentPairIndex[0] = 0; + this._adjacentPairIndex[1] = 0; + this._adjacentPairIndex[2] = 0; + } + dump() { + } +} +oimo.collision.narrowphase.detector.gjkepa.EpaVertex = class oimo_collision_narrowphase_detector_gjkepa_EpaVertex { + constructor() { + this.randId = Math.random() * 100000 | 0; + this.v = new oimo.common.Vec3(); + this.w1 = new oimo.common.Vec3(); + this.w2 = new oimo.common.Vec3(); + } + init(v,w1,w2) { + let _this = this.v; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.w1; + _this1.x = w1.x; + _this1.y = w1.y; + _this1.z = w1.z; + let _this2 = this.w2; + _this2.x = w2.x; + _this2.y = w2.y; + _this2.z = w2.z; + this._next = null; + this._tmpEdgeLoopNext = null; + this._tmpEdgeLoopOuterTriangle = null; + return this; + } + removeReferences() { + this._next = null; + this._tmpEdgeLoopNext = null; + this._tmpEdgeLoopOuterTriangle = null; + } +} +oimo.collision.narrowphase.detector.gjkepa.GjkCache = class oimo_collision_narrowphase_detector_gjkepa_GjkCache { + constructor() { + this.prevClosestDir = new oimo.common.Vec3(); + } + clear() { + this.prevClosestDir.zero(); + } +} +if(!oimo.common) oimo.common = {}; +oimo.common.Vec3 = class oimo_common_Vec3 { + constructor(x,y,z) { + if(z == null) { + z = 0; + } + if(y == null) { + y = 0; + } + if(x == null) { + x = 0; + } + this.x = x; + this.y = y; + this.z = z; + oimo.common.Vec3.numCreations++; + } + init(x,y,z) { + this.x = x; + this.y = y; + this.z = z; + return this; + } + zero() { + this.x = 0; + this.y = 0; + this.z = 0; + return this; + } + add(v) { + return new oimo.common.Vec3(this.x + v.x,this.y + v.y,this.z + v.z); + } + add3(vx,vy,vz) { + return new oimo.common.Vec3(this.x + vx,this.y + vy,this.z + vz); + } + addScaled(v,s) { + return new oimo.common.Vec3(this.x + v.x * s,this.y + v.y * s,this.z + v.z * s); + } + sub(v) { + return new oimo.common.Vec3(this.x - v.x,this.y - v.y,this.z - v.z); + } + sub3(vx,vy,vz) { + return new oimo.common.Vec3(this.x - vx,this.y - vy,this.z - vz); + } + scale(s) { + return new oimo.common.Vec3(this.x * s,this.y * s,this.z * s); + } + scale3(sx,sy,sz) { + return new oimo.common.Vec3(this.x * sx,this.y * sy,this.z * sz); + } + dot(v) { + return this.x * v.x + this.y * v.y + this.z * v.z; + } + cross(v) { + return new oimo.common.Vec3(this.y * v.z - this.z * v.y,this.z * v.x - this.x * v.z,this.x * v.y - this.y * v.x); + } + addEq(v) { + this.x += v.x; + this.y += v.y; + this.z += v.z; + return this; + } + add3Eq(vx,vy,vz) { + this.x += vx; + this.y += vy; + this.z += vz; + return this; + } + addScaledEq(v,s) { + this.x += v.x * s; + this.y += v.y * s; + this.z += v.z * s; + return this; + } + subEq(v) { + this.x -= v.x; + this.y -= v.y; + this.z -= v.z; + return this; + } + sub3Eq(vx,vy,vz) { + this.x -= vx; + this.y -= vy; + this.z -= vz; + return this; + } + scaleEq(s) { + this.x *= s; + this.y *= s; + this.z *= s; + return this; + } + scale3Eq(sx,sy,sz) { + this.x *= sx; + this.y *= sy; + this.z *= sz; + return this; + } + crossEq(v) { + let y = this.z * v.x - this.x * v.z; + let z = this.x * v.y - this.y * v.x; + this.x = this.y * v.z - this.z * v.y; + this.y = y; + this.z = z; + return this; + } + mulMat3(m) { + return new oimo.common.Vec3(this.x * m.e00 + this.y * m.e01 + this.z * m.e02,this.x * m.e10 + this.y * m.e11 + this.z * m.e12,this.x * m.e20 + this.y * m.e21 + this.z * m.e22); + } + mulMat4(m) { + return new oimo.common.Vec3(this.x * m.e00 + this.y * m.e01 + this.z * m.e02 + m.e03,this.x * m.e10 + this.y * m.e11 + this.z * m.e12 + m.e13,this.x * m.e20 + this.y * m.e21 + this.z * m.e22 + m.e23); + } + mulTransform(tf) { + let vX; + let vY; + let vZ; + vX = this.x; + vY = this.y; + vZ = this.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * vX + tf._rotation01 * vY + tf._rotation02 * vZ; + __tmp__Y = tf._rotation10 * vX + tf._rotation11 * vY + tf._rotation12 * vZ; + __tmp__Z = tf._rotation20 * vX + tf._rotation21 * vY + tf._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += tf._positionX; + vY += tf._positionY; + vZ += tf._positionZ; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + mulMat3Eq(m) { + let y = this.x * m.e10 + this.y * m.e11 + this.z * m.e12; + let z = this.x * m.e20 + this.y * m.e21 + this.z * m.e22; + this.x = this.x * m.e00 + this.y * m.e01 + this.z * m.e02; + this.y = y; + this.z = z; + return this; + } + mulMat4Eq(m) { + let y = this.x * m.e10 + this.y * m.e11 + this.z * m.e12 + m.e13; + let z = this.x * m.e20 + this.y * m.e21 + this.z * m.e22 + m.e23; + this.x = this.x * m.e00 + this.y * m.e01 + this.z * m.e02 + m.e03; + this.y = y; + this.z = z; + return this; + } + mulTransformEq(tf) { + let vX; + let vY; + let vZ; + vX = this.x; + vY = this.y; + vZ = this.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf._rotation00 * vX + tf._rotation01 * vY + tf._rotation02 * vZ; + __tmp__Y = tf._rotation10 * vX + tf._rotation11 * vY + tf._rotation12 * vZ; + __tmp__Z = tf._rotation20 * vX + tf._rotation21 * vY + tf._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += tf._positionX; + vY += tf._positionY; + vZ += tf._positionZ; + this.x = vX; + this.y = vY; + this.z = vZ; + return this; + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + lengthSq() { + return this.x * this.x + this.y * this.y + this.z * this.z; + } + normalized() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + return new oimo.common.Vec3(this.x * invLen,this.y * invLen,this.z * invLen); + } + normalize() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + this.x *= invLen; + this.y *= invLen; + this.z *= invLen; + return this; + } + negate() { + return new oimo.common.Vec3(-this.x,-this.y,-this.z); + } + negateEq() { + this.x = -this.x; + this.y = -this.y; + this.z = -this.z; + return this; + } + copyFrom(v) { + this.x = v.x; + this.y = v.y; + this.z = v.z; + return this; + } + clone() { + return new oimo.common.Vec3(this.x,this.y,this.z); + } + toString() { + return "Vec3[" + (this.x > 0 ? (this.x * 10000000 + 0.5 | 0) / 10000000 : (this.x * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.y > 0 ? (this.y * 10000000 + 0.5 | 0) / 10000000 : (this.y * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.z > 0 ? (this.z * 10000000 + 0.5 | 0) / 10000000 : (this.z * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +oimo.common.Transform = class oimo_common_Transform { + constructor() { + this._positionX = 0; + this._positionY = 0; + this._positionZ = 0; + this._rotation00 = 1; + this._rotation01 = 0; + this._rotation02 = 0; + this._rotation10 = 0; + this._rotation11 = 1; + this._rotation12 = 0; + this._rotation20 = 0; + this._rotation21 = 0; + this._rotation22 = 1; + } + identity() { + this._positionX = 0; + this._positionY = 0; + this._positionZ = 0; + this._rotation00 = 1; + this._rotation01 = 0; + this._rotation02 = 0; + this._rotation10 = 0; + this._rotation11 = 1; + this._rotation12 = 0; + this._rotation20 = 0; + this._rotation21 = 0; + this._rotation22 = 1; + return this; + } + getPosition() { + let position = new oimo.common.Vec3(); + position.x = this._positionX; + position.y = this._positionY; + position.z = this._positionZ; + return position; + } + getPositionTo(position) { + position.x = this._positionX; + position.y = this._positionY; + position.z = this._positionZ; + } + setPosition(position) { + this._positionX = position.x; + this._positionY = position.y; + this._positionZ = position.z; + return this; + } + translate(translation) { + let diffX; + let diffY; + let diffZ; + diffX = translation.x; + diffY = translation.y; + diffZ = translation.z; + this._positionX += diffX; + this._positionY += diffY; + this._positionZ += diffZ; + } + getRotation() { + let rotation = new oimo.common.Mat3(); + rotation.e00 = this._rotation00; + rotation.e01 = this._rotation01; + rotation.e02 = this._rotation02; + rotation.e10 = this._rotation10; + rotation.e11 = this._rotation11; + rotation.e12 = this._rotation12; + rotation.e20 = this._rotation20; + rotation.e21 = this._rotation21; + rotation.e22 = this._rotation22; + return rotation; + } + getRotationTo(out) { + out.e00 = this._rotation00; + out.e01 = this._rotation01; + out.e02 = this._rotation02; + out.e10 = this._rotation10; + out.e11 = this._rotation11; + out.e12 = this._rotation12; + out.e20 = this._rotation20; + out.e21 = this._rotation21; + out.e22 = this._rotation22; + } + setRotation(rotation) { + this._rotation00 = rotation.e00; + this._rotation01 = rotation.e01; + this._rotation02 = rotation.e02; + this._rotation10 = rotation.e10; + this._rotation11 = rotation.e11; + this._rotation12 = rotation.e12; + this._rotation20 = rotation.e20; + this._rotation21 = rotation.e21; + this._rotation22 = rotation.e22; + return this; + } + setRotationXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + this._rotation00 = cy * cz; + this._rotation01 = -cy * sz; + this._rotation02 = sy; + this._rotation10 = cx * sz + cz * sx * sy; + this._rotation11 = cx * cz - sx * sy * sz; + this._rotation12 = -cy * sx; + this._rotation20 = sx * sz - cx * cz * sy; + this._rotation21 = cz * sx + cx * sy * sz; + this._rotation22 = cx * cy; + } + rotate(rotation) { + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + rot00 = rotation.e00; + rot01 = rotation.e01; + rot02 = rotation.e02; + rot10 = rotation.e10; + rot11 = rotation.e11; + rot12 = rotation.e12; + rot20 = rotation.e20; + rot21 = rotation.e21; + rot22 = rotation.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._rotation00 + rot01 * this._rotation10 + rot02 * this._rotation20; + __tmp__01 = rot00 * this._rotation01 + rot01 * this._rotation11 + rot02 * this._rotation21; + __tmp__02 = rot00 * this._rotation02 + rot01 * this._rotation12 + rot02 * this._rotation22; + __tmp__10 = rot10 * this._rotation00 + rot11 * this._rotation10 + rot12 * this._rotation20; + __tmp__11 = rot10 * this._rotation01 + rot11 * this._rotation11 + rot12 * this._rotation21; + __tmp__12 = rot10 * this._rotation02 + rot11 * this._rotation12 + rot12 * this._rotation22; + __tmp__20 = rot20 * this._rotation00 + rot21 * this._rotation10 + rot22 * this._rotation20; + __tmp__21 = rot20 * this._rotation01 + rot21 * this._rotation11 + rot22 * this._rotation21; + __tmp__22 = rot20 * this._rotation02 + rot21 * this._rotation12 + rot22 * this._rotation22; + this._rotation00 = __tmp__00; + this._rotation01 = __tmp__01; + this._rotation02 = __tmp__02; + this._rotation10 = __tmp__10; + this._rotation11 = __tmp__11; + this._rotation12 = __tmp__12; + this._rotation20 = __tmp__20; + this._rotation21 = __tmp__21; + this._rotation22 = __tmp__22; + } + rotateXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + rot00 = cy * cz; + rot01 = -cy * sz; + rot02 = sy; + rot10 = cx * sz + cz * sx * sy; + rot11 = cx * cz - sx * sy * sz; + rot12 = -cy * sx; + rot20 = sx * sz - cx * cz * sy; + rot21 = cz * sx + cx * sy * sz; + rot22 = cx * cy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._rotation00 + rot01 * this._rotation10 + rot02 * this._rotation20; + __tmp__01 = rot00 * this._rotation01 + rot01 * this._rotation11 + rot02 * this._rotation21; + __tmp__02 = rot00 * this._rotation02 + rot01 * this._rotation12 + rot02 * this._rotation22; + __tmp__10 = rot10 * this._rotation00 + rot11 * this._rotation10 + rot12 * this._rotation20; + __tmp__11 = rot10 * this._rotation01 + rot11 * this._rotation11 + rot12 * this._rotation21; + __tmp__12 = rot10 * this._rotation02 + rot11 * this._rotation12 + rot12 * this._rotation22; + __tmp__20 = rot20 * this._rotation00 + rot21 * this._rotation10 + rot22 * this._rotation20; + __tmp__21 = rot20 * this._rotation01 + rot21 * this._rotation11 + rot22 * this._rotation21; + __tmp__22 = rot20 * this._rotation02 + rot21 * this._rotation12 + rot22 * this._rotation22; + this._rotation00 = __tmp__00; + this._rotation01 = __tmp__01; + this._rotation02 = __tmp__02; + this._rotation10 = __tmp__10; + this._rotation11 = __tmp__11; + this._rotation12 = __tmp__12; + this._rotation20 = __tmp__20; + this._rotation21 = __tmp__21; + this._rotation22 = __tmp__22; + } + getOrientation() { + let q = new oimo.common.Quat(); + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._rotation00; + let e11 = this._rotation11; + let e22 = this._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation21 - this._rotation12) * s; + iqY = (this._rotation02 - this._rotation20) * s; + iqZ = (this._rotation10 - this._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation02 + this._rotation20) * s; + iqW = (this._rotation21 - this._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation02 - this._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + q.x = iqX; + q.y = iqY; + q.z = iqZ; + q.w = iqW; + return q; + } + getOrientationTo(orientation) { + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._rotation00; + let e11 = this._rotation11; + let e22 = this._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation21 - this._rotation12) * s; + iqY = (this._rotation02 - this._rotation20) * s; + iqZ = (this._rotation10 - this._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation02 + this._rotation20) * s; + iqW = (this._rotation21 - this._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation01 + this._rotation10) * s; + iqZ = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation02 - this._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._rotation02 + this._rotation20) * s; + iqY = (this._rotation12 + this._rotation21) * s; + iqW = (this._rotation10 - this._rotation01) * s; + } + orientation.x = iqX; + orientation.y = iqY; + orientation.z = iqZ; + orientation.w = iqW; + } + setOrientation(quaternion) { + let qX; + let qY; + let qZ; + let qW; + qX = quaternion.x; + qY = quaternion.y; + qZ = quaternion.z; + qW = quaternion.w; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._rotation00 = 1 - yy - zz; + this._rotation01 = xy - wz; + this._rotation02 = xz + wy; + this._rotation10 = xy + wz; + this._rotation11 = 1 - xx - zz; + this._rotation12 = yz - wx; + this._rotation20 = xz - wy; + this._rotation21 = yz + wx; + this._rotation22 = 1 - xx - yy; + return this; + } + clone() { + let tf = new oimo.common.Transform(); + tf._positionX = this._positionX; + tf._positionY = this._positionY; + tf._positionZ = this._positionZ; + tf._rotation00 = this._rotation00; + tf._rotation01 = this._rotation01; + tf._rotation02 = this._rotation02; + tf._rotation10 = this._rotation10; + tf._rotation11 = this._rotation11; + tf._rotation12 = this._rotation12; + tf._rotation20 = this._rotation20; + tf._rotation21 = this._rotation21; + tf._rotation22 = this._rotation22; + return tf; + } + copyFrom(transform) { + this._positionX = transform._positionX; + this._positionY = transform._positionY; + this._positionZ = transform._positionZ; + this._rotation00 = transform._rotation00; + this._rotation01 = transform._rotation01; + this._rotation02 = transform._rotation02; + this._rotation10 = transform._rotation10; + this._rotation11 = transform._rotation11; + this._rotation12 = transform._rotation12; + this._rotation20 = transform._rotation20; + this._rotation21 = transform._rotation21; + this._rotation22 = transform._rotation22; + return this; + } +} +oimo.common.Setting = class oimo_common_Setting { +} +oimo.collision.narrowphase.detector.gjkepa.GjkEpa = class oimo_collision_narrowphase_detector_gjkepa_GjkEpa { + constructor() { + this.s = new Array(4); + this.w1 = new Array(4); + this.w2 = new Array(4); + this.baseDirs = new Array(3); + this.baseDirs[0] = new oimo.common.Vec3(1,0,0); + this.baseDirs[1] = new oimo.common.Vec3(0,1,0); + this.baseDirs[2] = new oimo.common.Vec3(0,0,1); + this.tl1 = new oimo.common.Vec3(); + this.tl2 = new oimo.common.Vec3(); + this.rayX = new oimo.common.Vec3(); + this.rayR = new oimo.common.Vec3(); + this.tempTransform = new oimo.common.Transform(); + this.s[0] = new oimo.common.Vec3(); + this.w1[0] = new oimo.common.Vec3(); + this.w2[0] = new oimo.common.Vec3(); + this.s[1] = new oimo.common.Vec3(); + this.w1[1] = new oimo.common.Vec3(); + this.w2[1] = new oimo.common.Vec3(); + this.s[2] = new oimo.common.Vec3(); + this.w1[2] = new oimo.common.Vec3(); + this.w2[2] = new oimo.common.Vec3(); + this.s[3] = new oimo.common.Vec3(); + this.w1[3] = new oimo.common.Vec3(); + this.w2[3] = new oimo.common.Vec3(); + this.dir = new oimo.common.Vec3(); + this.closest = new oimo.common.Vec3(); + this.closestPoint1 = new oimo.common.Vec3(); + this.closestPoint2 = new oimo.common.Vec3(); + this.polyhedron = new oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedron(); + } + computeClosestPointsImpl(c1,c2,tf1,tf2,cache,useEpa) { + this.c1 = c1; + this.c2 = c2; + this.tf1 = tf1; + this.tf2 = tf2; + let s = this.s; + let w1 = this.w1; + let w2 = this.w2; + let closest = this.closest; + let dir = this.dir; + if(cache != null) { + if(cache._gjkCache == null) { + cache._gjkCache = new oimo.collision.narrowphase.detector.gjkepa.GjkCache(); + } + this.loadCache(cache._gjkCache); + } else { + dir.zero(); + } + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z == 0) { + let firstDirX; + let firstDirY; + let firstDirZ; + firstDirX = tf2._positionX - tf1._positionX; + firstDirY = tf2._positionY - tf1._positionY; + firstDirZ = tf2._positionZ - tf1._positionZ; + dir.x = firstDirX; + dir.y = firstDirY; + dir.z = firstDirZ; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-6) { + dir.init(1,0,0); + } + } + this.simplexSize = 0; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v = this.w1[this.simplexSize]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let v1 = this.w2[this.simplexSize]; + _this.x -= v1.x; + _this.y -= v1.y; + _this.z -= v1.z; + this.simplexSize = 1; + let count = 0; + while(count < 40) { + let v = 0; + switch(this.simplexSize) { + case 1: + let v1 = s[0]; + closest.x = v1.x; + closest.y = v1.y; + closest.z = v1.z; + v = 1; + break; + case 2: + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v2 = s[0]; + v1X = v2.x; + v1Y = v2.y; + v1Z = v2.z; + let v3 = s[1]; + v2X = v3.x; + v2Y = v3.y; + v2Z = v3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + v = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + v = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + v = 3; + } + break; + case 3: + let vec1 = s[0]; + let vec2 = s[1]; + let vec3 = s[2]; + let v1X1; + let v1Y1; + let v1Z1; + let v2X1; + let v2Y1; + let v2Z1; + let v3X; + let v3Y; + let v3Z; + let v12X1; + let v12Y1; + let v12Z1; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X1 = vec1.x; + v1Y1 = vec1.y; + v1Z1 = vec1.z; + v2X1 = vec2.x; + v2Y1 = vec2.y; + v2Z1 = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X1 = v2X1 - v1X1; + v12Y1 = v2Y1 - v1Y1; + v12Z1 = v2Z1 - v1Z1; + v23X = v3X - v2X1; + v23Y = v3Y - v2Y1; + v23Z = v3Z - v2Z1; + v31X = v1X1 - v3X; + v31Y = v1Y1 - v3Y; + v31Z = v1Z1 - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y1 * v23Z - v12Z1 * v23Y; + nY = v12Z1 * v23X - v12X1 * v23Z; + nZ = v12X1 * v23Y - v12Y1 * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y1 * nZ - v12Z1 * nY; + n12Y = v12Z1 * nX - v12X1 * nZ; + n12Z = v12X1 * nY - v12Y1 * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X1 * n12X + v1Y1 * n12Y + v1Z1 * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X1 * n23X + v2Y1 * n23Y + v2Z1 * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X1 * nX + v1Y1 * nY + v1Z1 * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = 7; + } + break; + case 4: + let vec11 = s[0]; + let vec21 = s[1]; + let vec31 = s[2]; + let vec4 = s[3]; + let v1X2; + let v1Y2; + let v1Z2; + let v2X2; + let v2Y2; + let v2Z2; + let v3X1; + let v3Y1; + let v3Z1; + let v4X; + let v4Y; + let v4Z; + let v12X2; + let v12Y2; + let v12Z2; + let v13X; + let v13Y; + let v13Z; + let v14X; + let v14Y; + let v14Z; + let v23X1; + let v23Y1; + let v23Z1; + let v24X; + let v24Y; + let v24Z; + v1X2 = vec11.x; + v1Y2 = vec11.y; + v1Z2 = vec11.z; + v2X2 = vec21.x; + v2Y2 = vec21.y; + v2Z2 = vec21.z; + v3X1 = vec31.x; + v3Y1 = vec31.y; + v3Z1 = vec31.z; + v4X = vec4.x; + v4Y = vec4.y; + v4Z = vec4.z; + v12X2 = v2X2 - v1X2; + v12Y2 = v2Y2 - v1Y2; + v12Z2 = v2Z2 - v1Z2; + v13X = v3X1 - v1X2; + v13Y = v3Y1 - v1Y2; + v13Z = v3Z1 - v1Z2; + v14X = v4X - v1X2; + v14Y = v4Y - v1Y2; + v14Z = v4Z - v1Z2; + v23X1 = v3X1 - v2X2; + v23Y1 = v3Y1 - v2Y2; + v23Z1 = v3Z1 - v2Z2; + v24X = v4X - v2X2; + v24Y = v4Y - v2Y2; + v24Z = v4Z - v2Z2; + let n123X; + let n123Y; + let n123Z; + let n134X; + let n134Y; + let n134Z; + let n142X; + let n142Y; + let n142Z; + let n243X; + let n243Y; + let n243Z; + n123X = v12Y2 * v13Z - v12Z2 * v13Y; + n123Y = v12Z2 * v13X - v12X2 * v13Z; + n123Z = v12X2 * v13Y - v12Y2 * v13X; + n134X = v13Y * v14Z - v13Z * v14Y; + n134Y = v13Z * v14X - v13X * v14Z; + n134Z = v13X * v14Y - v13Y * v14X; + n142X = v14Y * v12Z2 - v14Z * v12Y2; + n142Y = v14Z * v12X2 - v14X * v12Z2; + n142Z = v14X * v12Y2 - v14Y * v12X2; + n243X = v24Y * v23Z1 - v24Z * v23Y1; + n243Y = v24Z * v23X1 - v24X * v23Z1; + n243Z = v24X * v23Y1 - v24Y * v23X1; + let sign = v12X2 * n243X + v12Y2 * n243Y + v12Z2 * n243Z > 0 ? 1 : -1; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if((v1X2 * n123X + v1Y2 * n123Y + v1Z2 * n123Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec31.x; + v3Y = vec31.y; + v3Z = vec31.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + mini1 = b; + mind1 = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + if((v1X2 * n134X + v1Y2 * n134Y + v1Z2 * n134Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 6) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v1X2 * n142X + v1Y2 * n142Y + v1Z2 * n142Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 3 | (b & 4) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v2X2 * n243X + v2Y2 * n243Y + v2Z2 * n243Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if(mind1 > 0) { + closest.x = minvX1; + closest.y = minvY1; + closest.z = minvZ1; + v = mini1; + } else { + closest.zero(); + v = 15; + } + break; + } + if(closest.x * closest.x + closest.y * closest.y + closest.z * closest.z < 1e-008) { + if(!useEpa) { + this.distance = 0; + return 0; + } + switch(this.simplexSize) { + case 1: + this.pointToTetrahedron(); + break; + case 2: + this.lineToTetrahedron(); + break; + case 3: + this.triangleToTetrahedron(); + break; + } + if(this.simplexSize == 4) { + let epaState = this.computeDepth(c1,c2,tf1,tf2,s,w1,w2); + if(epaState != 0) { + this.distance = 0; + return epaState; + } + this.distance = -this.depth; + return 0; + } + this.distance = 0; + return 1; + } + this.shrinkSimplex(v); + dir.x = closest.x; + dir.y = closest.y; + dir.z = closest.z; + dir.x = -dir.x; + dir.y = -dir.y; + dir.z = -dir.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this.x = v4.x; + _this.y = v4.y; + _this.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this.x -= v5.x; + _this.y -= v5.y; + _this.z -= v5.z; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-008) { + throw new Error("!?"); + } + let _this1 = s[this.simplexSize]; + if(_this1.x * dir.x + _this1.y * dir.y + _this1.z * dir.z - (closest.x * dir.x + closest.y * dir.y + closest.z * dir.z) < 1e-008) { + this.interpolateClosestPoints(); + this.distance = Math.sqrt(closest.x * closest.x + closest.y * closest.y + closest.z * closest.z); + if(cache != null && cache._gjkCache != null) { + this.saveCache(cache._gjkCache); + } + return 0; + } + this.simplexSize++; + ++count; + } + return 2; + } + convexCastImpl(c1,c2,tf1,tf2,tl1,tl2,hit) { + this.c1 = c1; + this.c2 = c2; + this.tf1 = tf1; + this.tf2 = tf2; + let s = this.s; + let closest = this.closest; + let dir = this.dir; + let firstDirX; + let firstDirY; + let firstDirZ; + firstDirX = tf2._positionX - tf1._positionX; + firstDirY = tf2._positionY - tf1._positionY; + firstDirZ = tf2._positionZ - tf1._positionZ; + dir.x = firstDirX; + dir.y = firstDirY; + dir.z = firstDirZ; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-6) { + dir.init(1,0,0); + } + this.simplexSize = 0; + if(this.c1 != null) { + this.computeWitnessPoint1(true); + } else { + let v = this.w1[this.simplexSize]; + v.x = this.tf1._positionX; + v.y = this.tf1._positionY; + v.z = this.tf1._positionZ; + } + this.computeWitnessPoint2(true); + let _this = this.s[this.simplexSize]; + let v = this.w1[this.simplexSize]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let v1 = this.w2[this.simplexSize]; + _this.x -= v1.x; + _this.y -= v1.y; + _this.z -= v1.z; + this.simplexSize = 1; + let count = 0; + let lambda = 0.0; + let rayX = this.rayX; + let rayR = this.rayR; + rayX.zero(); + rayR.x = tl2.x; + rayR.y = tl2.y; + rayR.z = tl2.z; + rayR.x -= tl1.x; + rayR.y -= tl1.y; + rayR.z -= tl1.z; + while(count < 40) { + let v = 0; + switch(this.simplexSize) { + case 1: + let v1 = s[0]; + closest.x = v1.x; + closest.y = v1.y; + closest.z = v1.z; + v = 1; + break; + case 2: + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v2 = s[0]; + v1X = v2.x; + v1Y = v2.y; + v1Z = v2.z; + let v3 = s[1]; + v2X = v3.x; + v2Y = v3.y; + v2Z = v3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + v = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + v = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + v = 3; + } + break; + case 3: + let vec1 = s[0]; + let vec2 = s[1]; + let vec3 = s[2]; + let v1X1; + let v1Y1; + let v1Z1; + let v2X1; + let v2Y1; + let v2Z1; + let v3X; + let v3Y; + let v3Z; + let v12X1; + let v12Y1; + let v12Z1; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X1 = vec1.x; + v1Y1 = vec1.y; + v1Z1 = vec1.z; + v2X1 = vec2.x; + v2Y1 = vec2.y; + v2Z1 = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X1 = v2X1 - v1X1; + v12Y1 = v2Y1 - v1Y1; + v12Z1 = v2Z1 - v1Z1; + v23X = v3X - v2X1; + v23Y = v3Y - v2Y1; + v23Z = v3Z - v2Z1; + v31X = v1X1 - v3X; + v31Y = v1Y1 - v3Y; + v31Z = v1Z1 - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y1 * v23Z - v12Z1 * v23Y; + nY = v12Z1 * v23X - v12X1 * v23Z; + nZ = v12X1 * v23Y - v12Y1 * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y1 * nZ - v12Z1 * nY; + n12Y = v12Z1 * nX - v12X1 * nZ; + n12Z = v12X1 * nY - v12Y1 * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X1 * n12X + v1Y1 * n12Y + v1Z1 * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X1 * n23X + v2Y1 * n23Y + v2Z1 * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X1 * nX + v1Y1 * nY + v1Z1 * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + v = 7; + } + break; + case 4: + let vec11 = s[0]; + let vec21 = s[1]; + let vec31 = s[2]; + let vec4 = s[3]; + let v1X2; + let v1Y2; + let v1Z2; + let v2X2; + let v2Y2; + let v2Z2; + let v3X1; + let v3Y1; + let v3Z1; + let v4X; + let v4Y; + let v4Z; + let v12X2; + let v12Y2; + let v12Z2; + let v13X; + let v13Y; + let v13Z; + let v14X; + let v14Y; + let v14Z; + let v23X1; + let v23Y1; + let v23Z1; + let v24X; + let v24Y; + let v24Z; + v1X2 = vec11.x; + v1Y2 = vec11.y; + v1Z2 = vec11.z; + v2X2 = vec21.x; + v2Y2 = vec21.y; + v2Z2 = vec21.z; + v3X1 = vec31.x; + v3Y1 = vec31.y; + v3Z1 = vec31.z; + v4X = vec4.x; + v4Y = vec4.y; + v4Z = vec4.z; + v12X2 = v2X2 - v1X2; + v12Y2 = v2Y2 - v1Y2; + v12Z2 = v2Z2 - v1Z2; + v13X = v3X1 - v1X2; + v13Y = v3Y1 - v1Y2; + v13Z = v3Z1 - v1Z2; + v14X = v4X - v1X2; + v14Y = v4Y - v1Y2; + v14Z = v4Z - v1Z2; + v23X1 = v3X1 - v2X2; + v23Y1 = v3Y1 - v2Y2; + v23Z1 = v3Z1 - v2Z2; + v24X = v4X - v2X2; + v24Y = v4Y - v2Y2; + v24Z = v4Z - v2Z2; + let n123X; + let n123Y; + let n123Z; + let n134X; + let n134Y; + let n134Z; + let n142X; + let n142Y; + let n142Z; + let n243X; + let n243Y; + let n243Z; + n123X = v12Y2 * v13Z - v12Z2 * v13Y; + n123Y = v12Z2 * v13X - v12X2 * v13Z; + n123Z = v12X2 * v13Y - v12Y2 * v13X; + n134X = v13Y * v14Z - v13Z * v14Y; + n134Y = v13Z * v14X - v13X * v14Z; + n134Z = v13X * v14Y - v13Y * v14X; + n142X = v14Y * v12Z2 - v14Z * v12Y2; + n142Y = v14Z * v12X2 - v14X * v12Z2; + n142Z = v14X * v12Y2 - v14Y * v12X2; + n243X = v24Y * v23Z1 - v24Z * v23Y1; + n243Y = v24Z * v23X1 - v24X * v23Z1; + n243Z = v24X * v23Y1 - v24Y * v23X1; + let sign = v12X2 * n243X + v12Y2 * n243Y + v12Z2 * n243Z > 0 ? 1 : -1; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if((v1X2 * n123X + v1Y2 * n123Y + v1Z2 * n123Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec31.x; + v3Y = vec31.y; + v3Z = vec31.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + mini1 = b; + mind1 = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + if((v1X2 * n134X + v1Y2 * n134Y + v1Z2 * n134Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 6) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v1X2 * n142X + v1Y2 * n142Y + v1Z2 * n142Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec21.x; + v2Y = vec21.y; + v2Z = vec21.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec11.x; + v1Y = vec11.y; + v1Z = vec11.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 3 | (b & 4) << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if((v2X2 * n243X + v2Y2 * n243Y + v2Z2 * n243Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec31.x; + v2Y = vec31.y; + v2Z = vec31.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + mini = b; + mind = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec31.x; + v1Y = vec31.y; + v1Z = vec31.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec21.x; + v1Y = vec21.y; + v1Z = vec21.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + closest.x = v1X; + closest.y = v1Y; + closest.z = v1Z; + b = 1; + } else if(t > 1) { + closest.x = v2X; + closest.y = v2Y; + closest.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + closest.x = pX; + closest.y = pY; + closest.z = pZ; + b = 3; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = closest.x; + minvY = closest.y; + minvZ = closest.z; + } + } + let b; + if(mind > 0) { + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = mini; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + closest.x = minvX; + closest.y = minvY; + closest.z = minvZ; + b = 7; + } + let d = closest.x * closest.x + closest.y * closest.y + closest.z * closest.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = closest.x; + minvY1 = closest.y; + minvZ1 = closest.z; + } + } + if(mind1 > 0) { + closest.x = minvX1; + closest.y = minvY1; + closest.z = minvZ1; + v = mini1; + } else { + closest.zero(); + v = 15; + } + break; + } + this.shrinkSimplex(v); + if(closest.x * closest.x + closest.y * closest.y + closest.z * closest.z < 1e-008) { + if(lambda == 0 || this.simplexSize == 4) { + hit.fraction = lambda; + return false; + } + this.interpolateClosestPoints(); + hit.fraction = lambda; + let _this = hit.normal; + _this.x = dir.x; + _this.y = dir.y; + _this.z = dir.z; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let _this1 = hit.position; + let v = this.closestPoint1; + _this1.x = v.x; + _this1.y = v.y; + _this1.z = v.z; + _this1.x += tl1.x * lambda; + _this1.y += tl1.y * lambda; + _this1.z += tl1.z * lambda; + return true; + } + dir.x = closest.x; + dir.y = closest.y; + dir.z = closest.z; + dir.x = -dir.x; + dir.y = -dir.y; + dir.z = -dir.z; + if(this.c1 != null) { + this.computeWitnessPoint1(true); + } else { + let v = this.w1[this.simplexSize]; + v.x = this.tf1._positionX; + v.y = this.tf1._positionY; + v.z = this.tf1._positionZ; + } + this.computeWitnessPoint2(true); + let _this = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this.x = v4.x; + _this.y = v4.y; + _this.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this.x -= v5.x; + _this.y -= v5.y; + _this.z -= v5.z; + let _this1 = s[this.simplexSize]; + _this1.x -= rayX.x; + _this1.y -= rayX.y; + _this1.z -= rayX.z; + if(dir.x * dir.x + dir.y * dir.y + dir.z * dir.z < 1e-008) { + throw new Error("!?"); + } + let p = s[this.simplexSize]; + let pn = p.x * dir.x + p.y * dir.y + p.z * dir.z; + if(pn < 0) { + if(rayR.x * dir.x + rayR.y * dir.y + rayR.z * dir.z >= 0) { + return false; + } + let dLambda = pn / (rayR.x * dir.x + rayR.y * dir.y + rayR.z * dir.z); + lambda += dLambda; + if(lambda >= 1) { + return false; + } + rayX.x += rayR.x * dLambda; + rayX.y += rayR.y * dLambda; + rayX.z += rayR.z * dLambda; + let _g = 0; + let _g1 = this.simplexSize + 1; + while(_g < _g1) { + let _this = s[_g++]; + let s1 = -dLambda; + _this.x += rayR.x * s1; + _this.y += rayR.y * s1; + _this.z += rayR.z * s1; + } + } + let duplicate = false; + let _g = 0; + let _g1 = this.simplexSize; + while(_g < _g1) { + let i = _g++; + let dx = s[i].x - s[this.simplexSize].x; + let dy = s[i].y - s[this.simplexSize].y; + let dz = s[i].z - s[this.simplexSize].z; + if(dx * dx + dy * dy + dz * dz < 1e-008) { + duplicate = true; + break; + } + } + if(!duplicate) { + this.simplexSize++; + } + ++count; + } + return false; + } + interpolateClosestPoints() { + switch(this.simplexSize) { + case 1: + let _this = this.closestPoint1; + let v = this.w1[0]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.closestPoint2; + let v1 = this.w2[0]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + break; + case 2: + let cX; + let cY; + let cZ; + let v2 = this.closest; + cX = v2.x; + cY = v2.y; + cZ = v2.z; + let s0X; + let s0Y; + let s0Z; + let w10X; + let w10Y; + let w10Z; + let w20X; + let w20Y; + let w20Z; + let s1X; + let s1Y; + let s1Z; + let w11X; + let w11Y; + let w11Z; + let w21X; + let w21Y; + let w21Z; + let v3 = this.s[0]; + s0X = v3.x; + s0Y = v3.y; + s0Z = v3.z; + let v4 = this.w1[0]; + w10X = v4.x; + w10Y = v4.y; + w10Z = v4.z; + let v5 = this.w2[0]; + w20X = v5.x; + w20Y = v5.y; + w20Z = v5.z; + let v6 = this.s[1]; + s1X = v6.x; + s1Y = v6.y; + s1Z = v6.z; + let v7 = this.w1[1]; + w11X = v7.x; + w11Y = v7.y; + w11Z = v7.z; + let v8 = this.w2[1]; + w21X = v8.x; + w21Y = v8.y; + w21Z = v8.z; + let s01X; + let s01Y; + let s01Z; + s01X = s1X - s0X; + s01Y = s1Y - s0Y; + s01Z = s1Z - s0Z; + let invDet = s01X * s01X + s01Y * s01Y + s01Z * s01Z; + if(invDet != 0) { + invDet = 1 / invDet; + } + let s0cX; + let s0cY; + let s0cZ; + s0cX = cX - s0X; + s0cY = cY - s0Y; + s0cZ = cZ - s0Z; + let t = (s0cX * s01X + s0cY * s01Y + s0cZ * s01Z) * invDet; + let diffX; + let diffY; + let diffZ; + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + diffX = w11X - w10X; + diffY = w11Y - w10Y; + diffZ = w11Z - w10Z; + cp1X = w10X + diffX * t; + cp1Y = w10Y + diffY * t; + cp1Z = w10Z + diffZ * t; + diffX = w21X - w20X; + diffY = w21Y - w20Y; + diffZ = w21Z - w20Z; + cp2X = w20X + diffX * t; + cp2Y = w20Y + diffY * t; + cp2Z = w20Z + diffZ * t; + let v9 = this.closestPoint1; + v9.x = cp1X; + v9.y = cp1Y; + v9.z = cp1Z; + let v10 = this.closestPoint2; + v10.x = cp2X; + v10.y = cp2Y; + v10.z = cp2Z; + break; + case 3: + let cX1; + let cY1; + let cZ1; + let v11 = this.closest; + cX1 = v11.x; + cY1 = v11.y; + cZ1 = v11.z; + let s0X1; + let s0Y1; + let s0Z1; + let w10X1; + let w10Y1; + let w10Z1; + let w20X1; + let w20Y1; + let w20Z1; + let s1X1; + let s1Y1; + let s1Z1; + let w11X1; + let w11Y1; + let w11Z1; + let w21X1; + let w21Y1; + let w21Z1; + let s2X; + let s2Y; + let s2Z; + let w12X; + let w12Y; + let w12Z; + let w22X; + let w22Y; + let w22Z; + let v12 = this.s[0]; + s0X1 = v12.x; + s0Y1 = v12.y; + s0Z1 = v12.z; + let v13 = this.w1[0]; + w10X1 = v13.x; + w10Y1 = v13.y; + w10Z1 = v13.z; + let v14 = this.w2[0]; + w20X1 = v14.x; + w20Y1 = v14.y; + w20Z1 = v14.z; + let v15 = this.s[1]; + s1X1 = v15.x; + s1Y1 = v15.y; + s1Z1 = v15.z; + let v16 = this.w1[1]; + w11X1 = v16.x; + w11Y1 = v16.y; + w11Z1 = v16.z; + let v17 = this.w2[1]; + w21X1 = v17.x; + w21Y1 = v17.y; + w21Z1 = v17.z; + let v18 = this.s[2]; + s2X = v18.x; + s2Y = v18.y; + s2Z = v18.z; + let v19 = this.w1[2]; + w12X = v19.x; + w12Y = v19.y; + w12Z = v19.z; + let v20 = this.w2[2]; + w22X = v20.x; + w22Y = v20.y; + w22Z = v20.z; + let s01X1; + let s01Y1; + let s01Z1; + let s02X; + let s02Y; + let s02Z; + let s0cX1; + let s0cY1; + let s0cZ1; + s01X1 = s1X1 - s0X1; + s01Y1 = s1Y1 - s0Y1; + s01Z1 = s1Z1 - s0Z1; + s02X = s2X - s0X1; + s02Y = s2Y - s0Y1; + s02Z = s2Z - s0Z1; + s0cX1 = cX1 - s0X1; + s0cY1 = cY1 - s0Y1; + s0cZ1 = cZ1 - s0Z1; + let d11 = s01X1 * s01X1 + s01Y1 * s01Y1 + s01Z1 * s01Z1; + let d12 = s01X1 * s02X + s01Y1 * s02Y + s01Z1 * s02Z; + let d22 = s02X * s02X + s02Y * s02Y + s02Z * s02Z; + let d1c = s01X1 * s0cX1 + s01Y1 * s0cY1 + s01Z1 * s0cZ1; + let d2c = s02X * s0cX1 + s02Y * s0cY1 + s02Z * s0cZ1; + let invDet1 = d11 * d22 - d12 * d12; + if(invDet1 != 0) { + invDet1 = 1 / invDet1; + } + let s = (d1c * d22 - d2c * d12) * invDet1; + let t1 = (-d1c * d12 + d2c * d11) * invDet1; + let diffX1; + let diffY1; + let diffZ1; + let cp1X1; + let cp1Y1; + let cp1Z1; + let cp2X1; + let cp2Y1; + let cp2Z1; + diffX1 = w11X1 - w10X1; + diffY1 = w11Y1 - w10Y1; + diffZ1 = w11Z1 - w10Z1; + cp1X1 = w10X1 + diffX1 * s; + cp1Y1 = w10Y1 + diffY1 * s; + cp1Z1 = w10Z1 + diffZ1 * s; + diffX1 = w12X - w10X1; + diffY1 = w12Y - w10Y1; + diffZ1 = w12Z - w10Z1; + cp1X1 += diffX1 * t1; + cp1Y1 += diffY1 * t1; + cp1Z1 += diffZ1 * t1; + diffX1 = w21X1 - w20X1; + diffY1 = w21Y1 - w20Y1; + diffZ1 = w21Z1 - w20Z1; + cp2X1 = w20X1 + diffX1 * s; + cp2Y1 = w20Y1 + diffY1 * s; + cp2Z1 = w20Z1 + diffZ1 * s; + diffX1 = w22X - w20X1; + diffY1 = w22Y - w20Y1; + diffZ1 = w22Z - w20Z1; + cp2X1 += diffX1 * t1; + cp2Y1 += diffY1 * t1; + cp2Z1 += diffZ1 * t1; + let v21 = this.closestPoint1; + v21.x = cp1X1; + v21.y = cp1Y1; + v21.z = cp1Z1; + let v22 = this.closestPoint2; + v22.x = cp2X1; + v22.y = cp2Y1; + v22.z = cp2Z1; + break; + default: + throw new Error("!?"); + } + } + loadCache(gjkCache) { + let _this = this.dir; + let v = gjkCache.prevClosestDir; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + } + saveCache(gjkCache) { + let _this = gjkCache.prevClosestDir; + let v = this.closest; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x = -_this.x; + _this.y = -_this.y; + _this.z = -_this.z; + } + shrinkSimplex(vertexBits) { + this.simplexSize = vertexBits; + this.simplexSize = (this.simplexSize & 5) + (this.simplexSize >> 1 & 5); + this.simplexSize = (this.simplexSize & 3) + (this.simplexSize >> 2 & 3); + switch(vertexBits) { + case 2: + let _this = this.s[0]; + let v = this.s[1]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.w1[0]; + let v1 = this.w1[1]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + let _this2 = this.w2[0]; + let v2 = this.w2[1]; + _this2.x = v2.x; + _this2.y = v2.y; + _this2.z = v2.z; + break; + case 4: + let _this3 = this.s[0]; + let v3 = this.s[2]; + _this3.x = v3.x; + _this3.y = v3.y; + _this3.z = v3.z; + let _this4 = this.w1[0]; + let v4 = this.w1[2]; + _this4.x = v4.x; + _this4.y = v4.y; + _this4.z = v4.z; + let _this5 = this.w2[0]; + let v5 = this.w2[2]; + _this5.x = v5.x; + _this5.y = v5.y; + _this5.z = v5.z; + break; + case 5: + let _this6 = this.s[1]; + let v6 = this.s[2]; + _this6.x = v6.x; + _this6.y = v6.y; + _this6.z = v6.z; + let _this7 = this.w1[1]; + let v7 = this.w1[2]; + _this7.x = v7.x; + _this7.y = v7.y; + _this7.z = v7.z; + let _this8 = this.w2[1]; + let v8 = this.w2[2]; + _this8.x = v8.x; + _this8.y = v8.y; + _this8.z = v8.z; + break; + case 6: + let _this9 = this.s[0]; + let v9 = this.s[2]; + _this9.x = v9.x; + _this9.y = v9.y; + _this9.z = v9.z; + let _this10 = this.w1[0]; + let v10 = this.w1[2]; + _this10.x = v10.x; + _this10.y = v10.y; + _this10.z = v10.z; + let _this11 = this.w2[0]; + let v11 = this.w2[2]; + _this11.x = v11.x; + _this11.y = v11.y; + _this11.z = v11.z; + break; + case 8: + let _this12 = this.s[0]; + let v12 = this.s[3]; + _this12.x = v12.x; + _this12.y = v12.y; + _this12.z = v12.z; + let _this13 = this.w1[0]; + let v13 = this.w1[3]; + _this13.x = v13.x; + _this13.y = v13.y; + _this13.z = v13.z; + let _this14 = this.w2[0]; + let v14 = this.w2[3]; + _this14.x = v14.x; + _this14.y = v14.y; + _this14.z = v14.z; + break; + case 9: + let _this15 = this.s[1]; + let v15 = this.s[3]; + _this15.x = v15.x; + _this15.y = v15.y; + _this15.z = v15.z; + let _this16 = this.w1[1]; + let v16 = this.w1[3]; + _this16.x = v16.x; + _this16.y = v16.y; + _this16.z = v16.z; + let _this17 = this.w2[1]; + let v17 = this.w2[3]; + _this17.x = v17.x; + _this17.y = v17.y; + _this17.z = v17.z; + break; + case 10: + let _this18 = this.s[0]; + let v18 = this.s[3]; + _this18.x = v18.x; + _this18.y = v18.y; + _this18.z = v18.z; + let _this19 = this.w1[0]; + let v19 = this.w1[3]; + _this19.x = v19.x; + _this19.y = v19.y; + _this19.z = v19.z; + let _this20 = this.w2[0]; + let v20 = this.w2[3]; + _this20.x = v20.x; + _this20.y = v20.y; + _this20.z = v20.z; + break; + case 11: + let _this21 = this.s[2]; + let v21 = this.s[3]; + _this21.x = v21.x; + _this21.y = v21.y; + _this21.z = v21.z; + let _this22 = this.w1[2]; + let v22 = this.w1[3]; + _this22.x = v22.x; + _this22.y = v22.y; + _this22.z = v22.z; + let _this23 = this.w2[2]; + let v23 = this.w2[3]; + _this23.x = v23.x; + _this23.y = v23.y; + _this23.z = v23.z; + break; + case 12: + let _this24 = this.s[0]; + let v24 = this.s[2]; + _this24.x = v24.x; + _this24.y = v24.y; + _this24.z = v24.z; + let _this25 = this.w1[0]; + let v25 = this.w1[2]; + _this25.x = v25.x; + _this25.y = v25.y; + _this25.z = v25.z; + let _this26 = this.w2[0]; + let v26 = this.w2[2]; + _this26.x = v26.x; + _this26.y = v26.y; + _this26.z = v26.z; + let _this27 = this.s[1]; + let v27 = this.s[3]; + _this27.x = v27.x; + _this27.y = v27.y; + _this27.z = v27.z; + let _this28 = this.w1[1]; + let v28 = this.w1[3]; + _this28.x = v28.x; + _this28.y = v28.y; + _this28.z = v28.z; + let _this29 = this.w2[1]; + let v29 = this.w2[3]; + _this29.x = v29.x; + _this29.y = v29.y; + _this29.z = v29.z; + break; + case 13: + let _this30 = this.s[1]; + let v30 = this.s[3]; + _this30.x = v30.x; + _this30.y = v30.y; + _this30.z = v30.z; + let _this31 = this.w1[1]; + let v31 = this.w1[3]; + _this31.x = v31.x; + _this31.y = v31.y; + _this31.z = v31.z; + let _this32 = this.w2[1]; + let v32 = this.w2[3]; + _this32.x = v32.x; + _this32.y = v32.y; + _this32.z = v32.z; + break; + case 14: + let _this33 = this.s[0]; + let v33 = this.s[3]; + _this33.x = v33.x; + _this33.y = v33.y; + _this33.z = v33.z; + let _this34 = this.w1[0]; + let v34 = this.w1[3]; + _this34.x = v34.x; + _this34.y = v34.y; + _this34.z = v34.z; + let _this35 = this.w2[0]; + let v35 = this.w2[3]; + _this35.x = v35.x; + _this35.y = v35.y; + _this35.z = v35.z; + break; + } + } + computeWitnessPoint1(addMargin) { + let tmpX; + let tmpY; + let tmpZ; + let idirX; + let idirY; + let idirZ; + let v = this.dir; + idirX = v.x; + idirY = v.y; + idirZ = v.z; + let ldir1X; + let ldir1Y; + let ldir1Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this.tf1._rotation00 * idirX + this.tf1._rotation10 * idirY + this.tf1._rotation20 * idirZ; + __tmp__Y = this.tf1._rotation01 * idirX + this.tf1._rotation11 * idirY + this.tf1._rotation21 * idirZ; + __tmp__Z = this.tf1._rotation02 * idirX + this.tf1._rotation12 * idirY + this.tf1._rotation22 * idirZ; + ldir1X = __tmp__X; + ldir1Y = __tmp__Y; + ldir1Z = __tmp__Z; + let iw1X; + let iw1Y; + let iw1Z; + let v1 = this.dir; + v1.x = ldir1X; + v1.y = ldir1Y; + v1.z = ldir1Z; + this.c1.computeLocalSupportingVertex(this.dir,this.w1[this.simplexSize]); + if(addMargin) { + let _this = this.dir; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let _this1 = this.w1[this.simplexSize]; + let v = this.dir; + let s = this.c1._gjkMargin; + _this1.x += v.x * s; + _this1.y += v.y * s; + _this1.z += v.z * s; + } + let v2 = this.w1[this.simplexSize]; + tmpX = v2.x; + tmpY = v2.y; + tmpZ = v2.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this.tf1._rotation00 * tmpX + this.tf1._rotation01 * tmpY + this.tf1._rotation02 * tmpZ; + __tmp__Y1 = this.tf1._rotation10 * tmpX + this.tf1._rotation11 * tmpY + this.tf1._rotation12 * tmpZ; + __tmp__Z1 = this.tf1._rotation20 * tmpX + this.tf1._rotation21 * tmpY + this.tf1._rotation22 * tmpZ; + iw1X = __tmp__X1; + iw1Y = __tmp__Y1; + iw1Z = __tmp__Z1; + iw1X += this.tf1._positionX; + iw1Y += this.tf1._positionY; + iw1Z += this.tf1._positionZ; + let v3 = this.w1[this.simplexSize]; + v3.x = iw1X; + v3.y = iw1Y; + v3.z = iw1Z; + let v4 = this.dir; + v4.x = idirX; + v4.y = idirY; + v4.z = idirZ; + } + computeWitnessPoint2(addMargin) { + let tmpX; + let tmpY; + let tmpZ; + let idirX; + let idirY; + let idirZ; + let v = this.dir; + idirX = v.x; + idirY = v.y; + idirZ = v.z; + let ldir2X; + let ldir2Y; + let ldir2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this.tf2._rotation00 * idirX + this.tf2._rotation10 * idirY + this.tf2._rotation20 * idirZ; + __tmp__Y = this.tf2._rotation01 * idirX + this.tf2._rotation11 * idirY + this.tf2._rotation21 * idirZ; + __tmp__Z = this.tf2._rotation02 * idirX + this.tf2._rotation12 * idirY + this.tf2._rotation22 * idirZ; + ldir2X = __tmp__X; + ldir2Y = __tmp__Y; + ldir2Z = __tmp__Z; + ldir2X = -ldir2X; + ldir2Y = -ldir2Y; + ldir2Z = -ldir2Z; + let iw2X; + let iw2Y; + let iw2Z; + let v1 = this.dir; + v1.x = ldir2X; + v1.y = ldir2Y; + v1.z = ldir2Z; + this.c2.computeLocalSupportingVertex(this.dir,this.w2[this.simplexSize]); + if(addMargin) { + let _this = this.dir; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + let _this1 = this.w2[this.simplexSize]; + let v = this.dir; + let s = this.c2._gjkMargin; + _this1.x += v.x * s; + _this1.y += v.y * s; + _this1.z += v.z * s; + } + let v2 = this.w2[this.simplexSize]; + tmpX = v2.x; + tmpY = v2.y; + tmpZ = v2.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this.tf2._rotation00 * tmpX + this.tf2._rotation01 * tmpY + this.tf2._rotation02 * tmpZ; + __tmp__Y1 = this.tf2._rotation10 * tmpX + this.tf2._rotation11 * tmpY + this.tf2._rotation12 * tmpZ; + __tmp__Z1 = this.tf2._rotation20 * tmpX + this.tf2._rotation21 * tmpY + this.tf2._rotation22 * tmpZ; + iw2X = __tmp__X1; + iw2Y = __tmp__Y1; + iw2Z = __tmp__Z1; + iw2X += this.tf2._positionX; + iw2Y += this.tf2._positionY; + iw2Z += this.tf2._positionZ; + let v3 = this.w2[this.simplexSize]; + v3.x = iw2X; + v3.y = iw2Y; + v3.z = iw2Z; + let v4 = this.dir; + v4.x = idirX; + v4.y = idirY; + v4.z = idirZ; + } + pointToTetrahedron() { + let _g = 0; + while(_g < 3) { + let _this = this.dir; + let v = this.baseDirs[_g++]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this1 = this.s[this.simplexSize]; + let v1 = this.w1[this.simplexSize]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + let v2 = this.w2[this.simplexSize]; + _this1.x -= v2.x; + _this1.y -= v2.y; + _this1.z -= v2.z; + this.simplexSize++; + this.lineToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + let _this2 = this.dir; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this3 = this.s[this.simplexSize]; + let v3 = this.w1[this.simplexSize]; + _this3.x = v3.x; + _this3.y = v3.y; + _this3.z = v3.z; + let v4 = this.w2[this.simplexSize]; + _this3.x -= v4.x; + _this3.y -= v4.y; + _this3.z -= v4.z; + this.simplexSize++; + this.lineToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + } + } + lineToTetrahedron() { + let oldDirX; + let oldDirY; + let oldDirZ; + let v = this.dir; + oldDirX = v.x; + oldDirY = v.y; + oldDirZ = v.z; + let s0X; + let s0Y; + let s0Z; + let s1X; + let s1Y; + let s1Z; + let lineDirX; + let lineDirY; + let lineDirZ; + let v1 = this.s[0]; + s0X = v1.x; + s0Y = v1.y; + s0Z = v1.z; + let v2 = this.s[1]; + s1X = v2.x; + s1Y = v2.y; + s1Z = v2.z; + lineDirX = s0X - s1X; + lineDirY = s0Y - s1Y; + lineDirZ = s0Z - s1Z; + let _g = 0; + while(_g < 3) { + let baseDirX; + let baseDirY; + let baseDirZ; + let v = this.baseDirs[_g++]; + baseDirX = v.x; + baseDirY = v.y; + baseDirZ = v.z; + let newDirX; + let newDirY; + let newDirZ; + newDirX = lineDirY * baseDirZ - lineDirZ * baseDirY; + newDirY = lineDirZ * baseDirX - lineDirX * baseDirZ; + newDirZ = lineDirX * baseDirY - lineDirY * baseDirX; + let v1 = this.dir; + v1.x = newDirX; + v1.y = newDirY; + v1.z = newDirZ; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v2 = this.w1[this.simplexSize]; + _this.x = v2.x; + _this.y = v2.y; + _this.z = v2.z; + let v3 = this.w2[this.simplexSize]; + _this.x -= v3.x; + _this.y -= v3.y; + _this.z -= v3.z; + this.simplexSize++; + this.triangleToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + let _this1 = this.dir; + _this1.x = -_this1.x; + _this1.y = -_this1.y; + _this1.z = -_this1.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this2 = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this2.x = v4.x; + _this2.y = v4.y; + _this2.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this2.x -= v5.x; + _this2.y -= v5.y; + _this2.z -= v5.z; + this.simplexSize++; + this.triangleToTetrahedron(); + if(this.simplexSize == 4) { + break; + } + this.simplexSize--; + } + let v3 = this.dir; + v3.x = oldDirX; + v3.y = oldDirY; + v3.z = oldDirZ; + } + triangleToTetrahedron() { + let oldDirX; + let oldDirY; + let oldDirZ; + let v = this.dir; + oldDirX = v.x; + oldDirY = v.y; + oldDirZ = v.z; + while(true) { + let s0X; + let s0Y; + let s0Z; + let s1X; + let s1Y; + let s1Z; + let s2X; + let s2Y; + let s2Z; + let s01X; + let s01Y; + let s01Z; + let s02X; + let s02Y; + let s02Z; + let v = this.s[0]; + s0X = v.x; + s0Y = v.y; + s0Z = v.z; + let v1 = this.s[1]; + s1X = v1.x; + s1Y = v1.y; + s1Z = v1.z; + let v2 = this.s[2]; + s2X = v2.x; + s2Y = v2.y; + s2Z = v2.z; + s01X = s1X - s0X; + s01Y = s1Y - s0Y; + s01Z = s1Z - s0Z; + s02X = s2X - s0X; + s02Y = s2Y - s0Y; + s02Z = s2Z - s0Z; + let nX; + let nY; + let nZ; + nX = s01Y * s02Z - s01Z * s02Y; + nY = s01Z * s02X - s01X * s02Z; + nZ = s01X * s02Y - s01Y * s02X; + let v3 = this.dir; + v3.x = nX; + v3.y = nY; + v3.z = nZ; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this = this.s[this.simplexSize]; + let v4 = this.w1[this.simplexSize]; + _this.x = v4.x; + _this.y = v4.y; + _this.z = v4.z; + let v5 = this.w2[this.simplexSize]; + _this.x -= v5.x; + _this.y -= v5.y; + _this.z -= v5.z; + this.simplexSize++; + if(this.isValidTetrahedron()) { + break; + } + this.simplexSize--; + let _this1 = this.dir; + _this1.x = -_this1.x; + _this1.y = -_this1.y; + _this1.z = -_this1.z; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this2 = this.s[this.simplexSize]; + let v6 = this.w1[this.simplexSize]; + _this2.x = v6.x; + _this2.y = v6.y; + _this2.z = v6.z; + let v7 = this.w2[this.simplexSize]; + _this2.x -= v7.x; + _this2.y -= v7.y; + _this2.z -= v7.z; + this.simplexSize++; + if(this.isValidTetrahedron()) { + break; + } + this.simplexSize--; + break; + } + let v1 = this.dir; + v1.x = oldDirX; + v1.y = oldDirY; + v1.z = oldDirZ; + } + isValidTetrahedron() { + let e10 = this.s[2].x - this.s[0].x; + let e11 = this.s[2].y - this.s[0].y; + let e12 = this.s[2].z - this.s[0].z; + let e20 = this.s[3].x - this.s[0].x; + let e21 = this.s[3].y - this.s[0].y; + let e22 = this.s[3].z - this.s[0].z; + let det = (this.s[1].x - this.s[0].x) * (e11 * e22 - e12 * e21) - (this.s[1].y - this.s[0].y) * (e10 * e22 - e12 * e20) + (this.s[1].z - this.s[0].z) * (e10 * e21 - e11 * e20); + if(!(det > 1e-12)) { + return det < -1e-12; + } else { + return true; + } + } + computeDepth(convex1,convex2,tf1,tf2,initialPolyhedron,initialPolyhedron1,initialPolyhedron2) { + let _this = this.polyhedron; + while(_this._numTriangles > 0) { + let t = _this._triangleList; + _this._numTriangles--; + let prev = t._prev; + let next = t._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(t == _this._triangleList) { + _this._triangleList = _this._triangleList._next; + } + if(t == _this._triangleListLast) { + _this._triangleListLast = _this._triangleListLast._prev; + } + t._next = null; + t._prev = null; + t.removeReferences(); + t._next = _this._trianglePool; + _this._trianglePool = t; + } + while(_this._numVertices > 0) { + let v = _this._vertices[--_this._numVertices]; + v.removeReferences(); + v._next = _this._vertexPool; + _this._vertexPool = v; + } + let tmp = this.polyhedron; + let _this1 = this.polyhedron; + let first = _this1._vertexPool; + if(first != null) { + _this1._vertexPool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let tmp1 = first.init(initialPolyhedron[0],initialPolyhedron1[0],initialPolyhedron2[0]); + let _this2 = this.polyhedron; + let first1 = _this2._vertexPool; + if(first1 != null) { + _this2._vertexPool = first1._next; + first1._next = null; + } else { + first1 = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let tmp2 = first1.init(initialPolyhedron[1],initialPolyhedron1[1],initialPolyhedron2[1]); + let _this3 = this.polyhedron; + let first2 = _this3._vertexPool; + if(first2 != null) { + _this3._vertexPool = first2._next; + first2._next = null; + } else { + first2 = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let tmp3 = first2.init(initialPolyhedron[2],initialPolyhedron1[2],initialPolyhedron2[2]); + let _this4 = this.polyhedron; + let first3 = _this4._vertexPool; + if(first3 != null) { + _this4._vertexPool = first3._next; + first3._next = null; + } else { + first3 = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + if(!tmp._init(tmp1,tmp2,tmp3,first3.init(initialPolyhedron[3],initialPolyhedron1[3],initialPolyhedron2[3]))) { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_INIT; + } + this.simplexSize = 0; + let supportingVertex = this.s[0]; + let witness1 = this.w1[0]; + let witness2 = this.w2[0]; + let count = 0; + while(count < 40) { + let f = this.polyhedron._triangleList; + let mind = 1e65536; + let minf = null; + while(f != null) { + if(f._distanceSq < mind) { + mind = f._distanceSq; + minf = f; + } + f = f._next; + } + let face = minf; + let _this = this.dir; + let v = face._normal; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + this.computeWitnessPoint1(false); + this.computeWitnessPoint2(false); + let _this1 = this.s[this.simplexSize]; + let v1 = this.w1[this.simplexSize]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + let v2 = this.w2[this.simplexSize]; + _this1.x -= v2.x; + _this1.y -= v2.y; + _this1.z -= v2.z; + let v0 = face._vertices[0]; + let v11 = face._vertices[1]; + let v21 = face._vertices[2]; + let _this2 = v0.v; + let v3 = this.dir; + let v4 = this.dir; + if(supportingVertex.x * v4.x + supportingVertex.y * v4.y + supportingVertex.z * v4.z - (_this2.x * v3.x + _this2.y * v3.y + _this2.z * v3.z) < 1e-6 || count == 39) { + let _this = this.closest; + let v = this.dir; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.dir; + let v1 = v0.v; + let _this2 = this.dir; + let s = (_this1.x * v1.x + _this1.y * v1.y + _this1.z * v1.z) / (_this2.x * _this2.x + _this2.y * _this2.y + _this2.z * _this2.z); + _this.x *= s; + _this.y *= s; + _this.z *= s; + let cX; + let cY; + let cZ; + let v2 = this.closest; + cX = v2.x; + cY = v2.y; + cZ = v2.z; + let s0X; + let s0Y; + let s0Z; + let w10X; + let w10Y; + let w10Z; + let w20X; + let w20Y; + let w20Z; + let s1X; + let s1Y; + let s1Z; + let w11X; + let w11Y; + let w11Z; + let w21X; + let w21Y; + let w21Z; + let s2X; + let s2Y; + let s2Z; + let w12X; + let w12Y; + let w12Z; + let w22X; + let w22Y; + let w22Z; + let v3 = v0.v; + s0X = v3.x; + s0Y = v3.y; + s0Z = v3.z; + let v4 = v0.w1; + w10X = v4.x; + w10Y = v4.y; + w10Z = v4.z; + let v5 = v0.w2; + w20X = v5.x; + w20Y = v5.y; + w20Z = v5.z; + let v6 = v11.v; + s1X = v6.x; + s1Y = v6.y; + s1Z = v6.z; + let v7 = v11.w1; + w11X = v7.x; + w11Y = v7.y; + w11Z = v7.z; + let v8 = v11.w2; + w21X = v8.x; + w21Y = v8.y; + w21Z = v8.z; + let v9 = v21.v; + s2X = v9.x; + s2Y = v9.y; + s2Z = v9.z; + let v10 = v21.w1; + w12X = v10.x; + w12Y = v10.y; + w12Z = v10.z; + let v12 = v21.w2; + w22X = v12.x; + w22Y = v12.y; + w22Z = v12.z; + let s01X; + let s01Y; + let s01Z; + let s02X; + let s02Y; + let s02Z; + let s0cX; + let s0cY; + let s0cZ; + s01X = s1X - s0X; + s01Y = s1Y - s0Y; + s01Z = s1Z - s0Z; + s02X = s2X - s0X; + s02Y = s2Y - s0Y; + s02Z = s2Z - s0Z; + s0cX = cX - s0X; + s0cY = cY - s0Y; + s0cZ = cZ - s0Z; + let d11 = s01X * s01X + s01Y * s01Y + s01Z * s01Z; + let d12 = s01X * s02X + s01Y * s02Y + s01Z * s02Z; + let d22 = s02X * s02X + s02Y * s02Y + s02Z * s02Z; + let d1c = s01X * s0cX + s01Y * s0cY + s01Z * s0cZ; + let d2c = s02X * s0cX + s02Y * s0cY + s02Z * s0cZ; + let invDet = d11 * d22 - d12 * d12; + if(invDet != 0) { + invDet = 1 / invDet; + } + let s1 = (d1c * d22 - d2c * d12) * invDet; + let t = (-d1c * d12 + d2c * d11) * invDet; + let diffX; + let diffY; + let diffZ; + let cp1X; + let cp1Y; + let cp1Z; + let cp2X; + let cp2Y; + let cp2Z; + diffX = w11X - w10X; + diffY = w11Y - w10Y; + diffZ = w11Z - w10Z; + cp1X = w10X + diffX * s1; + cp1Y = w10Y + diffY * s1; + cp1Z = w10Z + diffZ * s1; + diffX = w12X - w10X; + diffY = w12Y - w10Y; + diffZ = w12Z - w10Z; + cp1X += diffX * t; + cp1Y += diffY * t; + cp1Z += diffZ * t; + diffX = w21X - w20X; + diffY = w21Y - w20Y; + diffZ = w21Z - w20Z; + cp2X = w20X + diffX * s1; + cp2Y = w20Y + diffY * s1; + cp2Z = w20Z + diffZ * s1; + diffX = w22X - w20X; + diffY = w22Y - w20Y; + diffZ = w22Z - w20Z; + cp2X += diffX * t; + cp2Y += diffY * t; + cp2Z += diffZ * t; + let v13 = this.closestPoint1; + v13.x = cp1X; + v13.y = cp1Y; + v13.z = cp1Z; + let v14 = this.closestPoint2; + v14.x = cp2X; + v14.y = cp2Y; + v14.z = cp2Z; + let _this3 = this.closest; + this.depth = Math.sqrt(_this3.x * _this3.x + _this3.y * _this3.y + _this3.z * _this3.z); + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.SUCCEEDED; + } + let _this3 = this.polyhedron; + let first = _this3._vertexPool; + if(first != null) { + _this3._vertexPool = first._next; + first._next = null; + } else { + first = new oimo.collision.narrowphase.detector.gjkepa.EpaVertex(); + } + let epaVertex = first.init(supportingVertex,witness1,witness2); + if(!this.polyhedron._addVertex(epaVertex,face)) { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_ADD_VERTEX; + } + ++count; + } + return oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_DID_NOT_CONVERGE; + } + computeClosestPoints(c1,c2,tf1,tf2,cache) { + return this.computeClosestPointsImpl(c1,c2,tf1,tf2,cache,true); + } + computeDistance(c1,c2,tf1,tf2,cache) { + return this.computeClosestPointsImpl(c1,c2,tf1,tf2,cache,false); + } + convexCast(c1,c2,tf1,tf2,tl1,tl2,hit) { + return this.convexCastImpl(c1,c2,tf1,tf2,tl1,tl2,hit); + } + rayCast(c,tf,begin,end,hit) { + let tf1 = this.tempTransform; + tf1._positionX = begin.x; + tf1._positionY = begin.y; + tf1._positionZ = begin.z; + let tl1 = this.tl1; + let tl2 = this.tl2; + tl1.x = end.x; + tl1.y = end.y; + tl1.z = end.z; + tl1.x -= begin.x; + tl1.y -= begin.y; + tl1.z -= begin.z; + tl2.zero(); + return this.convexCastImpl(null,c,tf1,tf,tl1,tl2,hit); + } + static getInstance() { + return oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance; + } +} +oimo.collision.narrowphase.detector.gjkepa.GjkEpaLog = class oimo_collision_narrowphase_detector_gjkepa_GjkEpaLog { +} +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState = class oimo_collision_narrowphase_detector_gjkepa_GjkEpaResultState { +} +oimo.collision.narrowphase.detector.gjkepa.SimplexUtil = class oimo_collision_narrowphase_detector_gjkepa_SimplexUtil { + static projectOrigin2(vec1,vec2,out) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + return 1; + } + if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + return 2; + } + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + return 3; + } + static projectOrigin3(vec1,vec2,vec3,out) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini = b; + mind = out.x * out.x + out.y * out.y + out.z * out.z; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 2) << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(mind > 0) { + out.x = minvX; + out.y = minvY; + out.z = minvZ; + return mini; + } + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX = nX * l2; + minvY = nY * l2; + minvZ = nZ * l2; + out.x = minvX; + out.y = minvY; + out.z = minvZ; + return 7; + } + static projectOrigin4(vec1,vec2,vec3,vec4,out) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v4X; + let v4Y; + let v4Z; + let v12X; + let v12Y; + let v12Z; + let v13X; + let v13Y; + let v13Z; + let v14X; + let v14Y; + let v14Z; + let v23X; + let v23Y; + let v23Z; + let v24X; + let v24Y; + let v24Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v4X = vec4.x; + v4Y = vec4.y; + v4Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v13X = v3X - v1X; + v13Y = v3Y - v1Y; + v13Z = v3Z - v1Z; + v14X = v4X - v1X; + v14Y = v4Y - v1Y; + v14Z = v4Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v24X = v4X - v2X; + v24Y = v4Y - v2Y; + v24Z = v4Z - v2Z; + let n123X; + let n123Y; + let n123Z; + let n134X; + let n134Y; + let n134Z; + let n142X; + let n142Y; + let n142Z; + let n243X; + let n243Y; + let n243Z; + n123X = v12Y * v13Z - v12Z * v13Y; + n123Y = v12Z * v13X - v12X * v13Z; + n123Z = v12X * v13Y - v12Y * v13X; + n134X = v13Y * v14Z - v13Z * v14Y; + n134Y = v13Z * v14X - v13X * v14Z; + n134Z = v13X * v14Y - v13Y * v14X; + n142X = v14Y * v12Z - v14Z * v12Y; + n142Y = v14Z * v12X - v14X * v12Z; + n142Z = v14X * v12Y - v14Y * v12X; + n243X = v24Y * v23Z - v24Z * v23Y; + n243Y = v24Z * v23X - v24X * v23Z; + n243Z = v24X * v23Y - v24Y * v23X; + let sign = v12X * n243X + v12Y * n243Y + v12Z * n243Z > 0 ? 1 : -1; + let mind = -1; + let minvX; + let minvY; + let minvZ; + let mini = 0; + minvX = 0; + minvY = 0; + minvZ = 0; + if((v1X * n123X + v1Y * n123Y + v1Z * n123Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec3.x; + v3Y = vec3.y; + v3Z = vec3.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + mini = b; + mind = out.x * out.x + out.y * out.y + out.z * out.z; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + if((v1X * n134X + v1Y * n134Y + v1Z * n134Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec3.x; + v1Y = vec3.y; + v1Z = vec3.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b & 1 | (b & 6) << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if((v1X * n142X + v1Y * n142Y + v1Z * n142Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec2.x; + v2Y = vec2.y; + v2Z = vec2.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec1.x; + v1Y = vec1.y; + v1Z = vec1.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b & 3 | (b & 4) << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if((v2X * n243X + v2Y * n243Y + v2Z * n243Z) * sign < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + let v3X; + let v3Y; + let v3Z; + let v12X; + let v12Y; + let v12Z; + let v23X; + let v23Y; + let v23Z; + let v31X; + let v31Y; + let v31Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + v3X = vec4.x; + v3Y = vec4.y; + v3Z = vec4.z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + v23X = v3X - v2X; + v23Y = v3Y - v2Y; + v23Z = v3Z - v2Z; + v31X = v1X - v3X; + v31Y = v1Y - v3Y; + v31Z = v1Z - v3Z; + let nX; + let nY; + let nZ; + nX = v12Y * v23Z - v12Z * v23Y; + nY = v12Z * v23X - v12X * v23Z; + nZ = v12X * v23Y - v12Y * v23X; + let n12X; + let n12Y; + let n12Z; + let n23X; + let n23Y; + let n23Z; + let n31X; + let n31Y; + let n31Z; + n12X = v12Y * nZ - v12Z * nY; + n12Y = v12Z * nX - v12X * nZ; + n12Z = v12X * nY - v12Y * nX; + n23X = v23Y * nZ - v23Z * nY; + n23Y = v23Z * nX - v23X * nZ; + n23Z = v23X * nY - v23Y * nX; + n31X = v31Y * nZ - v31Z * nY; + n31Y = v31Z * nX - v31X * nZ; + n31Z = v31X * nY - v31Y * nX; + let mind1 = -1; + let minvX1; + let minvY1; + let minvZ1; + let mini1 = 0; + minvX1 = 0; + minvY1 = 0; + minvZ1 = 0; + if(v1X * n12X + v1Y * n12Y + v1Z * n12Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec3.x; + v2Y = vec3.y; + v2Z = vec3.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + mini1 = b; + mind1 = out.x * out.x + out.y * out.y + out.z * out.z; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + if(v2X * n23X + v2Y * n23Y + v2Z * n23Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec3.x; + v1Y = vec3.y; + v1Z = vec3.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + if(v3X * n31X + v3Y * n31Y + v3Z * n31Z < 0) { + let v1X; + let v1Y; + let v1Z; + let v2X; + let v2Y; + let v2Z; + v1X = vec2.x; + v1Y = vec2.y; + v1Z = vec2.z; + v2X = vec4.x; + v2Y = vec4.y; + v2Z = vec4.z; + let v12X; + let v12Y; + let v12Z; + v12X = v2X - v1X; + v12Y = v2Y - v1Y; + v12Z = v2Z - v1Z; + let t = v12X * v1X + v12Y * v1Y + v12Z * v1Z; + t = -t / (v12X * v12X + v12Y * v12Y + v12Z * v12Z); + let b; + if(t < 0) { + out.x = v1X; + out.y = v1Y; + out.z = v1Z; + b = 1; + } else if(t > 1) { + out.x = v2X; + out.y = v2Y; + out.z = v2Z; + b = 2; + } else { + let pX; + let pY; + let pZ; + pX = v1X + v12X * t; + pY = v1Y + v12Y * t; + pZ = v1Z + v12Z * t; + out.x = pX; + out.y = pY; + out.z = pZ; + b = 3; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind1 < 0 || d < mind1) { + mini1 = b & 1 | (b & 2) << 1; + mind1 = d; + minvX1 = out.x; + minvY1 = out.y; + minvZ1 = out.z; + } + } + let b; + if(mind1 > 0) { + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = mini1; + } else { + let l = nX * nX + nY * nY + nZ * nZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + nX *= l; + nY *= l; + nZ *= l; + let l2 = nX * nX + nY * nY + nZ * nZ; + l2 = (v1X * nX + v1Y * nY + v1Z * nZ) / l2; + minvX1 = nX * l2; + minvY1 = nY * l2; + minvZ1 = nZ * l2; + out.x = minvX1; + out.y = minvY1; + out.z = minvZ1; + b = 7; + } + let d = out.x * out.x + out.y * out.y + out.z * out.z; + if(mind < 0 || d < mind) { + mini = b << 1; + mind = d; + minvX = out.x; + minvY = out.y; + minvZ = out.z; + } + } + if(mind > 0) { + out.x = minvX; + out.y = minvY; + out.z = minvZ; + return mini; + } + out.zero(); + return 15; + } +} +oimo.common.Mat3 = class oimo_common_Mat3 { + constructor(e00,e01,e02,e10,e11,e12,e20,e21,e22) { + if(e22 == null) { + e22 = 1; + } + if(e21 == null) { + e21 = 0; + } + if(e20 == null) { + e20 = 0; + } + if(e12 == null) { + e12 = 0; + } + if(e11 == null) { + e11 = 1; + } + if(e10 == null) { + e10 = 0; + } + if(e02 == null) { + e02 = 0; + } + if(e01 == null) { + e01 = 0; + } + if(e00 == null) { + e00 = 1; + } + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + oimo.common.Mat3.numCreations++; + } + init(e00,e01,e02,e10,e11,e12,e20,e21,e22) { + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + return this; + } + identity() { + this.e00 = 1; + this.e01 = 0; + this.e02 = 0; + this.e10 = 0; + this.e11 = 1; + this.e12 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = 1; + return this; + } + add(m) { + return new oimo.common.Mat3(this.e00 + m.e00,this.e01 + m.e01,this.e02 + m.e02,this.e10 + m.e10,this.e11 + m.e11,this.e12 + m.e12,this.e20 + m.e20,this.e21 + m.e21,this.e22 + m.e22); + } + sub(m) { + return new oimo.common.Mat3(this.e00 - m.e00,this.e01 - m.e01,this.e02 - m.e02,this.e10 - m.e10,this.e11 - m.e11,this.e12 - m.e12,this.e20 - m.e20,this.e21 - m.e21,this.e22 - m.e22); + } + scale(s) { + return new oimo.common.Mat3(this.e00 * s,this.e01 * s,this.e02 * s,this.e10 * s,this.e11 * s,this.e12 * s,this.e20 * s,this.e21 * s,this.e22 * s); + } + mul(m) { + return new oimo.common.Mat3(this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20,this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21,this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22,this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20,this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21,this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22,this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20,this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21,this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22); + } + addEq(m) { + this.e00 += m.e00; + this.e01 += m.e01; + this.e02 += m.e02; + this.e10 += m.e10; + this.e11 += m.e11; + this.e12 += m.e12; + this.e20 += m.e20; + this.e21 += m.e21; + this.e22 += m.e22; + return this; + } + subEq(m) { + this.e00 -= m.e00; + this.e01 -= m.e01; + this.e02 -= m.e02; + this.e10 -= m.e10; + this.e11 -= m.e11; + this.e12 -= m.e12; + this.e20 -= m.e20; + this.e21 -= m.e21; + this.e22 -= m.e22; + return this; + } + scaleEq(s) { + this.e00 *= s; + this.e01 *= s; + this.e02 *= s; + this.e10 *= s; + this.e11 *= s; + this.e12 *= s; + this.e20 *= s; + this.e21 *= s; + this.e22 *= s; + return this; + } + mulEq(m) { + let e01 = this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21; + let e02 = this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22; + let e10 = this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20; + let e11 = this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21; + let e12 = this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22; + let e20 = this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20; + let e21 = this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21; + let e22 = this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22; + this.e00 = this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20; + this.e01 = e01; + this.e02 = e02; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + return this; + } + prependScale(sx,sy,sz) { + return new oimo.common.Mat3(this.e00 * sx,this.e01 * sx,this.e02 * sx,this.e10 * sy,this.e11 * sy,this.e12 * sy,this.e20 * sz,this.e21 * sz,this.e22 * sz); + } + appendScale(sx,sy,sz) { + return new oimo.common.Mat3(this.e00 * sx,this.e01 * sy,this.e02 * sz,this.e10 * sx,this.e11 * sy,this.e12 * sz,this.e20 * sx,this.e21 * sy,this.e22 * sz); + } + prependRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat3(r00 * this.e00 + r01 * this.e10 + r02 * this.e20,r00 * this.e01 + r01 * this.e11 + r02 * this.e21,r00 * this.e02 + r01 * this.e12 + r02 * this.e22,r10 * this.e00 + r11 * this.e10 + r12 * this.e20,r10 * this.e01 + r11 * this.e11 + r12 * this.e21,r10 * this.e02 + r11 * this.e12 + r12 * this.e22,r20 * this.e00 + r21 * this.e10 + r22 * this.e20,r20 * this.e01 + r21 * this.e11 + r22 * this.e21,r20 * this.e02 + r21 * this.e12 + r22 * this.e22); + } + appendRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat3(this.e00 * r00 + this.e01 * r10 + this.e02 * r20,this.e00 * r01 + this.e01 * r11 + this.e02 * r21,this.e00 * r02 + this.e01 * r12 + this.e02 * r22,this.e10 * r00 + this.e11 * r10 + this.e12 * r20,this.e10 * r01 + this.e11 * r11 + this.e12 * r21,this.e10 * r02 + this.e11 * r12 + this.e12 * r22,this.e20 * r00 + this.e21 * r10 + this.e22 * r20,this.e20 * r01 + this.e21 * r11 + this.e22 * r21,this.e20 * r02 + this.e21 * r12 + this.e22 * r22); + } + prependScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sx; + this.e02 *= sx; + this.e10 *= sy; + this.e11 *= sy; + this.e12 *= sy; + this.e20 *= sz; + this.e21 *= sz; + this.e22 *= sz; + return this; + } + appendScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sy; + this.e02 *= sz; + this.e10 *= sx; + this.e11 *= sy; + this.e12 *= sz; + this.e20 *= sx; + this.e21 *= sy; + this.e22 *= sz; + return this; + } + prependRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e10 = r10 * this.e00 + r11 * this.e10 + r12 * this.e20; + let e11 = r10 * this.e01 + r11 * this.e11 + r12 * this.e21; + let e12 = r10 * this.e02 + r11 * this.e12 + r12 * this.e22; + let e20 = r20 * this.e00 + r21 * this.e10 + r22 * this.e20; + let e21 = r20 * this.e01 + r21 * this.e11 + r22 * this.e21; + let e22 = r20 * this.e02 + r21 * this.e12 + r22 * this.e22; + this.e00 = r00 * this.e00 + r01 * this.e10 + r02 * this.e20; + this.e01 = r00 * this.e01 + r01 * this.e11 + r02 * this.e21; + this.e02 = r00 * this.e02 + r01 * this.e12 + r02 * this.e22; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + return this; + } + appendRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e01 = this.e00 * r01 + this.e01 * r11 + this.e02 * r21; + let e02 = this.e00 * r02 + this.e01 * r12 + this.e02 * r22; + let e11 = this.e10 * r01 + this.e11 * r11 + this.e12 * r21; + let e12 = this.e10 * r02 + this.e11 * r12 + this.e12 * r22; + let e21 = this.e20 * r01 + this.e21 * r11 + this.e22 * r21; + let e22 = this.e20 * r02 + this.e21 * r12 + this.e22 * r22; + this.e00 = this.e00 * r00 + this.e01 * r10 + this.e02 * r20; + this.e01 = e01; + this.e02 = e02; + this.e10 = this.e10 * r00 + this.e11 * r10 + this.e12 * r20; + this.e11 = e11; + this.e12 = e12; + this.e20 = this.e20 * r00 + this.e21 * r10 + this.e22 * r20; + this.e21 = e21; + this.e22 = e22; + return this; + } + transpose() { + return new oimo.common.Mat3(this.e00,this.e10,this.e20,this.e01,this.e11,this.e21,this.e02,this.e12,this.e22); + } + transposeEq() { + let e10 = this.e01; + let e20 = this.e02; + let e21 = this.e12; + + this.e01 = this.e10; + this.e02 = this.e20; + this.e10 = e10; + + this.e12 = this.e21; + this.e20 = e20; + this.e21 = e21; + + return this; + } + determinant() { + return this.e00 * (this.e11 * this.e22 - this.e12 * this.e21) - this.e01 * (this.e10 * this.e22 - this.e12 * this.e20) + this.e02 * (this.e10 * this.e21 - this.e11 * this.e20); + } + trace() { + return this.e00 + this.e11 + this.e22; + } + inverse() { + let d00 = this.e11 * this.e22 - this.e12 * this.e21; + let d01 = this.e10 * this.e22 - this.e12 * this.e20; + let d02 = this.e10 * this.e21 - this.e11 * this.e20; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02; + if(invDet != 0) { + invDet = 1 / invDet; + } + return new oimo.common.Mat3(d00 * invDet,-(this.e01 * this.e22 - this.e02 * this.e21) * invDet,(this.e01 * this.e12 - this.e02 * this.e11) * invDet,-d01 * invDet,(this.e00 * this.e22 - this.e02 * this.e20) * invDet,-(this.e00 * this.e12 - this.e02 * this.e10) * invDet,d02 * invDet,-(this.e00 * this.e21 - this.e01 * this.e20) * invDet,(this.e00 * this.e11 - this.e01 * this.e10) * invDet); + } + inverseEq() { + let d00 = this.e11 * this.e22 - this.e12 * this.e21; + let d01 = this.e10 * this.e22 - this.e12 * this.e20; + let d02 = this.e10 * this.e21 - this.e11 * this.e20; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02; + if(invDet != 0) { + invDet = 1 / invDet; + } + let t02 = (this.e01 * this.e12 - this.e02 * this.e11) * invDet; + let t11 = (this.e00 * this.e22 - this.e02 * this.e20) * invDet; + let t12 = -(this.e00 * this.e12 - this.e02 * this.e10) * invDet; + let t21 = -(this.e00 * this.e21 - this.e01 * this.e20) * invDet; + let t22 = (this.e00 * this.e11 - this.e01 * this.e10) * invDet; + this.e00 = d00 * invDet; + this.e01 = -(this.e01 * this.e22 - this.e02 * this.e21) * invDet; + this.e02 = t02; + this.e10 = -d01 * invDet; + this.e11 = t11; + this.e12 = t12; + this.e20 = d02 * invDet; + this.e21 = t21; + this.e22 = t22; + return this; + } + toArray(columnMajor) { + if(columnMajor == null) { + columnMajor = false; + } + if(columnMajor) { + return [this.e00,this.e10,this.e20,this.e01,this.e11,this.e21,this.e02,this.e12,this.e22]; + } else { + return [this.e00,this.e01,this.e02,this.e10,this.e11,this.e12,this.e20,this.e21,this.e22]; + } + } + copyFrom(m) { + this.e00 = m.e00; + this.e01 = m.e01; + this.e02 = m.e02; + this.e10 = m.e10; + this.e11 = m.e11; + this.e12 = m.e12; + this.e20 = m.e20; + this.e21 = m.e21; + this.e22 = m.e22; + return this; + } + clone() { + return new oimo.common.Mat3(this.e00,this.e01,this.e02,this.e10,this.e11,this.e12,this.e20,this.e21,this.e22); + } + fromQuat(q) { + let x = q.x; + let y = q.y; + let z = q.z; + let w = q.w; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this.e00 = 1 - yy - zz; + this.e01 = xy - wz; + this.e02 = xz + wy; + this.e10 = xy + wz; + this.e11 = 1 - xx - zz; + this.e12 = yz - wx; + this.e20 = xz - wy; + this.e21 = yz + wx; + this.e22 = 1 - xx - yy; + return this; + } + toQuat() { + let _this = new oimo.common.Quat(); + let e00 = this.e00; + let e11 = this.e11; + let e22 = this.e22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + _this.w = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e21 - this.e12) * s; + _this.y = (this.e02 - this.e20) * s; + _this.z = (this.e10 - this.e01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + _this.x = 0.5 * s; + s = 0.5 / s; + _this.y = (this.e01 + this.e10) * s; + _this.z = (this.e02 + this.e20) * s; + _this.w = (this.e21 - this.e12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + _this.z = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e02 + this.e20) * s; + _this.y = (this.e12 + this.e21) * s; + _this.w = (this.e10 - this.e01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + _this.y = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e01 + this.e10) * s; + _this.z = (this.e12 + this.e21) * s; + _this.w = (this.e02 - this.e20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + _this.z = 0.5 * s; + s = 0.5 / s; + _this.x = (this.e02 + this.e20) * s; + _this.y = (this.e12 + this.e21) * s; + _this.w = (this.e10 - this.e01) * s; + } + return _this; + } + fromEulerXyz(eulerAngles) { + let sx = Math.sin(eulerAngles.x); + let sy = Math.sin(eulerAngles.y); + let sz = Math.sin(eulerAngles.z); + let cx = Math.cos(eulerAngles.x); + let cy = Math.cos(eulerAngles.y); + let cz = Math.cos(eulerAngles.z); + this.e00 = cy * cz; + this.e01 = -cy * sz; + this.e02 = sy; + this.e10 = cx * sz + cz * sx * sy; + this.e11 = cx * cz - sx * sy * sz; + this.e12 = -cy * sx; + this.e20 = sx * sz - cx * cz * sy; + this.e21 = cz * sx + cx * sy * sz; + this.e22 = cx * cy; + return this; + } + toEulerXyz() { + let sy = this.e02; + if(sy <= -1) { + let xSubZ = Math.atan2(this.e21,this.e11); + return new oimo.common.Vec3(xSubZ * 0.5,-1.570796326794895,-xSubZ * 0.5); + } + if(sy >= 1) { + let xAddZ = Math.atan2(this.e21,this.e11); + return new oimo.common.Vec3(xAddZ * 0.5,1.570796326794895,xAddZ * 0.5); + } + return new oimo.common.Vec3(Math.atan2(-this.e12,this.e22),Math.asin(sy),Math.atan2(-this.e01,this.e00)); + } + getRow(index) { + if(index == 0) { + return new oimo.common.Vec3(this.e00,this.e01,this.e02); + } else if(index == 1) { + return new oimo.common.Vec3(this.e10,this.e11,this.e12); + } else if(index == 2) { + return new oimo.common.Vec3(this.e20,this.e21,this.e22); + } else { + return null; + } + } + getCol(index) { + if(index == 0) { + return new oimo.common.Vec3(this.e00,this.e10,this.e20); + } else if(index == 1) { + return new oimo.common.Vec3(this.e01,this.e11,this.e21); + } else if(index == 2) { + return new oimo.common.Vec3(this.e02,this.e12,this.e22); + } else { + return null; + } + } + getRowTo(index,dst) { + if(index == 0) { + dst.init(this.e00,this.e01,this.e02); + } else if(index == 1) { + dst.init(this.e10,this.e11,this.e12); + } else if(index == 2) { + dst.init(this.e20,this.e21,this.e22); + } else { + dst.zero(); + } + } + getColTo(index,dst) { + if(index == 0) { + dst.init(this.e00,this.e10,this.e20); + } else if(index == 1) { + dst.init(this.e01,this.e11,this.e21); + } else if(index == 2) { + dst.init(this.e02,this.e12,this.e22); + } else { + dst.zero(); + } + } + fromRows(row0,row1,row2) { + this.e00 = row0.x; + this.e01 = row0.y; + this.e02 = row0.z; + this.e10 = row1.x; + this.e11 = row1.y; + this.e12 = row1.z; + this.e20 = row2.x; + this.e21 = row2.y; + this.e22 = row2.z; + return this; + } + fromCols(col0,col1,col2) { + this.e00 = col0.x; + this.e01 = col1.x; + this.e02 = col2.x; + this.e10 = col0.y; + this.e11 = col1.y; + this.e12 = col2.y; + this.e20 = col0.z; + this.e21 = col1.z; + this.e22 = col2.z; + return this; + } + toString() { + return "Mat3[" + (this.e00 > 0 ? (this.e00 * 10000000 + 0.5 | 0) / 10000000 : (this.e00 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e01 > 0 ? (this.e01 * 10000000 + 0.5 | 0) / 10000000 : (this.e01 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e02 > 0 ? (this.e02 * 10000000 + 0.5 | 0) / 10000000 : (this.e02 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e10 > 0 ? (this.e10 * 10000000 + 0.5 | 0) / 10000000 : (this.e10 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e11 > 0 ? (this.e11 * 10000000 + 0.5 | 0) / 10000000 : (this.e11 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e12 > 0 ? (this.e12 * 10000000 + 0.5 | 0) / 10000000 : (this.e12 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e20 > 0 ? (this.e20 * 10000000 + 0.5 | 0) / 10000000 : (this.e20 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e21 > 0 ? (this.e21 * 10000000 + 0.5 | 0) / 10000000 : (this.e21 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e22 > 0 ? (this.e22 * 10000000 + 0.5 | 0) / 10000000 : (this.e22 * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +oimo.common.Mat4 = class oimo_common_Mat4 { + constructor(e00,e01,e02,e03,e10,e11,e12,e13,e20,e21,e22,e23,e30,e31,e32,e33) { + if(e33 == null) { + e33 = 1; + } + if(e32 == null) { + e32 = 0; + } + if(e31 == null) { + e31 = 0; + } + if(e30 == null) { + e30 = 0; + } + if(e23 == null) { + e23 = 0; + } + if(e22 == null) { + e22 = 1; + } + if(e21 == null) { + e21 = 0; + } + if(e20 == null) { + e20 = 0; + } + if(e13 == null) { + e13 = 0; + } + if(e12 == null) { + e12 = 0; + } + if(e11 == null) { + e11 = 1; + } + if(e10 == null) { + e10 = 0; + } + if(e03 == null) { + e03 = 0; + } + if(e02 == null) { + e02 = 0; + } + if(e01 == null) { + e01 = 0; + } + if(e00 == null) { + e00 = 1; + } + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e03 = e03; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + oimo.common.Mat4.numCreations++; + } + init(e00,e01,e02,e03,e10,e11,e12,e13,e20,e21,e22,e23,e30,e31,e32,e33) { + this.e00 = e00; + this.e01 = e01; + this.e02 = e02; + this.e03 = e03; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + return this; + } + identity() { + this.e00 = 1; + this.e01 = 0; + this.e02 = 0; + this.e03 = 0; + this.e10 = 0; + this.e11 = 1; + this.e12 = 0; + this.e13 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = 1; + this.e23 = 0; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + add(m) { + return new oimo.common.Mat4(this.e00 + m.e00,this.e01 + m.e01,this.e02 + m.e02,this.e03 + m.e03,this.e10 + m.e10,this.e11 + m.e11,this.e12 + m.e12,this.e13 + m.e13,this.e20 + m.e20,this.e21 + m.e21,this.e22 + m.e22,this.e23 + m.e23,this.e30 + m.e30,this.e31 + m.e31,this.e32 + m.e32,this.e33 + m.e33); + } + sub(m) { + return new oimo.common.Mat4(this.e00 - m.e00,this.e01 - m.e01,this.e02 - m.e02,this.e03 - m.e03,this.e10 - m.e10,this.e11 - m.e11,this.e12 - m.e12,this.e13 - m.e13,this.e20 - m.e20,this.e21 - m.e21,this.e22 - m.e22,this.e23 - m.e23,this.e30 - m.e30,this.e31 - m.e31,this.e32 - m.e32,this.e33 - m.e33); + } + scale(s) { + return new oimo.common.Mat4(this.e00 * s,this.e01 * s,this.e02 * s,this.e03 * s,this.e10 * s,this.e11 * s,this.e12 * s,this.e13 * s,this.e20 * s,this.e21 * s,this.e22 * s,this.e23 * s,this.e30 * s,this.e31 * s,this.e32 * s,this.e33 * s); + } + mul(m) { + return new oimo.common.Mat4(this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20 + this.e03 * m.e30,this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21 + this.e03 * m.e31,this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22 + this.e03 * m.e32,this.e00 * m.e03 + this.e01 * m.e13 + this.e02 * m.e23 + this.e03 * m.e33,this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20 + this.e13 * m.e30,this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21 + this.e13 * m.e31,this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22 + this.e13 * m.e32,this.e10 * m.e03 + this.e11 * m.e13 + this.e12 * m.e23 + this.e13 * m.e33,this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20 + this.e23 * m.e30,this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21 + this.e23 * m.e31,this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22 + this.e23 * m.e32,this.e20 * m.e03 + this.e21 * m.e13 + this.e22 * m.e23 + this.e23 * m.e33,this.e30 * m.e00 + this.e31 * m.e10 + this.e32 * m.e20 + this.e33 * m.e30,this.e30 * m.e01 + this.e31 * m.e11 + this.e32 * m.e21 + this.e33 * m.e31,this.e30 * m.e02 + this.e31 * m.e12 + this.e32 * m.e22 + this.e33 * m.e32,this.e30 * m.e03 + this.e31 * m.e13 + this.e32 * m.e23 + this.e33 * m.e33); + } + addEq(m) { + this.e00 += m.e00; + this.e01 += m.e01; + this.e02 += m.e02; + this.e03 += m.e03; + this.e10 += m.e10; + this.e11 += m.e11; + this.e12 += m.e12; + this.e13 += m.e13; + this.e20 += m.e20; + this.e21 += m.e21; + this.e22 += m.e22; + this.e23 += m.e23; + this.e30 += m.e30; + this.e31 += m.e31; + this.e32 += m.e32; + this.e33 += m.e33; + return this; + } + subEq(m) { + this.e00 -= m.e00; + this.e01 -= m.e01; + this.e02 -= m.e02; + this.e03 -= m.e03; + this.e10 -= m.e10; + this.e11 -= m.e11; + this.e12 -= m.e12; + this.e13 -= m.e13; + this.e20 -= m.e20; + this.e21 -= m.e21; + this.e22 -= m.e22; + this.e23 -= m.e23; + this.e30 -= m.e30; + this.e31 -= m.e31; + this.e32 -= m.e32; + this.e33 -= m.e33; + return this; + } + scaleEq(s) { + this.e00 *= s; + this.e01 *= s; + this.e02 *= s; + this.e03 *= s; + this.e10 *= s; + this.e11 *= s; + this.e12 *= s; + this.e13 *= s; + this.e20 *= s; + this.e21 *= s; + this.e22 *= s; + this.e23 *= s; + this.e30 *= s; + this.e31 *= s; + this.e32 *= s; + this.e33 *= s; + return this; + } + mulEq(m) { + let e01 = this.e00 * m.e01 + this.e01 * m.e11 + this.e02 * m.e21 + this.e03 * m.e31; + let e02 = this.e00 * m.e02 + this.e01 * m.e12 + this.e02 * m.e22 + this.e03 * m.e32; + let e03 = this.e00 * m.e03 + this.e01 * m.e13 + this.e02 * m.e23 + this.e03 * m.e33; + let e10 = this.e10 * m.e00 + this.e11 * m.e10 + this.e12 * m.e20 + this.e13 * m.e30; + let e11 = this.e10 * m.e01 + this.e11 * m.e11 + this.e12 * m.e21 + this.e13 * m.e31; + let e12 = this.e10 * m.e02 + this.e11 * m.e12 + this.e12 * m.e22 + this.e13 * m.e32; + let e13 = this.e10 * m.e03 + this.e11 * m.e13 + this.e12 * m.e23 + this.e13 * m.e33; + let e20 = this.e20 * m.e00 + this.e21 * m.e10 + this.e22 * m.e20 + this.e23 * m.e30; + let e21 = this.e20 * m.e01 + this.e21 * m.e11 + this.e22 * m.e21 + this.e23 * m.e31; + let e22 = this.e20 * m.e02 + this.e21 * m.e12 + this.e22 * m.e22 + this.e23 * m.e32; + let e23 = this.e20 * m.e03 + this.e21 * m.e13 + this.e22 * m.e23 + this.e23 * m.e33; + let e30 = this.e30 * m.e00 + this.e31 * m.e10 + this.e32 * m.e20 + this.e33 * m.e30; + let e31 = this.e30 * m.e01 + this.e31 * m.e11 + this.e32 * m.e21 + this.e33 * m.e31; + let e32 = this.e30 * m.e02 + this.e31 * m.e12 + this.e32 * m.e22 + this.e33 * m.e32; + let e33 = this.e30 * m.e03 + this.e31 * m.e13 + this.e32 * m.e23 + this.e33 * m.e33; + this.e00 = this.e00 * m.e00 + this.e01 * m.e10 + this.e02 * m.e20 + this.e03 * m.e30; + this.e01 = e01; + this.e02 = e02; + this.e03 = e03; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + this.e33 = e33; + return this; + } + prependScale(sx,sy,sz) { + return new oimo.common.Mat4(this.e00 * sx,this.e01 * sx,this.e02 * sx,this.e03 * sx,this.e10 * sy,this.e11 * sy,this.e12 * sy,this.e13 * sy,this.e20 * sz,this.e21 * sz,this.e22 * sz,this.e23 * sz,this.e30,this.e31,this.e32,this.e33); + } + appendScale(sx,sy,sz) { + return new oimo.common.Mat4(this.e00 * sx,this.e01 * sy,this.e02 * sz,this.e03,this.e10 * sx,this.e11 * sy,this.e12 * sz,this.e13,this.e20 * sx,this.e21 * sy,this.e22 * sz,this.e23,this.e30 * sx,this.e31 * sy,this.e32 * sz,this.e33); + } + prependRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat4(r00 * this.e00 + r01 * this.e10 + r02 * this.e20,r00 * this.e01 + r01 * this.e11 + r02 * this.e21,r00 * this.e02 + r01 * this.e12 + r02 * this.e22,r00 * this.e03 + r01 * this.e13 + r02 * this.e23,r10 * this.e00 + r11 * this.e10 + r12 * this.e20,r10 * this.e01 + r11 * this.e11 + r12 * this.e21,r10 * this.e02 + r11 * this.e12 + r12 * this.e22,r10 * this.e03 + r11 * this.e13 + r12 * this.e23,r20 * this.e00 + r21 * this.e10 + r22 * this.e20,r20 * this.e01 + r21 * this.e11 + r22 * this.e21,r20 * this.e02 + r21 * this.e12 + r22 * this.e22,r20 * this.e03 + r21 * this.e13 + r22 * this.e23,this.e30,this.e31,this.e32,this.e33); + } + appendRotation(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + return new oimo.common.Mat4(this.e00 * r00 + this.e01 * r10 + this.e02 * r20,this.e00 * r01 + this.e01 * r11 + this.e02 * r21,this.e00 * r02 + this.e01 * r12 + this.e02 * r22,this.e03,this.e10 * r00 + this.e11 * r10 + this.e12 * r20,this.e10 * r01 + this.e11 * r11 + this.e12 * r21,this.e10 * r02 + this.e11 * r12 + this.e12 * r22,this.e13,this.e20 * r00 + this.e21 * r10 + this.e22 * r20,this.e20 * r01 + this.e21 * r11 + this.e22 * r21,this.e20 * r02 + this.e21 * r12 + this.e22 * r22,this.e23,this.e30 * r00 + this.e31 * r10 + this.e32 * r20,this.e30 * r01 + this.e31 * r11 + this.e32 * r21,this.e30 * r02 + this.e31 * r12 + this.e32 * r22,this.e33); + } + prependTranslation(tx,ty,tz) { + return new oimo.common.Mat4(this.e00 + tx * this.e30,this.e01 + tx * this.e31,this.e02 + tx * this.e32,this.e03 + tx * this.e33,this.e10 + ty * this.e30,this.e11 + ty * this.e31,this.e12 + ty * this.e32,this.e13 + ty * this.e33,this.e20 + tz * this.e30,this.e21 + tz * this.e31,this.e22 + tz * this.e32,this.e23 + tz * this.e33,this.e30,this.e31,this.e32,this.e33); + } + appendTranslation(tx,ty,tz) { + return new oimo.common.Mat4(this.e00,this.e01,this.e02,this.e00 * tx + this.e01 * ty + this.e02 * tz + this.e03,this.e10,this.e11,this.e12,this.e10 * tx + this.e11 * ty + this.e12 * tz + this.e13,this.e20,this.e21,this.e22,this.e20 * tx + this.e21 * ty + this.e22 * tz + this.e23,this.e30,this.e31,this.e32,this.e30 * tx + this.e31 * ty + this.e32 * tz + this.e33); + } + prependScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sx; + this.e02 *= sx; + this.e03 *= sx; + this.e10 *= sy; + this.e11 *= sy; + this.e12 *= sy; + this.e13 *= sy; + this.e20 *= sz; + this.e21 *= sz; + this.e22 *= sz; + this.e23 *= sz; + + + + + return this; + } + appendScaleEq(sx,sy,sz) { + this.e00 *= sx; + this.e01 *= sy; + this.e02 *= sz; + + this.e10 *= sx; + this.e11 *= sy; + this.e12 *= sz; + + this.e20 *= sx; + this.e21 *= sy; + this.e22 *= sz; + + this.e30 *= sx; + this.e31 *= sy; + this.e32 *= sz; + + return this; + } + prependRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e10 = r10 * this.e00 + r11 * this.e10 + r12 * this.e20; + let e11 = r10 * this.e01 + r11 * this.e11 + r12 * this.e21; + let e12 = r10 * this.e02 + r11 * this.e12 + r12 * this.e22; + let e13 = r10 * this.e03 + r11 * this.e13 + r12 * this.e23; + let e20 = r20 * this.e00 + r21 * this.e10 + r22 * this.e20; + let e21 = r20 * this.e01 + r21 * this.e11 + r22 * this.e21; + let e22 = r20 * this.e02 + r21 * this.e12 + r22 * this.e22; + let e23 = r20 * this.e03 + r21 * this.e13 + r22 * this.e23; + this.e00 = r00 * this.e00 + r01 * this.e10 + r02 * this.e20; + this.e01 = r00 * this.e01 + r01 * this.e11 + r02 * this.e21; + this.e02 = r00 * this.e02 + r01 * this.e12 + r02 * this.e22; + this.e03 = r00 * this.e03 + r01 * this.e13 + r02 * this.e23; + this.e10 = e10; + this.e11 = e11; + this.e12 = e12; + this.e13 = e13; + this.e20 = e20; + this.e21 = e21; + this.e22 = e22; + this.e23 = e23; + + + + + return this; + } + appendRotationEq(rad,axisX,axisY,axisZ) { + let s = Math.sin(rad); + let c = Math.cos(rad); + let c1 = 1 - c; + let r00 = axisX * axisX * c1 + c; + let r01 = axisX * axisY * c1 - axisZ * s; + let r02 = axisX * axisZ * c1 + axisY * s; + let r10 = axisY * axisX * c1 + axisZ * s; + let r11 = axisY * axisY * c1 + c; + let r12 = axisY * axisZ * c1 - axisX * s; + let r20 = axisZ * axisX * c1 - axisY * s; + let r21 = axisZ * axisY * c1 + axisX * s; + let r22 = axisZ * axisZ * c1 + c; + let e01 = this.e00 * r01 + this.e01 * r11 + this.e02 * r21; + let e02 = this.e00 * r02 + this.e01 * r12 + this.e02 * r22; + let e11 = this.e10 * r01 + this.e11 * r11 + this.e12 * r21; + let e12 = this.e10 * r02 + this.e11 * r12 + this.e12 * r22; + let e21 = this.e20 * r01 + this.e21 * r11 + this.e22 * r21; + let e22 = this.e20 * r02 + this.e21 * r12 + this.e22 * r22; + let e31 = this.e30 * r01 + this.e31 * r11 + this.e32 * r21; + let e32 = this.e30 * r02 + this.e31 * r12 + this.e32 * r22; + this.e00 = this.e00 * r00 + this.e01 * r10 + this.e02 * r20; + this.e01 = e01; + this.e02 = e02; + + this.e10 = this.e10 * r00 + this.e11 * r10 + this.e12 * r20; + this.e11 = e11; + this.e12 = e12; + + this.e20 = this.e20 * r00 + this.e21 * r10 + this.e22 * r20; + this.e21 = e21; + this.e22 = e22; + + this.e30 = this.e30 * r00 + this.e31 * r10 + this.e32 * r20; + this.e31 = e31; + this.e32 = e32; + + return this; + } + prependTranslationEq(tx,ty,tz) { + this.e00 += tx * this.e30; + this.e01 += tx * this.e31; + this.e02 += tx * this.e32; + this.e03 += tx * this.e33; + this.e10 += ty * this.e30; + this.e11 += ty * this.e31; + this.e12 += ty * this.e32; + this.e13 += ty * this.e33; + this.e20 += tz * this.e30; + this.e21 += tz * this.e31; + this.e22 += tz * this.e32; + this.e23 += tz * this.e33; + + + + + return this; + } + appendTranslationEq(tx,ty,tz) { + let e03 = this.e00 * tx + this.e01 * ty + this.e02 * tz + this.e03; + let e13 = this.e10 * tx + this.e11 * ty + this.e12 * tz + this.e13; + let e23 = this.e20 * tx + this.e21 * ty + this.e22 * tz + this.e23; + let e33 = this.e30 * tx + this.e31 * ty + this.e32 * tz + this.e33; + + + + + this.e03 = e03; + + + + + this.e13 = e13; + + + + + this.e23 = e23; + + + + + this.e33 = e33; + return this; + } + transpose() { + return new oimo.common.Mat4(this.e00,this.e10,this.e20,this.e30,this.e01,this.e11,this.e21,this.e31,this.e02,this.e12,this.e22,this.e32,this.e03,this.e13,this.e23,this.e33); + } + transposeEq() { + let e10 = this.e01; + let e20 = this.e02; + let e21 = this.e12; + let e30 = this.e03; + let e31 = this.e13; + let e32 = this.e23; + + this.e01 = this.e10; + this.e02 = this.e20; + this.e03 = this.e30; + this.e10 = e10; + + this.e12 = this.e21; + this.e13 = this.e31; + this.e20 = e20; + this.e21 = e21; + + this.e23 = this.e32; + this.e30 = e30; + this.e31 = e31; + this.e32 = e32; + + return this; + } + determinant() { + let d23_01 = this.e20 * this.e31 - this.e21 * this.e30; + let d23_02 = this.e20 * this.e32 - this.e22 * this.e30; + let d23_03 = this.e20 * this.e33 - this.e23 * this.e30; + let d23_12 = this.e21 * this.e32 - this.e22 * this.e31; + let d23_13 = this.e21 * this.e33 - this.e23 * this.e31; + let d23_23 = this.e22 * this.e33 - this.e23 * this.e32; + return this.e00 * (this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12) - this.e01 * (this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02) + this.e02 * (this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01) - this.e03 * (this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01); + } + trace() { + return this.e00 + this.e11 + this.e22 + this.e33; + } + inverse() { + let d01_01 = this.e00 * this.e11 - this.e01 * this.e10; + let d01_02 = this.e00 * this.e12 - this.e02 * this.e10; + let d01_03 = this.e00 * this.e13 - this.e03 * this.e10; + let d01_12 = this.e01 * this.e12 - this.e02 * this.e11; + let d01_13 = this.e01 * this.e13 - this.e03 * this.e11; + let d01_23 = this.e02 * this.e13 - this.e03 * this.e12; + let d23_01 = this.e20 * this.e31 - this.e21 * this.e30; + let d23_02 = this.e20 * this.e32 - this.e22 * this.e30; + let d23_03 = this.e20 * this.e33 - this.e23 * this.e30; + let d23_12 = this.e21 * this.e32 - this.e22 * this.e31; + let d23_13 = this.e21 * this.e33 - this.e23 * this.e31; + let d23_23 = this.e22 * this.e33 - this.e23 * this.e32; + let d00 = this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12; + let d01 = this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02; + let d02 = this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01; + let d03 = this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02 - this.e03 * d03; + if(invDet != 0) { + invDet = 1 / invDet; + } + return new oimo.common.Mat4(d00 * invDet,-(this.e01 * d23_23 - this.e02 * d23_13 + this.e03 * d23_12) * invDet,(this.e31 * d01_23 - this.e32 * d01_13 + this.e33 * d01_12) * invDet,-(this.e21 * d01_23 - this.e22 * d01_13 + this.e23 * d01_12) * invDet,-d01 * invDet,(this.e00 * d23_23 - this.e02 * d23_03 + this.e03 * d23_02) * invDet,-(this.e30 * d01_23 - this.e32 * d01_03 + this.e33 * d01_02) * invDet,(this.e20 * d01_23 - this.e22 * d01_03 + this.e23 * d01_02) * invDet,d02 * invDet,-(this.e00 * d23_13 - this.e01 * d23_03 + this.e03 * d23_01) * invDet,(this.e30 * d01_13 - this.e31 * d01_03 + this.e33 * d01_01) * invDet,-(this.e20 * d01_13 - this.e21 * d01_03 + this.e23 * d01_01) * invDet,-d03 * invDet,(this.e00 * d23_12 - this.e01 * d23_02 + this.e02 * d23_01) * invDet,-(this.e30 * d01_12 - this.e31 * d01_02 + this.e32 * d01_01) * invDet,(this.e20 * d01_12 - this.e21 * d01_02 + this.e22 * d01_01) * invDet); + } + inverseEq() { + let d01_01 = this.e00 * this.e11 - this.e01 * this.e10; + let d01_02 = this.e00 * this.e12 - this.e02 * this.e10; + let d01_03 = this.e00 * this.e13 - this.e03 * this.e10; + let d01_12 = this.e01 * this.e12 - this.e02 * this.e11; + let d01_13 = this.e01 * this.e13 - this.e03 * this.e11; + let d01_23 = this.e02 * this.e13 - this.e03 * this.e12; + let d23_01 = this.e20 * this.e31 - this.e21 * this.e30; + let d23_02 = this.e20 * this.e32 - this.e22 * this.e30; + let d23_03 = this.e20 * this.e33 - this.e23 * this.e30; + let d23_12 = this.e21 * this.e32 - this.e22 * this.e31; + let d23_13 = this.e21 * this.e33 - this.e23 * this.e31; + let d23_23 = this.e22 * this.e33 - this.e23 * this.e32; + let d00 = this.e11 * d23_23 - this.e12 * d23_13 + this.e13 * d23_12; + let d01 = this.e10 * d23_23 - this.e12 * d23_03 + this.e13 * d23_02; + let d02 = this.e10 * d23_13 - this.e11 * d23_03 + this.e13 * d23_01; + let d03 = this.e10 * d23_12 - this.e11 * d23_02 + this.e12 * d23_01; + let invDet = this.e00 * d00 - this.e01 * d01 + this.e02 * d02 - this.e03 * d03; + if(invDet != 0) { + invDet = 1 / invDet; + } + let t11 = (this.e00 * d23_23 - this.e02 * d23_03 + this.e03 * d23_02) * invDet; + let t21 = -(this.e00 * d23_13 - this.e01 * d23_03 + this.e03 * d23_01) * invDet; + let t23 = -(this.e20 * d01_13 - this.e21 * d01_03 + this.e23 * d01_01) * invDet; + let t31 = (this.e00 * d23_12 - this.e01 * d23_02 + this.e02 * d23_01) * invDet; + let t32 = -(this.e30 * d01_12 - this.e31 * d01_02 + this.e32 * d01_01) * invDet; + let t33 = (this.e20 * d01_12 - this.e21 * d01_02 + this.e22 * d01_01) * invDet; + this.e00 = d00 * invDet; + this.e01 = -(this.e01 * d23_23 - this.e02 * d23_13 + this.e03 * d23_12) * invDet; + this.e02 = (this.e31 * d01_23 - this.e32 * d01_13 + this.e33 * d01_12) * invDet; + this.e03 = -(this.e21 * d01_23 - this.e22 * d01_13 + this.e23 * d01_12) * invDet; + this.e10 = -d01 * invDet; + this.e11 = t11; + this.e12 = -(this.e30 * d01_23 - this.e32 * d01_03 + this.e33 * d01_02) * invDet; + this.e13 = (this.e20 * d01_23 - this.e22 * d01_03 + this.e23 * d01_02) * invDet; + this.e20 = d02 * invDet; + this.e21 = t21; + this.e22 = (this.e30 * d01_13 - this.e31 * d01_03 + this.e33 * d01_01) * invDet; + this.e23 = t23; + this.e30 = -d03 * invDet; + this.e31 = t31; + this.e32 = t32; + this.e33 = t33; + return this; + } + lookAt(eyeX,eyeY,eyeZ,atX,atY,atZ,upX,upY,upZ) { + let zx = eyeX - atX; + let zy = eyeY - atY; + let zz = eyeZ - atZ; + let tmp = 1 / Math.sqrt(zx * zx + zy * zy + zz * zz); + zx *= tmp; + zy *= tmp; + zz *= tmp; + let xx = upY * zz - upZ * zy; + let xy = upZ * zx - upX * zz; + let xz = upX * zy - upY * zx; + tmp = 1 / Math.sqrt(xx * xx + xy * xy + xz * xz); + xx *= tmp; + xy *= tmp; + xz *= tmp; + let yx = zy * xz - zz * xy; + let yy = zz * xx - zx * xz; + let yz = zx * xy - zy * xx; + this.e00 = xx; + this.e01 = xy; + this.e02 = xz; + this.e03 = -(xx * eyeX + xy * eyeY + xz * eyeZ); + this.e10 = yx; + this.e11 = yy; + this.e12 = yz; + this.e13 = -(yx * eyeX + yy * eyeY + yz * eyeZ); + this.e20 = zx; + this.e21 = zy; + this.e22 = zz; + this.e23 = -(zx * eyeX + zy * eyeY + zz * eyeZ); + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + perspective(fovY,aspect,near,far) { + let h = 1 / Math.tan(fovY * 0.5); + let fnf = far / (near - far); + this.e00 = h / aspect; + this.e01 = 0; + this.e02 = 0; + this.e03 = 0; + this.e10 = 0; + this.e11 = h; + this.e12 = 0; + this.e13 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = fnf; + this.e23 = near * fnf; + this.e30 = 0; + this.e31 = 0; + this.e32 = -1; + this.e33 = 0; + return this; + } + ortho(width,height,near,far) { + let nf = 1 / (near - far); + this.e00 = 2 / width; + this.e01 = 0; + this.e02 = 0; + this.e03 = 0; + this.e10 = 0; + this.e11 = 2 / height; + this.e12 = 0; + this.e13 = 0; + this.e20 = 0; + this.e21 = 0; + this.e22 = nf; + this.e23 = near * nf; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + toArray(columnMajor) { + if(columnMajor == null) { + columnMajor = false; + } + if(columnMajor) { + return [this.e00,this.e10,this.e20,this.e30,this.e01,this.e11,this.e21,this.e31,this.e02,this.e12,this.e22,this.e32,this.e03,this.e13,this.e23,this.e33]; + } else { + return [this.e00,this.e01,this.e02,this.e03,this.e10,this.e11,this.e12,this.e13,this.e20,this.e21,this.e22,this.e23,this.e30,this.e31,this.e32,this.e33]; + } + } + copyFrom(m) { + this.e00 = m.e00; + this.e01 = m.e01; + this.e02 = m.e02; + this.e03 = m.e03; + this.e10 = m.e10; + this.e11 = m.e11; + this.e12 = m.e12; + this.e13 = m.e13; + this.e20 = m.e20; + this.e21 = m.e21; + this.e22 = m.e22; + this.e23 = m.e23; + this.e30 = m.e30; + this.e31 = m.e31; + this.e32 = m.e32; + this.e33 = m.e33; + return this; + } + fromMat3(m) { + this.e00 = m.e00; + this.e01 = m.e01; + this.e02 = m.e02; + this.e03 = 0; + this.e10 = m.e10; + this.e11 = m.e11; + this.e12 = m.e12; + this.e13 = 0; + this.e20 = m.e20; + this.e21 = m.e21; + this.e22 = m.e22; + this.e23 = 0; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + fromTransform(transform) { + this.e00 = transform._rotation00; + this.e01 = transform._rotation01; + this.e02 = transform._rotation02; + this.e10 = transform._rotation10; + this.e11 = transform._rotation11; + this.e12 = transform._rotation12; + this.e20 = transform._rotation20; + this.e21 = transform._rotation21; + this.e22 = transform._rotation22; + this.e03 = transform._positionX; + this.e13 = transform._positionY; + this.e23 = transform._positionZ; + this.e30 = 0; + this.e31 = 0; + this.e32 = 0; + this.e33 = 1; + return this; + } + clone() { + return new oimo.common.Mat4(this.e00,this.e01,this.e02,this.e03,this.e10,this.e11,this.e12,this.e13,this.e20,this.e21,this.e22,this.e23,this.e30,this.e31,this.e32,this.e33); + } + toString() { + return "Mat4[" + (this.e00 > 0 ? (this.e00 * 10000000 + 0.5 | 0) / 10000000 : (this.e00 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e01 > 0 ? (this.e01 * 10000000 + 0.5 | 0) / 10000000 : (this.e01 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e02 > 0 ? (this.e02 * 10000000 + 0.5 | 0) / 10000000 : (this.e02 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e03 > 0 ? (this.e03 * 10000000 + 0.5 | 0) / 10000000 : (this.e03 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e10 > 0 ? (this.e10 * 10000000 + 0.5 | 0) / 10000000 : (this.e10 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e11 > 0 ? (this.e11 * 10000000 + 0.5 | 0) / 10000000 : (this.e11 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e12 > 0 ? (this.e12 * 10000000 + 0.5 | 0) / 10000000 : (this.e12 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e13 > 0 ? (this.e13 * 10000000 + 0.5 | 0) / 10000000 : (this.e13 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e20 > 0 ? (this.e20 * 10000000 + 0.5 | 0) / 10000000 : (this.e20 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e21 > 0 ? (this.e21 * 10000000 + 0.5 | 0) / 10000000 : (this.e21 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e22 > 0 ? (this.e22 * 10000000 + 0.5 | 0) / 10000000 : (this.e22 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e23 > 0 ? (this.e23 * 10000000 + 0.5 | 0) / 10000000 : (this.e23 * 10000000 - 0.5 | 0) / 10000000) + ",\n" + " " + (this.e30 > 0 ? (this.e30 * 10000000 + 0.5 | 0) / 10000000 : (this.e30 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e31 > 0 ? (this.e31 * 10000000 + 0.5 | 0) / 10000000 : (this.e31 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e32 > 0 ? (this.e32 * 10000000 + 0.5 | 0) / 10000000 : (this.e32 * 10000000 - 0.5 | 0) / 10000000) + ", " + (this.e33 > 0 ? (this.e33 * 10000000 + 0.5 | 0) / 10000000 : (this.e33 * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +oimo.common.MathUtil = class oimo_common_MathUtil { + static abs(x) { + if(x > 0) { + return x; + } else { + return -x; + } + } + static sin(x) { + return Math.sin(x); + } + static cos(x) { + return Math.cos(x); + } + static tan(x) { + return Math.tan(x); + } + static asin(x) { + return Math.asin(x); + } + static acos(x) { + return Math.acos(x); + } + static atan(x) { + return Math.atan(x); + } + static safeAsin(x) { + if(x <= -1) { + return -1.570796326794895; + } + if(x >= 1) { + return 1.570796326794895; + } + return Math.asin(x); + } + static safeAcos(x) { + if(x <= -1) { + return 3.14159265358979; + } + if(x >= 1) { + return 0; + } + return Math.acos(x); + } + static atan2(y,x) { + return Math.atan2(y,x); + } + static sqrt(x) { + return Math.sqrt(x); + } + static clamp(x,min,max) { + if(x < min) { + return min; + } else if(x > max) { + return max; + } else { + return x; + } + } + static rand() { + return Math.random(); + } + static randIn(min,max) { + return min + Math.random() * (max - min); + } + static randVec3In(min,max) { + return new oimo.common.Vec3(min + Math.random() * (max - min),min + Math.random() * (max - min),min + Math.random() * (max - min)); + } + static randVec3() { + return new oimo.common.Vec3(-1 + Math.random() * 2,-1 + Math.random() * 2,-1 + Math.random() * 2); + } +} +oimo.common.Pool = class oimo_common_Pool { + constructor() { + this.stackVec3 = new Array(256); + this.sizeVec3 = 0; + this.stackMat3 = new Array(256); + this.sizeMat3 = 0; + this.stackMat4 = new Array(256); + this.sizeMat4 = 0; + this.stackQuat = new Array(256); + this.sizeQuat = 0; + } + vec3() { + if(this.sizeVec3 == 0) { + return new oimo.common.Vec3(); + } else { + return this.stackVec3[--this.sizeVec3]; + } + } + mat3() { + if(this.sizeMat3 == 0) { + return new oimo.common.Mat3(); + } else { + return this.stackMat3[--this.sizeMat3]; + } + } + mat4() { + if(this.sizeMat4 == 0) { + return new oimo.common.Mat4(); + } else { + return this.stackMat4[--this.sizeMat4]; + } + } + quat() { + if(this.sizeQuat == 0) { + return new oimo.common.Quat(); + } else { + return this.stackQuat[--this.sizeQuat]; + } + } + dispose(vec3,mat3,mat4,quat) { + if(vec3 != null) { + vec3.zero(); + if(this.sizeVec3 == this.stackVec3.length) { + let newArray = new Array(this.sizeVec3 << 1); + let _g = 0; + let _g1 = this.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackVec3[i]; + this.stackVec3[i] = null; + } + this.stackVec3 = newArray; + } + this.stackVec3[this.sizeVec3++] = vec3; + } + if(mat3 != null) { + mat3.e00 = 1; + mat3.e01 = 0; + mat3.e02 = 0; + mat3.e10 = 0; + mat3.e11 = 1; + mat3.e12 = 0; + mat3.e20 = 0; + mat3.e21 = 0; + mat3.e22 = 1; + if(this.sizeMat3 == this.stackMat3.length) { + let newArray = new Array(this.sizeMat3 << 1); + let _g = 0; + let _g1 = this.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat3[i]; + this.stackMat3[i] = null; + } + this.stackMat3 = newArray; + } + this.stackMat3[this.sizeMat3++] = mat3; + } + if(mat4 != null) { + mat4.e00 = 1; + mat4.e01 = 0; + mat4.e02 = 0; + mat4.e03 = 0; + mat4.e10 = 0; + mat4.e11 = 1; + mat4.e12 = 0; + mat4.e13 = 0; + mat4.e20 = 0; + mat4.e21 = 0; + mat4.e22 = 1; + mat4.e23 = 0; + mat4.e30 = 0; + mat4.e31 = 0; + mat4.e32 = 0; + mat4.e33 = 1; + if(this.sizeMat4 == this.stackMat4.length) { + let newArray = new Array(this.sizeMat4 << 1); + let _g = 0; + let _g1 = this.sizeMat4; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat4[i]; + this.stackMat4[i] = null; + } + this.stackMat4 = newArray; + } + this.stackMat4[this.sizeMat4++] = mat4; + } + if(quat != null) { + quat.x = 0; + quat.y = 0; + quat.z = 0; + quat.w = 1; + if(this.sizeQuat == this.stackQuat.length) { + let newArray = new Array(this.sizeQuat << 1); + let _g = 0; + let _g1 = this.sizeQuat; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackQuat[i]; + this.stackQuat[i] = null; + } + this.stackQuat = newArray; + } + this.stackQuat[this.sizeQuat++] = quat; + } + } + disposeVec3(v) { + v.zero(); + if(this.sizeVec3 == this.stackVec3.length) { + let newArray = new Array(this.sizeVec3 << 1); + let _g = 0; + let _g1 = this.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackVec3[i]; + this.stackVec3[i] = null; + } + this.stackVec3 = newArray; + } + this.stackVec3[this.sizeVec3++] = v; + } + disposeMat3(m) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(this.sizeMat3 == this.stackMat3.length) { + let newArray = new Array(this.sizeMat3 << 1); + let _g = 0; + let _g1 = this.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat3[i]; + this.stackMat3[i] = null; + } + this.stackMat3 = newArray; + } + this.stackMat3[this.sizeMat3++] = m; + } + disposeMat4(m) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e03 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e13 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + m.e23 = 0; + m.e30 = 0; + m.e31 = 0; + m.e32 = 0; + m.e33 = 1; + if(this.sizeMat4 == this.stackMat4.length) { + let newArray = new Array(this.sizeMat4 << 1); + let _g = 0; + let _g1 = this.sizeMat4; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackMat4[i]; + this.stackMat4[i] = null; + } + this.stackMat4 = newArray; + } + this.stackMat4[this.sizeMat4++] = m; + } + disposeQuat(q) { + q.x = 0; + q.y = 0; + q.z = 0; + q.w = 1; + if(this.sizeQuat == this.stackQuat.length) { + let newArray = new Array(this.sizeQuat << 1); + let _g = 0; + let _g1 = this.sizeQuat; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.stackQuat[i]; + this.stackQuat[i] = null; + } + this.stackQuat = newArray; + } + this.stackQuat[this.sizeQuat++] = q; + } +} +oimo.common.Quat = class oimo_common_Quat { + constructor(x,y,z,w) { + if(w == null) { + w = 1; + } + if(z == null) { + z = 0; + } + if(y == null) { + y = 0; + } + if(x == null) { + x = 0; + } + this.x = x; + this.y = y; + this.z = z; + this.w = w; + oimo.common.Quat.numCreations++; + } + identity() { + this.x = 0; + this.y = 0; + this.z = 0; + this.w = 1; + return this; + } + init(x,y,z,w) { + this.x = x; + this.y = y; + this.z = z; + this.w = w; + return this; + } + add(q) { + return new oimo.common.Quat(this.x + q.x,this.y + q.y,this.z + q.z,this.w + q.w); + } + sub(q) { + return new oimo.common.Quat(this.x - q.x,this.y - q.y,this.z - q.z,this.w - q.w); + } + scale(s) { + return new oimo.common.Quat(this.x * s,this.y * s,this.z * s,this.w * s); + } + addEq(q) { + this.x += q.x; + this.y += q.y; + this.z += q.z; + this.w += q.w; + return this; + } + subEq(q) { + this.x -= q.x; + this.y -= q.y; + this.z -= q.z; + this.w -= q.w; + return this; + } + scaleEq(s) { + this.x *= s; + this.y *= s; + this.z *= s; + this.w *= s; + return this; + } + length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + } + lengthSq() { + return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w; + } + dot(q) { + return this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w; + } + normalized() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + if(invLen > 0) { + invLen = 1 / invLen; + } + return new oimo.common.Quat(this.x * invLen,this.y * invLen,this.z * invLen,this.w * invLen); + } + normalize() { + let invLen = Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w); + if(invLen > 0) { + invLen = 1 / invLen; + } + this.x *= invLen; + this.y *= invLen; + this.z *= invLen; + this.w *= invLen; + return this; + } + setArc(v1,v2) { + let x1 = v1.x; + let y1 = v1.y; + let z1 = v1.z; + let x2 = v2.x; + let y2 = v2.y; + let z2 = v2.z; + let d = x1 * x2 + y1 * y2 + z1 * z2; + this.w = Math.sqrt((1 + d) * 0.5); + if(this.w == 0) { + x2 = x1 * x1; + y2 = y1 * y1; + z2 = z1 * z1; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this.x = 0; + this.y = z1 * d; + this.z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this.z = 0; + this.x = y1 * d; + this.y = -x1 * d; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this.y = 0; + this.z = x1 * d; + this.x = -z1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this.z = 0; + this.x = y1 * d; + this.y = -x1 * d; + } + return this; + } + d = 0.5 / this.w; + this.x = (y1 * z2 - z1 * y2) * d; + this.y = (z1 * x2 - x1 * z2) * d; + this.z = (x1 * y2 - y1 * x2) * d; + return this; + } + slerp(q,t) { + let qx; + let qy; + let qz; + let qw; + let d = this.x * q.x + this.y * q.y + this.z * q.z + this.w * q.w; + if(d < 0) { + d = -d; + qx = -q.x; + qy = -q.y; + qz = -q.z; + qw = -q.w; + } else { + qx = q.x; + qy = q.y; + qz = q.z; + qw = q.w; + } + if(d > 0.999999) { + let _this = new oimo.common.Quat(this.x + (qx - this.x) * t,this.y + (qy - this.y) * t,this.z + (qz - this.z) * t,this.w + (qw - this.w) * t); + let invLen = Math.sqrt(_this.x * _this.x + _this.y * _this.y + _this.z * _this.z + _this.w * _this.w); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this.x *= invLen; + _this.y *= invLen; + _this.z *= invLen; + _this.w *= invLen; + return _this; + } + let theta = t * Math.acos(d); + qx -= this.x * d; + qy -= this.y * d; + qz -= this.z * d; + qw -= this.w * d; + let invLen = 1 / Math.sqrt(qx * qx + qy * qy + qz * qz + qw * qw); + qx *= invLen; + qy *= invLen; + qz *= invLen; + qw *= invLen; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + return new oimo.common.Quat(this.x * cos + qx * sin,this.y * cos + qy * sin,this.z * cos + qz * sin,this.w * cos + qw * sin); + } + copyFrom(q) { + this.x = q.x; + this.y = q.y; + this.z = q.z; + this.w = q.w; + return this; + } + clone() { + return new oimo.common.Quat(this.x,this.y,this.z,this.w); + } + fromMat3(m) { + let e00 = m.e00; + let e11 = m.e11; + let e22 = m.e22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + this.w = 0.5 * s; + s = 0.5 / s; + this.x = (m.e21 - m.e12) * s; + this.y = (m.e02 - m.e20) * s; + this.z = (m.e10 - m.e01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + this.x = 0.5 * s; + s = 0.5 / s; + this.y = (m.e01 + m.e10) * s; + this.z = (m.e02 + m.e20) * s; + this.w = (m.e21 - m.e12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + this.z = 0.5 * s; + s = 0.5 / s; + this.x = (m.e02 + m.e20) * s; + this.y = (m.e12 + m.e21) * s; + this.w = (m.e10 - m.e01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + this.y = 0.5 * s; + s = 0.5 / s; + this.x = (m.e01 + m.e10) * s; + this.z = (m.e12 + m.e21) * s; + this.w = (m.e02 - m.e20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + this.z = 0.5 * s; + s = 0.5 / s; + this.x = (m.e02 + m.e20) * s; + this.y = (m.e12 + m.e21) * s; + this.w = (m.e10 - m.e01) * s; + } + return this; + } + toMat3() { + let _this = new oimo.common.Mat3(); + let x = this.x; + let y = this.y; + let z = this.z; + let w = this.w; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this.e00 = 1 - yy - zz; + _this.e01 = xy - wz; + _this.e02 = xz + wy; + _this.e10 = xy + wz; + _this.e11 = 1 - xx - zz; + _this.e12 = yz - wx; + _this.e20 = xz - wy; + _this.e21 = yz + wx; + _this.e22 = 1 - xx - yy; + return _this; + } + toString() { + return "Quat[" + (this.x > 0 ? (this.x * 10000000 + 0.5 | 0) / 10000000 : (this.x * 10000000 - 0.5 | 0) / 10000000) + " i,\n" + " " + (this.y > 0 ? (this.y * 10000000 + 0.5 | 0) / 10000000 : (this.y * 10000000 - 0.5 | 0) / 10000000) + " j,\n" + " " + (this.z > 0 ? (this.z * 10000000 + 0.5 | 0) / 10000000 : (this.z * 10000000 - 0.5 | 0) / 10000000) + " k,\n" + " " + (this.w > 0 ? (this.w * 10000000 + 0.5 | 0) / 10000000 : (this.w * 10000000 - 0.5 | 0) / 10000000) + "]"; + } +} +if(!oimo.dynamics) oimo.dynamics = {}; +oimo.dynamics.Contact = class oimo_dynamics_Contact { + constructor() { + this._next = null; + this._prev = null; + this._link1 = new oimo.dynamics.ContactLink(); + this._link2 = new oimo.dynamics.ContactLink(); + this._s1 = null; + this._s2 = null; + this._b1 = null; + this._b2 = null; + this._detector = null; + this._cachedDetectorData = new oimo.collision.narrowphase.detector.CachedDetectorData(); + this._detectorResult = new oimo.collision.narrowphase.DetectorResult(); + this._latest = false; + this._shouldBeSkipped = false; + this._manifold = new oimo.dynamics.constraint.contact.Manifold(); + this._updater = new oimo.dynamics.constraint.contact.ManifoldUpdater(this._manifold); + this._contactConstraint = new oimo.dynamics.constraint.contact.ContactConstraint(this._manifold); + this._touching = false; + } + _updateManifold() { + if(this._detector == null) { + return; + } + let ptouching = this._touching; + let result = this._detectorResult; + this._detector.detect(result,this._s1._geom,this._s2._geom,this._s1._transform,this._s2._transform,this._cachedDetectorData); + this._touching = result.numPoints > 0; + if(this._touching) { + this._manifold._buildBasis(result.normal); + if(result.getMaxDepth() > oimo.common.Setting.contactUseAlternativePositionCorrectionAlgorithmDepthThreshold) { + this._contactConstraint._positionCorrectionAlgorithm = oimo.common.Setting.alternativeContactPositionCorrectionAlgorithm; + } else { + this._contactConstraint._positionCorrectionAlgorithm = oimo.common.Setting.defaultContactPositionCorrectionAlgorithm; + } + if(result.incremental) { + this._updater.incrementalUpdate(result,this._b1._transform,this._b2._transform); + } else { + this._updater.totalUpdate(result,this._b1._transform,this._b2._transform); + } + } else { + this._manifold._clear(); + } + if(this._touching && !ptouching) { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.beginContact(this); + } + if(cc2 != null) { + cc2.beginContact(this); + } + } + if(!this._touching && ptouching) { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(this); + } + if(cc2 != null) { + cc2.endContact(this); + } + } + if(this._touching) { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.preSolve(this); + } + if(cc2 != null) { + cc2.preSolve(this); + } + } + } + _postSolve() { + let cc1 = this._s1._contactCallback; + let cc2 = this._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.postSolve(this); + } + if(cc2 != null) { + cc2.postSolve(this); + } + } + getShape1() { + return this._s1; + } + getShape2() { + return this._s2; + } + isTouching() { + return this._touching; + } + getManifold() { + return this._manifold; + } + getContactConstraint() { + return this._contactConstraint; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.ContactLink = class oimo_dynamics_ContactLink { + constructor() { + this._prev = null; + this._next = null; + this._contact = null; + this._other = null; + } + getContact() { + return this._contact; + } + getOther() { + return this._other; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.ContactManager = class oimo_dynamics_ContactManager { + constructor(broadPhase) { + this._broadPhase = broadPhase; + this._collisionMatrix = new oimo.collision.narrowphase.CollisionMatrix(); + this._numContacts = 0; + } + createContacts() { + let pp = this._broadPhase._proxyPairList; + while(pp != null) { + let n = pp._next; + while(true) { + let s1; + let s2; + if(pp._p1._id < pp._p2._id) { + s1 = pp._p1.userData; + s2 = pp._p2.userData; + } else { + s1 = pp._p2.userData; + s2 = pp._p1.userData; + } + if(!this.shouldCollide(s1,s2)) { + break; + } + let b1 = s1._rigidBody; + let b2 = s2._rigidBody; + let l; + if(b1._numContactLinks < b2._numContactLinks) { + l = b1._contactLinkList; + } else { + l = b2._contactLinkList; + } + let id1 = s1._id; + let id2 = s2._id; + let found = false; + while(l != null) { + let c = l._contact; + if(c._s1._id == id1 && c._s2._id == id2) { + c._latest = true; + found = true; + break; + } + l = l._next; + } + if(!found) { + let first = this._contactPool; + if(first != null) { + this._contactPool = first._next; + first._next = null; + } else { + first = new oimo.dynamics.Contact(); + } + let c = first; + if(this._contactList == null) { + this._contactList = c; + this._contactListLast = c; + } else { + this._contactListLast._next = c; + c._prev = this._contactListLast; + this._contactListLast = c; + } + c._latest = true; + let detector = this._collisionMatrix.detectors[s1._geom._type][s2._geom._type]; + c._s1 = s1; + c._s2 = s2; + c._b1 = s1._rigidBody; + c._b2 = s2._rigidBody; + c._touching = false; + if(c._b1._contactLinkList == null) { + c._b1._contactLinkList = c._link1; + c._b1._contactLinkListLast = c._link1; + } else { + c._b1._contactLinkListLast._next = c._link1; + c._link1._prev = c._b1._contactLinkListLast; + c._b1._contactLinkListLast = c._link1; + } + if(c._b2._contactLinkList == null) { + c._b2._contactLinkList = c._link2; + c._b2._contactLinkListLast = c._link2; + } else { + c._b2._contactLinkListLast._next = c._link2; + c._link2._prev = c._b2._contactLinkListLast; + c._b2._contactLinkListLast = c._link2; + } + c._b1._numContactLinks++; + c._b2._numContactLinks++; + c._link1._other = c._b2; + c._link2._other = c._b1; + c._link1._contact = c; + c._link2._contact = c; + c._detector = detector; + let _this = c._contactConstraint; + _this._s1 = s1; + _this._s2 = s2; + _this._b1 = _this._s1._rigidBody; + _this._b2 = _this._s2._rigidBody; + _this._tf1 = _this._b1._transform; + _this._tf2 = _this._b2._transform; + this._numContacts++; + } + break; + } + pp = n; + } + } + destroyOutdatedContacts() { + let incremental = this._broadPhase._incremental; + let c = this._contactList; + while(c != null) { + let n = c._next; + while(true) { + if(c._latest) { + c._latest = false; + c._shouldBeSkipped = false; + break; + } + if(!incremental) { + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == this._contactList) { + this._contactList = this._contactList._next; + } + if(c == this._contactListLast) { + this._contactListLast = this._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this = c._contactConstraint; + _this._s1 = null; + _this._s2 = null; + _this._b1 = null; + _this._b2 = null; + _this._tf1 = null; + _this._tf2 = null; + c._next = this._contactPool; + this._contactPool = c; + this._numContacts--; + break; + } + let s1 = c._s1; + let s2 = c._s2; + let r1 = s1._rigidBody; + let r2 = s2._rigidBody; + if(!(!r1._sleeping && r1._type != 1) && !(!r2._sleeping && r2._type != 1)) { + c._shouldBeSkipped = true; + break; + } + let aabb1 = s1._aabb; + let aabb2 = s2._aabb; + let proxy1 = s1._proxy; + let proxy2 = s2._proxy; + if(!(proxy1._aabbMinX < proxy2._aabbMaxX && proxy1._aabbMaxX > proxy2._aabbMinX && proxy1._aabbMinY < proxy2._aabbMaxY && proxy1._aabbMaxY > proxy2._aabbMinY && proxy1._aabbMinZ < proxy2._aabbMaxZ && proxy1._aabbMaxZ > proxy2._aabbMinZ) || !this.shouldCollide(s1,s2)) { + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == this._contactList) { + this._contactList = this._contactList._next; + } + if(c == this._contactListLast) { + this._contactListLast = this._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this = c._contactConstraint; + _this._s1 = null; + _this._s2 = null; + _this._b1 = null; + _this._b2 = null; + _this._tf1 = null; + _this._tf2 = null; + c._next = this._contactPool; + this._contactPool = c; + this._numContacts--; + break; + } + c._shouldBeSkipped = !(aabb1._minX < aabb2._maxX && aabb1._maxX > aabb2._minX && aabb1._minY < aabb2._maxY && aabb1._maxY > aabb2._minY && aabb1._minZ < aabb2._maxZ && aabb1._maxZ > aabb2._minZ); + break; + } + c = n; + } + } + shouldCollide(s1,s2) { + let r1 = s1._rigidBody; + let r2 = s2._rigidBody; + if(r1 == r2) { + return false; + } + if(r1._type != 0 && r2._type != 0) { + return false; + } + if((s1._collisionGroup & s2._collisionMask) == 0 || (s2._collisionGroup & s1._collisionMask) == 0) { + return false; + } + let jl; + let other; + if(r1._numJointLinks < r2._numJointLinks) { + jl = r1._jointLinkList; + other = r2; + } else { + jl = r2._jointLinkList; + other = r1; + } + while(jl != null) { + if(jl._other == other && !jl._joint._allowCollision) { + return false; + } + jl = jl._next; + } + return true; + } + _updateContacts() { + this._broadPhase.collectPairs(); + this.createContacts(); + this.destroyOutdatedContacts(); + } + _postSolve() { + let c = this._contactList; + while(c != null) { + let n = c._next; + if(c._touching) { + c._postSolve(); + } + c = n; + } + } + getNumContacts() { + return this._numContacts; + } + getContactList() { + return this._contactList; + } +} +oimo.dynamics.Island = class oimo_dynamics_Island { + constructor() { + this.rigidBodies = new Array(oimo.common.Setting.islandInitialRigidBodyArraySize); + this.solvers = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this.solversSi = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this.solversNgs = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this.numRigidBodies = 0; + this.numSolvers = 0; + this.numSolversSi = 0; + this.numSolversNgs = 0; + } + _clear() { + while(this.numRigidBodies > 0) this.rigidBodies[--this.numRigidBodies] = null; + while(this.numSolvers > 0) this.solvers[--this.numSolvers] = null; + while(this.numSolversSi > 0) this.solversSi[--this.numSolversSi] = null; + while(this.numSolversNgs > 0) this.solversNgs[--this.numSolversNgs] = null; + } + _addRigidBody(rigidBody) { + if(this.numRigidBodies == this.rigidBodies.length) { + let newArray = new Array(this.numRigidBodies << 1); + let _g = 0; + let _g1 = this.numRigidBodies; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.rigidBodies[i]; + this.rigidBodies[i] = null; + } + this.rigidBodies = newArray; + } + rigidBody._addedToIsland = true; + this.rigidBodies[this.numRigidBodies++] = rigidBody; + } + _addConstraintSolver(solver,positionCorrection) { + if(this.numSolvers == this.solvers.length) { + let newArray = new Array(this.numSolvers << 1); + let _g = 0; + let _g1 = this.numSolvers; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.solvers[i]; + this.solvers[i] = null; + } + this.solvers = newArray; + } + solver._addedToIsland = true; + this.solvers[this.numSolvers++] = solver; + if(positionCorrection == oimo.dynamics.constraint.PositionCorrectionAlgorithm.SPLIT_IMPULSE) { + if(this.numSolversSi == this.solversSi.length) { + let newArray = new Array(this.numSolversSi << 1); + let _g = 0; + let _g1 = this.numSolversSi; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.solversSi[i]; + this.solversSi[i] = null; + } + this.solversSi = newArray; + } + this.solversSi[this.numSolversSi++] = solver; + } + if(positionCorrection == oimo.dynamics.constraint.PositionCorrectionAlgorithm.NGS) { + if(this.numSolversNgs == this.solversNgs.length) { + let newArray = new Array(this.numSolversNgs << 1); + let _g = 0; + let _g1 = this.numSolversNgs; + while(_g < _g1) { + let i = _g++; + newArray[i] = this.solversNgs[i]; + this.solversNgs[i] = null; + } + this.solversNgs = newArray; + } + this.solversNgs[this.numSolversNgs++] = solver; + } + } + _stepSingleRigidBody(timeStep,rb) { + let dt = timeStep.dt; + let dst = rb._ptransform; + let src = rb._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + rb._linearContactImpulseX = 0; + rb._linearContactImpulseY = 0; + rb._linearContactImpulseZ = 0; + rb._angularContactImpulseX = 0; + rb._angularContactImpulseY = 0; + rb._angularContactImpulseZ = 0; + if(rb._autoSleep && rb._velX * rb._velX + rb._velY * rb._velY + rb._velZ * rb._velZ < oimo.common.Setting.sleepingVelocityThreshold * oimo.common.Setting.sleepingVelocityThreshold && rb._angVelX * rb._angVelX + rb._angVelY * rb._angVelY + rb._angVelZ * rb._angVelZ < oimo.common.Setting.sleepingAngularVelocityThreshold * oimo.common.Setting.sleepingAngularVelocityThreshold) { + rb._sleepTime += dt; + if(rb._sleepTime > oimo.common.Setting.sleepingTimeThreshold) { + rb._sleeping = true; + rb._sleepTime = 0; + } + } else { + rb._sleepTime = 0; + } + if(!rb._sleeping) { + if(rb._type == 0) { + let x = dt * rb._linearDamping; + let x2 = x * x; + let linScale = 1 / (1 + x + x2 * (0.5 + x * 0.16666666666666666 + x2 * 0.041666666666666664)); + let x1 = dt * rb._angularDamping; + let x21 = x1 * x1; + let angScale = 1 / (1 + x1 + x21 * (0.5 + x1 * 0.16666666666666666 + x21 * 0.041666666666666664)); + let linAccX; + let linAccY; + let linAccZ; + let angAccX; + let angAccY; + let angAccZ; + linAccX = this.gravityX * rb._gravityScale; + linAccY = this.gravityY * rb._gravityScale; + linAccZ = this.gravityZ * rb._gravityScale; + linAccX += rb._forceX * rb._invMass; + linAccY += rb._forceY * rb._invMass; + linAccZ += rb._forceZ * rb._invMass; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rb._invInertia00 * rb._torqueX + rb._invInertia01 * rb._torqueY + rb._invInertia02 * rb._torqueZ; + __tmp__Y = rb._invInertia10 * rb._torqueX + rb._invInertia11 * rb._torqueY + rb._invInertia12 * rb._torqueZ; + __tmp__Z = rb._invInertia20 * rb._torqueX + rb._invInertia21 * rb._torqueY + rb._invInertia22 * rb._torqueZ; + angAccX = __tmp__X; + angAccY = __tmp__Y; + angAccZ = __tmp__Z; + rb._velX += linAccX * dt; + rb._velY += linAccY * dt; + rb._velZ += linAccZ * dt; + rb._velX *= linScale; + rb._velY *= linScale; + rb._velZ *= linScale; + rb._angVelX += angAccX * dt; + rb._angVelY += angAccY * dt; + rb._angVelZ += angAccZ * dt; + rb._angVelX *= angScale; + rb._angVelY *= angScale; + rb._angVelZ *= angScale; + } + rb._integrate(dt); + let s = rb._shapeList; + while(s != null) { + let n = s._next; + let tf1 = rb._ptransform; + let tf2 = rb._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } + _step(timeStep,numVelocityIterations,numPositionIterations) { + let dt = timeStep.dt; + let sleepIsland = true; + let _g = 0; + let _g1 = this.numRigidBodies; + while(_g < _g1) { + let rb = this.rigidBodies[_g++]; + let dst = rb._ptransform; + let src = rb._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + rb._linearContactImpulseX = 0; + rb._linearContactImpulseY = 0; + rb._linearContactImpulseZ = 0; + rb._angularContactImpulseX = 0; + rb._angularContactImpulseY = 0; + rb._angularContactImpulseZ = 0; + rb._sleeping = false; + if(rb._autoSleep && rb._velX * rb._velX + rb._velY * rb._velY + rb._velZ * rb._velZ < oimo.common.Setting.sleepingVelocityThreshold * oimo.common.Setting.sleepingVelocityThreshold && rb._angVelX * rb._angVelX + rb._angVelY * rb._angVelY + rb._angVelZ * rb._angVelZ < oimo.common.Setting.sleepingAngularVelocityThreshold * oimo.common.Setting.sleepingAngularVelocityThreshold) { + rb._sleepTime += dt; + } else { + rb._sleepTime = 0; + } + if(rb._sleepTime < oimo.common.Setting.sleepingTimeThreshold) { + sleepIsland = false; + } + if(rb._type == 0) { + let x = dt * rb._linearDamping; + let x2 = x * x; + let linScale = 1 / (1 + x + x2 * (0.5 + x * 0.16666666666666666 + x2 * 0.041666666666666664)); + let x1 = dt * rb._angularDamping; + let x21 = x1 * x1; + let angScale = 1 / (1 + x1 + x21 * (0.5 + x1 * 0.16666666666666666 + x21 * 0.041666666666666664)); + let linAccX; + let linAccY; + let linAccZ; + let angAccX; + let angAccY; + let angAccZ; + linAccX = this.gravityX * rb._gravityScale; + linAccY = this.gravityY * rb._gravityScale; + linAccZ = this.gravityZ * rb._gravityScale; + linAccX += rb._forceX * rb._invMass; + linAccY += rb._forceY * rb._invMass; + linAccZ += rb._forceZ * rb._invMass; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rb._invInertia00 * rb._torqueX + rb._invInertia01 * rb._torqueY + rb._invInertia02 * rb._torqueZ; + __tmp__Y = rb._invInertia10 * rb._torqueX + rb._invInertia11 * rb._torqueY + rb._invInertia12 * rb._torqueZ; + __tmp__Z = rb._invInertia20 * rb._torqueX + rb._invInertia21 * rb._torqueY + rb._invInertia22 * rb._torqueZ; + angAccX = __tmp__X; + angAccY = __tmp__Y; + angAccZ = __tmp__Z; + rb._velX += linAccX * dt; + rb._velY += linAccY * dt; + rb._velZ += linAccZ * dt; + rb._velX *= linScale; + rb._velY *= linScale; + rb._velZ *= linScale; + rb._angVelX += angAccX * dt; + rb._angVelY += angAccY * dt; + rb._angVelZ += angAccZ * dt; + rb._angVelX *= angScale; + rb._angVelY *= angScale; + rb._angVelZ *= angScale; + } + } + if(sleepIsland) { + let _g = 0; + let _g1 = this.numRigidBodies; + while(_g < _g1) { + let rb = this.rigidBodies[_g++]; + rb._sleeping = true; + rb._sleepTime = 0; + } + return; + } + let _g2 = 0; + let _g3 = this.numSolvers; + while(_g2 < _g3) this.solvers[_g2++].preSolveVelocity(timeStep); + let _g4 = 0; + let _g5 = this.numSolvers; + while(_g4 < _g5) this.solvers[_g4++].warmStart(timeStep); + let _g6 = 0; + while(_g6 < numVelocityIterations) { + ++_g6; + let _g = 0; + let _g1 = this.numSolvers; + while(_g < _g1) this.solvers[_g++].solveVelocity(); + } + let _g7 = 0; + let _g8 = this.numSolvers; + while(_g7 < _g8) this.solvers[_g7++].postSolveVelocity(timeStep); + let _g9 = 0; + let _g10 = this.numRigidBodies; + while(_g9 < _g10) this.rigidBodies[_g9++]._integrate(dt); + let _g11 = 0; + let _g12 = this.numSolversSi; + while(_g11 < _g12) this.solversSi[_g11++].preSolvePosition(timeStep); + let _g13 = 0; + while(_g13 < numPositionIterations) { + ++_g13; + let _g = 0; + let _g1 = this.numSolversSi; + while(_g < _g1) this.solversSi[_g++].solvePositionSplitImpulse(); + } + let _g14 = 0; + let _g15 = this.numRigidBodies; + while(_g14 < _g15) this.rigidBodies[_g14++]._integratePseudoVelocity(); + let _g16 = 0; + let _g17 = this.numSolversNgs; + while(_g16 < _g17) this.solversNgs[_g16++].preSolvePosition(timeStep); + let _g18 = 0; + while(_g18 < numPositionIterations) { + ++_g18; + let _g = 0; + let _g1 = this.numSolversNgs; + while(_g < _g1) this.solversNgs[_g++].solvePositionNgs(timeStep); + } + let _g19 = 0; + let _g20 = this.numSolvers; + while(_g19 < _g20) this.solvers[_g19++].postSolve(); + let _g21 = 0; + let _g22 = this.numRigidBodies; + while(_g21 < _g22) { + let rb = this.rigidBodies[_g21++]; + let s = rb._shapeList; + while(s != null) { + let n = s._next; + let tf1 = rb._ptransform; + let tf2 = rb._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } +} +oimo.dynamics.TimeStep = class oimo_dynamics_TimeStep { + constructor() { + this.dt = 0; + this.invDt = 0; + this.dtRatio = 1; + } +} +oimo.dynamics.World = class oimo_dynamics_World { + constructor(broadPhaseType,gravity) { + if(broadPhaseType == null) { + broadPhaseType = 2; + } + switch(broadPhaseType) { + case 1: + this._broadPhase = new oimo.collision.broadphase.bruteforce.BruteForceBroadPhase(); + break; + case 2: + this._broadPhase = new oimo.collision.broadphase.bvh.BvhBroadPhase(); + break; + } + this._contactManager = new oimo.dynamics.ContactManager(this._broadPhase); + if(gravity == null) { + gravity = new oimo.common.Vec3(0,-9.80665,0); + } + this._gravity = new oimo.common.Vec3(gravity.x,gravity.y,gravity.z); + this._rigidBodyList = null; + this._rigidBodyListLast = null; + this._jointList = null; + this._jointListLast = null; + this._numRigidBodies = 0; + this._numShapes = 0; + this._numJoints = 0; + this._numIslands = 0; + this._numVelocityIterations = 10; + this._numPositionIterations = 5; + this._rayCastWrapper = new oimo.dynamics._World.RayCastWrapper(); + this._convexCastWrapper = new oimo.dynamics._World.ConvexCastWrapper(); + this._aabbTestWrapper = new oimo.dynamics._World.AabbTestWrapper(); + this._island = new oimo.dynamics.Island(); + this._solversInIslands = new Array(oimo.common.Setting.islandInitialConstraintArraySize); + this._rigidBodyStack = new Array(oimo.common.Setting.islandInitialRigidBodyArraySize); + this._timeStep = new oimo.dynamics.TimeStep(); + this._pool = new oimo.common.Pool(); + this._shapeIdCount = 0; + } + _updateContacts() { + let st = Date.now() / 1000; + this._contactManager._updateContacts(); + oimo.dynamics.common.Performance.broadPhaseCollisionTime = (Date.now() / 1000 - st) * 1000; + let st1 = Date.now() / 1000; + let c = this._contactManager._contactList; + while(c != null) { + let n = c._next; + if(!c._shouldBeSkipped) { + c._updateManifold(); + } + c = n; + } + oimo.dynamics.common.Performance.narrowPhaseCollisionTime = (Date.now() / 1000 - st1) * 1000; + } + _solveIslands() { + let st = Date.now() / 1000; + if(oimo.common.Setting.disableSleeping) { + let b = this._rigidBodyList; + while(b != null) { + b._sleeping = false; + b._sleepTime = 0; + b = b._next; + } + } + if(this._rigidBodyStack.length < this._numRigidBodies) { + let newStackSize = this._rigidBodyStack.length << 1; + while(newStackSize < this._numRigidBodies) newStackSize <<= 1; + this._rigidBodyStack = new Array(newStackSize); + } + this._numIslands = 0; + let _this = this._island; + let gravity = this._gravity; + _this.gravityX = gravity.x; + _this.gravityY = gravity.y; + _this.gravityZ = gravity.z; + let b = this._rigidBodyList; + this._numSolversInIslands = 0; + while(b != null) { + let n = b._next; + while(!(b._addedToIsland || b._sleeping || b._type == 1)) { + if(b._numContactLinks == 0 && b._numJointLinks == 0) { + this._island._stepSingleRigidBody(this._timeStep,b); + this._numIslands++; + break; + } + this.buildIsland(b); + this._island._step(this._timeStep,this._numVelocityIterations,this._numPositionIterations); + this._island._clear(); + this._numIslands++; + break; + } + b = n; + } + this._contactManager._postSolve(); + b = this._rigidBodyList; + while(b != null) { + b._addedToIsland = false; + b = b._next; + } + b = this._rigidBodyList; + while(b != null) { + b._forceX = 0; + b._forceY = 0; + b._forceZ = 0; + b._torqueX = 0; + b._torqueY = 0; + b._torqueZ = 0; + b = b._next; + } + while(this._numSolversInIslands > 0) { + this._solversInIslands[--this._numSolversInIslands]._addedToIsland = false; + this._solversInIslands[this._numSolversInIslands] = null; + } + oimo.dynamics.common.Performance.dynamicsTime = (Date.now() / 1000 - st) * 1000; + } + buildIsland(base) { + let stackCount = 1; + this._island._addRigidBody(base); + this._rigidBodyStack[0] = base; + while(stackCount > 0) { + let rb = this._rigidBodyStack[--stackCount]; + this._rigidBodyStack[stackCount] = null; + if(rb._type == 1) { + continue; + } + let cl = rb._contactLinkList; + while(cl != null) { + let n = cl._next; + let cc = cl._contact._contactConstraint; + let ccs = cl._contact._contactConstraint._solver; + if(cc.isTouching() && !ccs._addedToIsland) { + if(this._solversInIslands.length == this._numSolversInIslands) { + let newArray = new Array(this._numSolversInIslands << 1); + let _g = 0; + let _g1 = this._numSolversInIslands; + while(_g < _g1) { + let i = _g++; + newArray[i] = this._solversInIslands[i]; + this._solversInIslands[i] = null; + } + this._solversInIslands = newArray; + } + this._solversInIslands[this._numSolversInIslands++] = ccs; + this._island._addConstraintSolver(ccs,cc._positionCorrectionAlgorithm); + let other = cl._other; + if(!other._addedToIsland) { + this._island._addRigidBody(other); + this._rigidBodyStack[stackCount++] = other; + } + } + cl = n; + } + let jl = rb._jointLinkList; + while(jl != null) { + let n = jl._next; + let j = jl._joint; + let js1 = j._solver; + if(!js1._addedToIsland) { + if(this._solversInIslands.length == this._numSolversInIslands) { + let newArray = new Array(this._numSolversInIslands << 1); + let _g = 0; + let _g1 = this._numSolversInIslands; + while(_g < _g1) { + let i = _g++; + newArray[i] = this._solversInIslands[i]; + this._solversInIslands[i] = null; + } + this._solversInIslands = newArray; + } + this._solversInIslands[this._numSolversInIslands++] = js1; + this._island._addConstraintSolver(js1,j._positionCorrectionAlgorithm); + let other = jl._other; + if(!other._addedToIsland) { + this._island._addRigidBody(other); + this._rigidBodyStack[stackCount++] = other; + } + } + jl = n; + } + } + } + _drawBvh(d,tree) { + if(d.drawBvh) { + this._drawBvhNode(d,tree._root,0,d.style.bvhNodeColor); + } + } + _drawBvhNode(d,node,level,color) { + if(node == null) { + return; + } + if(level >= d.drawBvhMinLevel && level <= d.drawBvhMaxLevel) { + let _this = this._pool; + let min = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let max = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = min; + v.x = node._aabbMinX; + v.y = node._aabbMinY; + v.z = node._aabbMinZ; + let v1 = max; + v1.x = node._aabbMaxX; + v1.y = node._aabbMaxY; + v1.z = node._aabbMaxZ; + d.aabb(min,max,color); + let _this2 = this._pool; + if(min != null) { + min.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = min; + } + let _this3 = this._pool; + if(max != null) { + max.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = max; + } + } + this._drawBvhNode(d,node._children[0],level + 1,color); + this._drawBvhNode(d,node._children[1],level + 1,color); + } + _drawRigidBodies(d) { + let style = d.style; + let r = this._rigidBodyList; + while(r != null) { + let n = r._next; + if(d.drawBases) { + let style = d.style; + d.basis(r._transform,style.basisLength,style.basisColorX,style.basisColorY,style.basisColorZ); + } + let shapeColor = null; + let isDynamic = r._type == 0; + if(!isDynamic) { + shapeColor = r._type == 2 ? style.kinematicShapeColor : style.staticShapeColor; + } + let s = r._shapeList; + while(s != null) { + let n = s._next; + if(isDynamic) { + if((s._id & 1) == 0) { + shapeColor = r._sleeping ? style.sleepingShapeColor1 : r._sleepTime > oimo.common.Setting.sleepingTimeThreshold ? style.sleepyShapeColor1 : style.shapeColor1; + } else { + shapeColor = r._sleeping ? style.sleepingShapeColor2 : r._sleepTime > oimo.common.Setting.sleepingTimeThreshold ? style.sleepyShapeColor2 : style.shapeColor2; + } + } + if(d.drawShapes) { + let geom = s._geom; + let tf = s._transform; + switch(geom._type) { + case 0: + d.sphere(tf,geom._radius,shapeColor); + break; + case 1: + let g = geom; + let _this = this._pool; + let hx = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let v = hx; + v.x = g._halfExtentsX; + v.y = g._halfExtentsY; + v.z = g._halfExtentsZ; + d.box(tf,hx,shapeColor); + let _this1 = this._pool; + if(hx != null) { + hx.zero(); + if(_this1.sizeVec3 == _this1.stackVec3.length) { + let newArray = new Array(_this1.sizeVec3 << 1); + let _g = 0; + let _g1 = _this1.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this1.stackVec3[i]; + _this1.stackVec3[i] = null; + } + _this1.stackVec3 = newArray; + } + _this1.stackVec3[_this1.sizeVec3++] = hx; + } + break; + case 2: + let g1 = geom; + d.cylinder(tf,g1._radius,g1._halfHeight,shapeColor); + break; + case 3: + let g2 = geom; + d.cone(tf,g2._radius,g2._halfHeight,shapeColor); + break; + case 4: + let g3 = geom; + d.capsule(tf,g3._radius,g3._halfHeight,shapeColor); + break; + case 5: + let g4 = geom; + let n = g4._numVertices; + let _this2 = this._pool; + let v1 = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this._pool; + let v2 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this._pool; + let v3 = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let _this5 = this._pool; + let v12 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + let _this6 = this._pool; + let v13 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + let _this7 = this._pool; + let normal = _this7.sizeVec3 == 0 ? new oimo.common.Vec3() : _this7.stackVec3[--_this7.sizeVec3]; + let _this8 = this._pool; + let m = _this8.sizeMat3 == 0 ? new oimo.common.Mat3() : _this8.stackMat3[--_this8.sizeMat3]; + let _this9 = this._pool; + let o = _this9.sizeVec3 == 0 ? new oimo.common.Vec3() : _this9.stackVec3[--_this9.sizeVec3]; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + let v4 = o; + v4.x = tf._positionX; + v4.y = tf._positionY; + v4.z = tf._positionZ; + let _g = 0; + while(_g < n) { + let i = _g++; + let _this = g4._tmpVertices[i]; + let v = g4._vertices[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + _this.x += o.x; + _this.y += o.y; + _this.z += o.z; + } + if(n > 30) { + let _g = 0; + while(_g < n) { + let i = _g++; + let v = g4._tmpVertices[i]; + v1.x = v.x; + v1.y = v.y; + v1.z = v.z; + let v3 = g4._tmpVertices[(i + 1) % n]; + v2.x = v3.x; + v2.y = v3.y; + v2.z = v3.z; + d.line(v1,v2,shapeColor); + } + } else if(this._debugDraw.wireframe || n > 10) { + let _g = 0; + while(_g < n) { + let i = _g++; + let v = g4._tmpVertices[i]; + v1.x = v.x; + v1.y = v.y; + v1.z = v.z; + let _g1 = 0; + while(_g1 < i) { + let v = g4._tmpVertices[_g1++]; + v2.x = v.x; + v2.y = v.y; + v2.z = v.z; + d.line(v1,v2,shapeColor); + } + } + } else { + let _g = 0; + while(_g < n) { + let i = _g++; + let v = g4._tmpVertices[i]; + v1.x = v.x; + v1.y = v.y; + v1.z = v.z; + let _g1 = 0; + while(_g1 < i) { + let j = _g1++; + let v = g4._tmpVertices[j]; + v2.x = v.x; + v2.y = v.y; + v2.z = v.z; + let _g = 0; + while(_g < j) { + let v = g4._tmpVertices[_g++]; + v3.x = v.x; + v3.y = v.y; + v3.z = v.z; + v12.x = v2.x; + v12.y = v2.y; + v12.z = v2.z; + let _this = v12; + _this.x -= v1.x; + _this.y -= v1.y; + _this.z -= v1.z; + v13.x = v3.x; + v13.y = v3.y; + v13.z = v3.z; + let _this1 = v13; + _this1.x -= v1.x; + _this1.y -= v1.y; + _this1.z -= v1.z; + normal.x = v12.x; + normal.y = v12.y; + normal.z = v12.z; + let _this2 = normal; + let y = _this2.z * v13.x - _this2.x * v13.z; + let z = _this2.x * v13.y - _this2.y * v13.x; + _this2.x = _this2.y * v13.z - _this2.z * v13.y; + _this2.y = y; + _this2.z = z; + let invLen = Math.sqrt(_this2.x * _this2.x + _this2.y * _this2.y + _this2.z * _this2.z); + if(invLen > 0) { + invLen = 1 / invLen; + } + _this2.x *= invLen; + _this2.y *= invLen; + _this2.z *= invLen; + d.triangle(v1,v2,v3,normal,normal,normal,shapeColor); + normal.x = -normal.x; + normal.y = -normal.y; + normal.z = -normal.z; + d.triangle(v1,v3,v2,normal,normal,normal,shapeColor); + } + } + } + } + let _this10 = this._pool; + if(v1 != null) { + v1.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = v1; + } + let _this11 = this._pool; + if(v2 != null) { + v2.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = v2; + } + let _this12 = this._pool; + if(v3 != null) { + v3.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = v3; + } + let _this13 = this._pool; + if(v12 != null) { + v12.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = v12; + } + let _this14 = this._pool; + if(v13 != null) { + v13.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = v13; + } + let _this15 = this._pool; + if(normal != null) { + normal.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = normal; + } + let _this16 = this._pool; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this16.sizeMat3 == _this16.stackMat3.length) { + let newArray = new Array(_this16.sizeMat3 << 1); + let _g = 0; + let _g1 = _this16.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackMat3[i]; + _this16.stackMat3[i] = null; + } + _this16.stackMat3 = newArray; + } + _this16.stackMat3[_this16.sizeMat3++] = m; + } + let _this17 = this._pool; + if(o != null) { + o.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = o; + } + break; + } + } + if(d.drawAabbs) { + let aabb = s._aabb; + let color = style.aabbColor; + let _this = this._pool; + let min = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let max = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = min; + v.x = aabb._minX; + v.y = aabb._minY; + v.z = aabb._minZ; + let v1 = max; + v1.x = aabb._maxX; + v1.y = aabb._maxY; + v1.z = aabb._maxZ; + d.aabb(min,max,color); + let _this2 = this._pool; + if(min != null) { + min.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = min; + } + let _this3 = this._pool; + if(max != null) { + max.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = max; + } + } + s = n; + } + r = n; + } + } + _drawConstraints(d) { + let style = d.style; + if(d.drawPairs || d.drawContacts) { + let c = this._contactManager._contactList; + while(c != null) { + let n = c._next; + if(d.drawPairs) { + let color = style.pairColor; + let _this = this._pool; + let v1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let v2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = v1; + v.x = c._s1._transform._positionX; + v.y = c._s1._transform._positionY; + v.z = c._s1._transform._positionZ; + let v3 = v2; + v3.x = c._s2._transform._positionX; + v3.y = c._s2._transform._positionY; + v3.z = c._s2._transform._positionZ; + d.line(v1,v2,color); + let _this2 = this._pool; + if(v1 != null) { + v1.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = v1; + } + let _this3 = this._pool; + if(v2 != null) { + v2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = v2; + } + } + if(d.drawContacts) { + let cc = c._contactConstraint; + let ps = c._contactConstraint._manifold._points; + let _g = 0; + let _g1 = c._contactConstraint._manifold._numPoints; + while(_g < _g1) { + let p = ps[_g++]; + let style = d.style; + let _this = this._pool; + let pos1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let pos2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this._pool; + let normal = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this._pool; + let tangent = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this._pool; + let binormal = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let v = pos1; + v.x = p._pos1X; + v.y = p._pos1Y; + v.z = p._pos1Z; + let v1 = pos2; + v1.x = p._pos2X; + v1.y = p._pos2Y; + v1.z = p._pos2Z; + let v2 = normal; + v2.x = cc._manifold._normalX; + v2.y = cc._manifold._normalY; + v2.z = cc._manifold._normalZ; + let v3 = tangent; + v3.x = cc._manifold._tangentX; + v3.y = cc._manifold._tangentY; + v3.z = cc._manifold._tangentZ; + let v4 = binormal; + v4.x = cc._manifold._binormalX; + v4.y = cc._manifold._binormalY; + v4.z = cc._manifold._binormalZ; + if(p._disabled) { + d.point(pos1,style.disabledContactColor); + d.point(pos2,style.disabledContactColor); + d.line(pos1,pos2,style.disabledContactColor); + } else if(p._warmStarted) { + let color; + switch(p._id & 3) { + case 0: + color = style.contactColor; + break; + case 1: + color = style.contactColor2; + break; + case 2: + color = style.contactColor3; + break; + default: + color = style.contactColor4; + } + d.point(pos1,color); + d.point(pos2,color); + d.line(pos1,pos2,style.contactColor); + } else { + d.point(pos1,style.newContactColor); + d.point(pos2,style.newContactColor); + d.line(pos1,pos2,style.newContactColor); + } + pos2.x = pos1.x; + pos2.y = pos1.y; + pos2.z = pos1.z; + let _this5 = pos2; + let s = style.contactNormalLength; + _this5.x += normal.x * s; + _this5.y += normal.y * s; + _this5.z += normal.z * s; + d.line(pos1,pos2,style.contactNormalColor); + if(d.drawContactBases) { + pos2.x = pos1.x; + pos2.y = pos1.y; + pos2.z = pos1.z; + let _this = pos2; + let s = style.contactTangentLength; + _this.x += tangent.x * s; + _this.y += tangent.y * s; + _this.z += tangent.z * s; + d.line(pos1,pos2,style.contactTangentColor); + pos2.x = pos1.x; + pos2.y = pos1.y; + pos2.z = pos1.z; + let _this1 = pos2; + let s1 = style.contactBinormalLength; + _this1.x += binormal.x * s1; + _this1.y += binormal.y * s1; + _this1.z += binormal.z * s1; + d.line(pos1,pos2,style.contactBinormalColor); + } + let _this6 = this._pool; + if(pos1 != null) { + pos1.zero(); + if(_this6.sizeVec3 == _this6.stackVec3.length) { + let newArray = new Array(_this6.sizeVec3 << 1); + let _g = 0; + let _g1 = _this6.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this6.stackVec3[i]; + _this6.stackVec3[i] = null; + } + _this6.stackVec3 = newArray; + } + _this6.stackVec3[_this6.sizeVec3++] = pos1; + } + let _this7 = this._pool; + if(pos2 != null) { + pos2.zero(); + if(_this7.sizeVec3 == _this7.stackVec3.length) { + let newArray = new Array(_this7.sizeVec3 << 1); + let _g = 0; + let _g1 = _this7.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this7.stackVec3[i]; + _this7.stackVec3[i] = null; + } + _this7.stackVec3 = newArray; + } + _this7.stackVec3[_this7.sizeVec3++] = pos2; + } + let _this8 = this._pool; + if(normal != null) { + normal.zero(); + if(_this8.sizeVec3 == _this8.stackVec3.length) { + let newArray = new Array(_this8.sizeVec3 << 1); + let _g = 0; + let _g1 = _this8.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this8.stackVec3[i]; + _this8.stackVec3[i] = null; + } + _this8.stackVec3 = newArray; + } + _this8.stackVec3[_this8.sizeVec3++] = normal; + } + let _this9 = this._pool; + if(tangent != null) { + tangent.zero(); + if(_this9.sizeVec3 == _this9.stackVec3.length) { + let newArray = new Array(_this9.sizeVec3 << 1); + let _g = 0; + let _g1 = _this9.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackVec3[i]; + _this9.stackVec3[i] = null; + } + _this9.stackVec3 = newArray; + } + _this9.stackVec3[_this9.sizeVec3++] = tangent; + } + let _this10 = this._pool; + if(binormal != null) { + binormal.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = binormal; + } + } + } + c = n; + } + } + if(d.drawJoints) { + let j = this._jointList; + while(j != null) { + let n = j._next; + let _this = this._pool; + let p1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let p2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let v = p1; + v.x = j._b1._transform._positionX; + v.y = j._b1._transform._positionY; + v.z = j._b1._transform._positionZ; + let v1 = p2; + v1.x = j._b2._transform._positionX; + v1.y = j._b2._transform._positionY; + v1.z = j._b2._transform._positionZ; + let _this2 = this._pool; + let anchor1 = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this._pool; + let anchor2 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this._pool; + let basisX1 = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let _this5 = this._pool; + let basisY1 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + let _this6 = this._pool; + let basisZ1 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + let _this7 = this._pool; + let basisX2 = _this7.sizeVec3 == 0 ? new oimo.common.Vec3() : _this7.stackVec3[--_this7.sizeVec3]; + let _this8 = this._pool; + let basisY2 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + let _this9 = this._pool; + let basisZ2 = _this9.sizeVec3 == 0 ? new oimo.common.Vec3() : _this9.stackVec3[--_this9.sizeVec3]; + let v2 = anchor1; + v2.x = j._anchor1X; + v2.y = j._anchor1Y; + v2.z = j._anchor1Z; + let v3 = anchor2; + v3.x = j._anchor2X; + v3.y = j._anchor2Y; + v3.z = j._anchor2Z; + let v4 = basisX1; + v4.x = j._basisX1X; + v4.y = j._basisX1Y; + v4.z = j._basisX1Z; + let v5 = basisY1; + v5.x = j._basisY1X; + v5.y = j._basisY1Y; + v5.z = j._basisY1Z; + let v6 = basisZ1; + v6.x = j._basisZ1X; + v6.y = j._basisZ1Y; + v6.z = j._basisZ1Z; + let v7 = basisX2; + v7.x = j._basisX2X; + v7.y = j._basisX2Y; + v7.z = j._basisX2Z; + let v8 = basisY2; + v8.x = j._basisY2X; + v8.y = j._basisY2Y; + v8.z = j._basisY2Z; + let v9 = basisZ2; + v9.x = j._basisZ2X; + v9.y = j._basisZ2Y; + v9.z = j._basisZ2Z; + d.line(p1,anchor1,d.style.jointLineColor); + d.line(p2,anchor2,d.style.jointLineColor); + if(d.drawJointLimits) { + switch(j._type) { + case 0: + break; + case 1: + let lm = j._lm; + this._drawRotationalLimit(d,anchor1,basisY1,basisZ1,basisY2,d.style.jointRotationalConstraintRadius,lm.lowerLimit,lm.upperLimit,d.style.jointLineColor); + break; + case 2: + let j1 = j; + let color = d.style.jointLineColor; + let rlm = j1._rotLm; + let tlm = j1._translLm; + this._drawRotationalLimit(d,anchor2,basisY1,basisZ1,basisY2,d.style.jointRotationalConstraintRadius,rlm.lowerLimit,rlm.upperLimit,color); + this._drawTranslationalLimit(d,anchor1,basisX1,tlm.lowerLimit,tlm.upperLimit,color); + break; + case 3: + let lm1 = j._lm; + this._drawTranslationalLimit(d,anchor1,basisX1,lm1.lowerLimit,lm1.upperLimit,d.style.jointLineColor); + break; + case 4: + let j2 = j; + let radius = d.style.jointRotationalConstraintRadius; + let color1 = d.style.jointLineColor; + let lm11 = j2._lm1; + let lm2 = j2._lm2; + this._drawRotationalLimit(d,anchor1,basisY1,basisZ1,basisY1,radius,j2._angleX - lm11.upperLimit,j2._angleX - lm11.lowerLimit,color1); + this._drawRotationalLimit(d,anchor2,basisX2,basisY2,basisX2,radius,lm2.lowerLimit - j2._angleZ,lm2.upperLimit - j2._angleZ,color1); + break; + case 5: + let j3 = j; + let radius1 = d.style.jointRotationalConstraintRadius; + let color2 = d.style.jointLineColor; + let lm3 = j3._twistLm; + this._drawRotationalLimit(d,anchor2,basisY2,basisZ2,basisY2,radius1,lm3.lowerLimit - j3._twistAngle,lm3.upperLimit - j3._twistAngle,color2); + this._drawEllipseOnSphere(d,anchor1,basisX1,basisY1,basisZ1,j3._maxSwingAngle1,j3._maxSwingAngle2,radius1,color2); + let _this10 = this._pool; + let _this11 = _this10.sizeVec3 == 0 ? new oimo.common.Vec3() : _this10.stackVec3[--_this10.sizeVec3]; + _this11.x = anchor2.x; + _this11.y = anchor2.y; + _this11.z = anchor2.z; + let _this12 = _this11; + _this12.x += basisX2.x * radius1; + _this12.y += basisX2.y * radius1; + _this12.z += basisX2.z * radius1; + d.line(anchor2,_this12,color2); + let _this13 = this._pool; + if(_this12 != null) { + _this12.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = _this12; + } + break; + case 6: + let j4 = j; + let radius2 = d.style.jointRotationalConstraintRadius; + let color3 = d.style.jointLineColor; + let rxlm = j4._rotLms[0]; + let rylm = j4._rotLms[1]; + let rzlm = j4._rotLms[2]; + this._drawTranslationalLimit3D(d,anchor1,basisX1,basisY1,basisZ1,j4._translLms[0],j4._translLms[1],j4._translLms[2],color3); + let _this14 = this._pool; + let rotYAxis = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + let v10 = rotYAxis; + v10.x = j4._axisYX; + v10.y = j4._axisYY; + v10.z = j4._axisYZ; + let _this15 = this._pool; + let _this16 = _this15.sizeVec3 == 0 ? new oimo.common.Vec3() : _this15.stackVec3[--_this15.sizeVec3]; + _this16.x = basisX1.x; + _this16.y = basisX1.y; + _this16.z = basisX1.z; + let rotYBasisX = _this16; + let _this17 = this._pool; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = basisX1.x; + _this18.y = basisX1.y; + _this18.z = basisX1.z; + let _this19 = _this18; + let y = _this19.z * rotYAxis.x - _this19.x * rotYAxis.z; + let z = _this19.x * rotYAxis.y - _this19.y * rotYAxis.x; + _this19.x = _this19.y * rotYAxis.z - _this19.z * rotYAxis.y; + _this19.y = y; + _this19.z = z; + this._drawRotationalLimit(d,anchor2,basisY1,basisZ1,basisY1,radius2,j4._angleX - rxlm.upperLimit,j4._angleX - rxlm.lowerLimit,color3); + this._drawRotationalLimit(d,anchor2,rotYBasisX,_this19,rotYBasisX,radius2,rylm.lowerLimit - j4._angleY,rylm.upperLimit - j4._angleY,color3); + this._drawRotationalLimit(d,anchor2,basisX2,basisY2,basisX2,radius2,rzlm.lowerLimit - j4._angleZ,rzlm.upperLimit - j4._angleZ,color3); + break; + } + } + d.line(anchor1,anchor2,d.style.jointErrorColor); + let _this20 = this._pool; + if(p1 != null) { + p1.zero(); + if(_this20.sizeVec3 == _this20.stackVec3.length) { + let newArray = new Array(_this20.sizeVec3 << 1); + let _g = 0; + let _g1 = _this20.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this20.stackVec3[i]; + _this20.stackVec3[i] = null; + } + _this20.stackVec3 = newArray; + } + _this20.stackVec3[_this20.sizeVec3++] = p1; + } + let _this21 = this._pool; + if(p2 != null) { + p2.zero(); + if(_this21.sizeVec3 == _this21.stackVec3.length) { + let newArray = new Array(_this21.sizeVec3 << 1); + let _g = 0; + let _g1 = _this21.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this21.stackVec3[i]; + _this21.stackVec3[i] = null; + } + _this21.stackVec3 = newArray; + } + _this21.stackVec3[_this21.sizeVec3++] = p2; + } + let _this22 = this._pool; + if(anchor1 != null) { + anchor1.zero(); + if(_this22.sizeVec3 == _this22.stackVec3.length) { + let newArray = new Array(_this22.sizeVec3 << 1); + let _g = 0; + let _g1 = _this22.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this22.stackVec3[i]; + _this22.stackVec3[i] = null; + } + _this22.stackVec3 = newArray; + } + _this22.stackVec3[_this22.sizeVec3++] = anchor1; + } + let _this23 = this._pool; + if(anchor2 != null) { + anchor2.zero(); + if(_this23.sizeVec3 == _this23.stackVec3.length) { + let newArray = new Array(_this23.sizeVec3 << 1); + let _g = 0; + let _g1 = _this23.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this23.stackVec3[i]; + _this23.stackVec3[i] = null; + } + _this23.stackVec3 = newArray; + } + _this23.stackVec3[_this23.sizeVec3++] = anchor2; + } + let _this24 = this._pool; + if(basisX1 != null) { + basisX1.zero(); + if(_this24.sizeVec3 == _this24.stackVec3.length) { + let newArray = new Array(_this24.sizeVec3 << 1); + let _g = 0; + let _g1 = _this24.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this24.stackVec3[i]; + _this24.stackVec3[i] = null; + } + _this24.stackVec3 = newArray; + } + _this24.stackVec3[_this24.sizeVec3++] = basisX1; + } + let _this25 = this._pool; + if(basisY1 != null) { + basisY1.zero(); + if(_this25.sizeVec3 == _this25.stackVec3.length) { + let newArray = new Array(_this25.sizeVec3 << 1); + let _g = 0; + let _g1 = _this25.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this25.stackVec3[i]; + _this25.stackVec3[i] = null; + } + _this25.stackVec3 = newArray; + } + _this25.stackVec3[_this25.sizeVec3++] = basisY1; + } + let _this26 = this._pool; + if(basisZ1 != null) { + basisZ1.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = basisZ1; + } + let _this27 = this._pool; + if(basisX2 != null) { + basisX2.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = basisX2; + } + let _this28 = this._pool; + if(basisY2 != null) { + basisY2.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = basisY2; + } + let _this29 = this._pool; + if(basisZ2 != null) { + basisZ2.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = basisZ2; + } + j = n; + } + } + } + _drawRotationalLimit(d,center,ex,ey,needle,radius,min,max,color) { + if(min != max) { + let _this = this._pool; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = center.x; + _this1.y = center.y; + _this1.z = center.z; + let _this2 = _this1; + _this2.x += needle.x * radius; + _this2.y += needle.y * radius; + _this2.z += needle.z * radius; + d.line(center,_this2,color); + let _this3 = this._pool; + if(_this2 != null) { + _this2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = _this2; + } + if(min > max) { + d.ellipse(center,ex,ey,radius,radius,color); + } else { + d.arc(center,ex,ey,radius,radius,min,max,true,color); + } + } + } + _drawTranslationalLimit(d,center,ex,min,max,color) { + if(min < max) { + let _this = this._pool; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = center.x; + _this1.y = center.y; + _this1.z = center.z; + let _this2 = _this1; + _this2.x += ex.x * min; + _this2.y += ex.y * min; + _this2.z += ex.z * min; + let _this3 = this._pool; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = center.x; + _this4.y = center.y; + _this4.z = center.z; + let _this5 = _this4; + _this5.x += ex.x * max; + _this5.y += ex.y * max; + _this5.z += ex.z * max; + d.line(_this2,_this5,color); + let _this6 = this._pool; + if(_this2 != null) { + _this2.zero(); + if(_this6.sizeVec3 == _this6.stackVec3.length) { + let newArray = new Array(_this6.sizeVec3 << 1); + let _g = 0; + let _g1 = _this6.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this6.stackVec3[i]; + _this6.stackVec3[i] = null; + } + _this6.stackVec3 = newArray; + } + _this6.stackVec3[_this6.sizeVec3++] = _this2; + } + let _this7 = this._pool; + if(_this5 != null) { + _this5.zero(); + if(_this7.sizeVec3 == _this7.stackVec3.length) { + let newArray = new Array(_this7.sizeVec3 << 1); + let _g = 0; + let _g1 = _this7.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this7.stackVec3[i]; + _this7.stackVec3[i] = null; + } + _this7.stackVec3 = newArray; + } + _this7.stackVec3[_this7.sizeVec3++] = _this5; + } + } + } + _drawTranslationalLimit3D(d,center,ex,ey,ez,xlm,ylm,zlm,color) { + let minx = xlm.lowerLimit; + let maxx = xlm.upperLimit; + let miny = ylm.lowerLimit; + let maxy = ylm.upperLimit; + let minz = zlm.lowerLimit; + let maxz = zlm.upperLimit; + let _this = this._pool; + if(_this.sizeVec3 == 0) { + new oimo.common.Vec3(); + } else { + --_this.sizeVec3; + } + let _this1 = this._pool; + if(_this1.sizeVec3 == 0) { + new oimo.common.Vec3(); + } else { + --_this1.sizeVec3; + } + let _this2 = this._pool; + let _this3 = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + _this3.x = center.x; + _this3.y = center.y; + _this3.z = center.z; + let _this4 = _this3; + _this4.x += ex.x * minx; + _this4.y += ex.y * minx; + _this4.z += ex.z * minx; + _this4.x += ey.x * miny; + _this4.y += ey.y * miny; + _this4.z += ey.z * miny; + _this4.x += ez.x * minz; + _this4.y += ez.y * minz; + _this4.z += ez.z * minz; + let _this5 = this._pool; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = center.x; + _this6.y = center.y; + _this6.z = center.z; + let _this7 = _this6; + _this7.x += ex.x * minx; + _this7.y += ex.y * minx; + _this7.z += ex.z * minx; + _this7.x += ey.x * miny; + _this7.y += ey.y * miny; + _this7.z += ey.z * miny; + _this7.x += ez.x * maxz; + _this7.y += ez.y * maxz; + _this7.z += ez.z * maxz; + let _this8 = this._pool; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = center.x; + _this9.y = center.y; + _this9.z = center.z; + let _this10 = _this9; + _this10.x += ex.x * minx; + _this10.y += ex.y * minx; + _this10.z += ex.z * minx; + _this10.x += ey.x * maxy; + _this10.y += ey.y * maxy; + _this10.z += ey.z * maxy; + _this10.x += ez.x * minz; + _this10.y += ez.y * minz; + _this10.z += ez.z * minz; + let _this11 = this._pool; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = center.x; + _this12.y = center.y; + _this12.z = center.z; + let _this13 = _this12; + _this13.x += ex.x * minx; + _this13.y += ex.y * minx; + _this13.z += ex.z * minx; + _this13.x += ey.x * maxy; + _this13.y += ey.y * maxy; + _this13.z += ey.z * maxy; + _this13.x += ez.x * maxz; + _this13.y += ez.y * maxz; + _this13.z += ez.z * maxz; + let _this14 = this._pool; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = center.x; + _this15.y = center.y; + _this15.z = center.z; + let _this16 = _this15; + _this16.x += ex.x * maxx; + _this16.y += ex.y * maxx; + _this16.z += ex.z * maxx; + _this16.x += ey.x * miny; + _this16.y += ey.y * miny; + _this16.z += ey.z * miny; + _this16.x += ez.x * minz; + _this16.y += ez.y * minz; + _this16.z += ez.z * minz; + let _this17 = this._pool; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = center.x; + _this18.y = center.y; + _this18.z = center.z; + let _this19 = _this18; + _this19.x += ex.x * maxx; + _this19.y += ex.y * maxx; + _this19.z += ex.z * maxx; + _this19.x += ey.x * miny; + _this19.y += ey.y * miny; + _this19.z += ey.z * miny; + _this19.x += ez.x * maxz; + _this19.y += ez.y * maxz; + _this19.z += ez.z * maxz; + let _this20 = this._pool; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = center.x; + _this21.y = center.y; + _this21.z = center.z; + let _this22 = _this21; + _this22.x += ex.x * maxx; + _this22.y += ex.y * maxx; + _this22.z += ex.z * maxx; + _this22.x += ey.x * maxy; + _this22.y += ey.y * maxy; + _this22.z += ey.z * maxy; + _this22.x += ez.x * minz; + _this22.y += ez.y * minz; + _this22.z += ez.z * minz; + let _this23 = this._pool; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = center.x; + _this24.y = center.y; + _this24.z = center.z; + let _this25 = _this24; + _this25.x += ex.x * maxx; + _this25.y += ex.y * maxx; + _this25.z += ex.z * maxx; + _this25.x += ey.x * maxy; + _this25.y += ey.y * maxy; + _this25.z += ey.z * maxy; + _this25.x += ez.x * maxz; + _this25.y += ez.y * maxz; + _this25.z += ez.z * maxz; + d.line(_this4,_this16,color); + d.line(_this10,_this22,color); + d.line(_this7,_this19,color); + d.line(_this13,_this25,color); + d.line(_this4,_this10,color); + d.line(_this16,_this22,color); + d.line(_this7,_this13,color); + d.line(_this19,_this25,color); + d.line(_this4,_this7,color); + d.line(_this16,_this19,color); + d.line(_this10,_this13,color); + d.line(_this22,_this25,color); + let _this26 = this._pool; + if(_this4 != null) { + _this4.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = _this4; + } + let _this27 = this._pool; + if(_this7 != null) { + _this7.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = _this7; + } + let _this28 = this._pool; + if(_this10 != null) { + _this10.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = _this10; + } + let _this29 = this._pool; + if(_this13 != null) { + _this13.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this13; + } + let _this30 = this._pool; + if(_this16 != null) { + _this16.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this16; + } + let _this31 = this._pool; + if(_this19 != null) { + _this19.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this19; + } + let _this32 = this._pool; + if(_this22 != null) { + _this22.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this22; + } + let _this33 = this._pool; + if(_this25 != null) { + _this25.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this25; + } + } + _drawEllipseOnSphere(d,center,normal,x,y,radiansX,radiansY,radius,color) { + let theta = 0; + let _this = this._pool; + let rotVec = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this._pool; + let rotQ = _this1.sizeQuat == 0 ? new oimo.common.Quat() : _this1.stackQuat[--_this1.sizeQuat]; + let _this2 = this._pool; + let rotM = _this2.sizeMat3 == 0 ? new oimo.common.Mat3() : _this2.stackMat3[--_this2.sizeMat3]; + let _this3 = this._pool; + let prevV = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _g = 0; + while(_g < 17) { + let i = _g++; + let rx = Math.cos(theta) * radiansX; + let ry = Math.sin(theta) * radiansY; + let halfRotAng = Math.sqrt(rx * rx + ry * ry); + let rotSin = Math.sin(halfRotAng * 0.5); + let rotCos = Math.cos(halfRotAng * 0.5); + let _this = rotVec.zero(); + _this.x += x.x * rx; + _this.y += x.y * rx; + _this.z += x.z * rx; + _this.x += y.x * ry; + _this.y += y.y * ry; + _this.z += y.z * ry; + let s = 1 / halfRotAng * rotSin; + rotVec.x *= s; + rotVec.y *= s; + rotVec.z *= s; + rotQ.x = rotVec.x; + rotQ.y = rotVec.y; + rotQ.z = rotVec.z; + rotQ.w = rotCos; + let x1 = rotQ.x; + let y1 = rotQ.y; + let z = rotQ.z; + let w = rotQ.w; + let x2 = 2 * x1; + let y2 = 2 * y1; + let z2 = 2 * z; + let xx = x1 * x2; + let yy = y1 * y2; + let zz = z * z2; + let xy = x1 * y2; + let yz = y1 * z2; + let xz = x1 * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + rotM.e00 = 1 - yy - zz; + rotM.e01 = xy - wz; + rotM.e02 = xz + wy; + rotM.e10 = xy + wz; + rotM.e11 = 1 - xx - zz; + rotM.e12 = yz - wx; + rotM.e20 = xz - wy; + rotM.e21 = yz + wx; + rotM.e22 = 1 - xx - yy; + let _this1 = this._pool; + let _this2 = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + _this2.x += normal.x * radius; + _this2.y += normal.y * radius; + _this2.z += normal.z * radius; + let v = _this2; + let y3 = v.x * rotM.e10 + v.y * rotM.e11 + v.z * rotM.e12; + let z1 = v.x * rotM.e20 + v.y * rotM.e21 + v.z * rotM.e22; + v.x = v.x * rotM.e00 + v.y * rotM.e01 + v.z * rotM.e02; + v.y = y3; + v.z = z1; + v.x += center.x; + v.y += center.y; + v.z += center.z; + if(i >= 1) { + d.line(prevV,v,color); + } + let _this3 = this._pool; + if(prevV != null) { + prevV.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = prevV; + } + prevV = v; + theta += 0.39269908169872375; + } + let _this4 = this._pool; + if(rotVec != null) { + rotVec.zero(); + if(_this4.sizeVec3 == _this4.stackVec3.length) { + let newArray = new Array(_this4.sizeVec3 << 1); + let _g = 0; + let _g1 = _this4.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this4.stackVec3[i]; + _this4.stackVec3[i] = null; + } + _this4.stackVec3 = newArray; + } + _this4.stackVec3[_this4.sizeVec3++] = rotVec; + } + let _this5 = this._pool; + if(rotQ != null) { + rotQ.x = 0; + rotQ.y = 0; + rotQ.z = 0; + rotQ.w = 1; + if(_this5.sizeQuat == _this5.stackQuat.length) { + let newArray = new Array(_this5.sizeQuat << 1); + let _g = 0; + let _g1 = _this5.sizeQuat; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this5.stackQuat[i]; + _this5.stackQuat[i] = null; + } + _this5.stackQuat = newArray; + } + _this5.stackQuat[_this5.sizeQuat++] = rotQ; + } + let _this6 = this._pool; + if(rotM != null) { + rotM.e00 = 1; + rotM.e01 = 0; + rotM.e02 = 0; + rotM.e10 = 0; + rotM.e11 = 1; + rotM.e12 = 0; + rotM.e20 = 0; + rotM.e21 = 0; + rotM.e22 = 1; + if(_this6.sizeMat3 == _this6.stackMat3.length) { + let newArray = new Array(_this6.sizeMat3 << 1); + let _g = 0; + let _g1 = _this6.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this6.stackMat3[i]; + _this6.stackMat3[i] = null; + } + _this6.stackMat3 = newArray; + } + _this6.stackMat3[_this6.sizeMat3++] = rotM; + } + let _this7 = this._pool; + if(prevV != null) { + prevV.zero(); + if(_this7.sizeVec3 == _this7.stackVec3.length) { + let newArray = new Array(_this7.sizeVec3 << 1); + let _g = 0; + let _g1 = _this7.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this7.stackVec3[i]; + _this7.stackVec3[i] = null; + } + _this7.stackVec3 = newArray; + } + _this7.stackVec3[_this7.sizeVec3++] = prevV; + } + } + step(timeStep) { + if(this._timeStep.dt > 0) { + this._timeStep.dtRatio = timeStep / this._timeStep.dt; + } + this._timeStep.dt = timeStep; + this._timeStep.invDt = 1 / timeStep; + let st = Date.now() / 1000; + this._updateContacts(); + this._solveIslands(); + oimo.dynamics.common.Performance.totalTime = (Date.now() / 1000 - st) * 1000; + } + addRigidBody(rigidBody) { + if(rigidBody._world != null) { + throw new Error("A rigid body cannot belong to multiple worlds."); + } + if(this._rigidBodyList == null) { + this._rigidBodyList = rigidBody; + this._rigidBodyListLast = rigidBody; + } else { + this._rigidBodyListLast._next = rigidBody; + rigidBody._prev = this._rigidBodyListLast; + this._rigidBodyListLast = rigidBody; + } + rigidBody._world = this; + let s = rigidBody._shapeList; + while(s != null) { + let n = s._next; + s._proxy = this._broadPhase.createProxy(s,s._aabb); + s._id = this._shapeIdCount++; + this._numShapes++; + s = n; + } + this._numRigidBodies++; + } + removeRigidBody(rigidBody) { + if(rigidBody._world != this) { + throw new Error("The rigid body doesn't belong to the world."); + } + let prev = rigidBody._prev; + let next = rigidBody._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(rigidBody == this._rigidBodyList) { + this._rigidBodyList = this._rigidBodyList._next; + } + if(rigidBody == this._rigidBodyListLast) { + this._rigidBodyListLast = this._rigidBodyListLast._prev; + } + rigidBody._next = null; + rigidBody._prev = null; + rigidBody._world = null; + let s = rigidBody._shapeList; + while(s != null) { + let n = s._next; + this._broadPhase.destroyProxy(s._proxy); + s._proxy = null; + s._id = -1; + let cl = s._rigidBody._contactLinkList; + while(cl != null) { + let n = cl._next; + let c = cl._contact; + if(c._s1 == s || c._s2 == s) { + let _this = cl._other; + _this._sleeping = false; + _this._sleepTime = 0; + let _this1 = this._contactManager; + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == _this1._contactList) { + _this1._contactList = _this1._contactList._next; + } + if(c == _this1._contactListLast) { + _this1._contactListLast = _this1._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this2 = c._contactConstraint; + _this2._s1 = null; + _this2._s2 = null; + _this2._b1 = null; + _this2._b2 = null; + _this2._tf1 = null; + _this2._tf2 = null; + c._next = _this1._contactPool; + _this1._contactPool = c; + _this1._numContacts--; + } + cl = n; + } + this._numShapes--; + s = n; + } + this._numRigidBodies--; + } + addJoint(joint) { + if(joint._world != null) { + throw new Error("A joint cannot belong to multiple worlds."); + } + if(this._jointList == null) { + this._jointList = joint; + this._jointListLast = joint; + } else { + this._jointListLast._next = joint; + joint._prev = this._jointListLast; + this._jointListLast = joint; + } + joint._world = this; + joint._link1._other = joint._b2; + joint._link2._other = joint._b1; + if(joint._b1._jointLinkList == null) { + joint._b1._jointLinkList = joint._link1; + joint._b1._jointLinkListLast = joint._link1; + } else { + joint._b1._jointLinkListLast._next = joint._link1; + joint._link1._prev = joint._b1._jointLinkListLast; + joint._b1._jointLinkListLast = joint._link1; + } + if(joint._b2._jointLinkList == null) { + joint._b2._jointLinkList = joint._link2; + joint._b2._jointLinkListLast = joint._link2; + } else { + joint._b2._jointLinkListLast._next = joint._link2; + joint._link2._prev = joint._b2._jointLinkListLast; + joint._b2._jointLinkListLast = joint._link2; + } + joint._b1._numJointLinks++; + joint._b2._numJointLinks++; + let _this = joint._b1; + _this._sleeping = false; + _this._sleepTime = 0; + let _this1 = joint._b2; + _this1._sleeping = false; + _this1._sleepTime = 0; + joint._syncAnchors(); + this._numJoints++; + } + removeJoint(joint) { + if(joint._world != this) { + throw new Error("The joint doesn't belong to the world."); + } + let prev = joint._prev; + let next = joint._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(joint == this._jointList) { + this._jointList = this._jointList._next; + } + if(joint == this._jointListLast) { + this._jointListLast = this._jointListLast._prev; + } + joint._next = null; + joint._prev = null; + joint._world = null; + let prev1 = joint._link1._prev; + let next1 = joint._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(joint._link1 == joint._b1._jointLinkList) { + joint._b1._jointLinkList = joint._b1._jointLinkList._next; + } + if(joint._link1 == joint._b1._jointLinkListLast) { + joint._b1._jointLinkListLast = joint._b1._jointLinkListLast._prev; + } + joint._link1._next = null; + joint._link1._prev = null; + let prev2 = joint._link2._prev; + let next2 = joint._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(joint._link2 == joint._b2._jointLinkList) { + joint._b2._jointLinkList = joint._b2._jointLinkList._next; + } + if(joint._link2 == joint._b2._jointLinkListLast) { + joint._b2._jointLinkListLast = joint._b2._jointLinkListLast._prev; + } + joint._link2._next = null; + joint._link2._prev = null; + joint._link1._other = null; + joint._link2._other = null; + joint._b1._numJointLinks--; + joint._b2._numJointLinks--; + let _this = joint._b1; + _this._sleeping = false; + _this._sleepTime = 0; + let _this1 = joint._b2; + _this1._sleeping = false; + _this1._sleepTime = 0; + this._numJoints--; + } + setDebugDraw(debugDraw) { + this._debugDraw = debugDraw; + } + getDebugDraw() { + return this._debugDraw; + } + debugDraw() { + if(this._debugDraw != null) { + if(this._broadPhase._type == 2) { + this._drawBvh(this._debugDraw,this._broadPhase._tree); + } + this._drawRigidBodies(this._debugDraw); + this._drawConstraints(this._debugDraw); + } + } + rayCast(begin,end,callback) { + let _this = this._rayCastWrapper.begin; + _this.x = begin.x; + _this.y = begin.y; + _this.z = begin.z; + let _this1 = this._rayCastWrapper.end; + _this1.x = end.x; + _this1.y = end.y; + _this1.z = end.z; + this._rayCastWrapper.callback = callback; + this._broadPhase.rayCast(begin,end,this._rayCastWrapper); + } + convexCast(convex,begin,translation,callback) { + this._convexCastWrapper.convex = convex; + let _this = this._convexCastWrapper.begin; + _this._positionX = begin._positionX; + _this._positionY = begin._positionY; + _this._positionZ = begin._positionZ; + _this._rotation00 = begin._rotation00; + _this._rotation01 = begin._rotation01; + _this._rotation02 = begin._rotation02; + _this._rotation10 = begin._rotation10; + _this._rotation11 = begin._rotation11; + _this._rotation12 = begin._rotation12; + _this._rotation20 = begin._rotation20; + _this._rotation21 = begin._rotation21; + _this._rotation22 = begin._rotation22; + let _this1 = this._convexCastWrapper.translation; + _this1.x = translation.x; + _this1.y = translation.y; + _this1.z = translation.z; + this._convexCastWrapper.callback = callback; + this._broadPhase.convexCast(convex,begin,translation,this._convexCastWrapper); + } + aabbTest(aabb,callback) { + this._aabbTestWrapper._aabb.copyFrom(aabb); + this._aabbTestWrapper._callback = callback; + this._broadPhase.aabbTest(aabb,this._aabbTestWrapper); + } + getRigidBodyList() { + return this._rigidBodyList; + } + getJointList() { + return this._jointList; + } + getBroadPhase() { + return this._broadPhase; + } + getContactManager() { + return this._contactManager; + } + getNumRigidBodies() { + return this._numRigidBodies; + } + getNumJoints() { + return this._numJoints; + } + getNumShapes() { + return this._numShapes; + } + getNumIslands() { + return this._numIslands; + } + getNumVelocityIterations() { + return this._numVelocityIterations; + } + setNumVelocityIterations(numVelocityIterations) { + this._numVelocityIterations = numVelocityIterations; + } + getNumPositionIterations() { + return this._numPositionIterations; + } + setNumPositionIterations(numPositionIterations) { + this._numPositionIterations = numPositionIterations; + } + getGravity() { + return this._gravity; + } + setGravity(gravity) { + let _this = this._gravity; + _this.x = gravity.x; + _this.y = gravity.y; + _this.z = gravity.z; + } +} +if(!oimo.dynamics._World) oimo.dynamics._World = {}; +oimo.dynamics._World.RayCastWrapper = class oimo_dynamics__$World_RayCastWrapper extends oimo.collision.broadphase.BroadPhaseProxyCallback { + constructor() { + super(); + this.rayCastHit = new oimo.collision.geometry.RayCastHit(); + this.begin = new oimo.common.Vec3(); + this.end = new oimo.common.Vec3(); + this.callback = null; + } + process(proxy) { + let shape = proxy.userData; + if(shape._geom.rayCast(this.begin,this.end,shape._transform,this.rayCastHit)) { + this.callback.process(shape,this.rayCastHit); + } + } +} +oimo.dynamics._World.ConvexCastWrapper = class oimo_dynamics__$World_ConvexCastWrapper extends oimo.collision.broadphase.BroadPhaseProxyCallback { + constructor() { + super(); + this.rayCastHit = new oimo.collision.geometry.RayCastHit(); + this.begin = new oimo.common.Transform(); + this.translation = new oimo.common.Vec3(); + this.zero = new oimo.common.Vec3(); + this.callback = null; + this.convex = null; + } + process(proxy) { + let shape = proxy.userData; + let type = shape._geom._type; + if(type < 0 || type > 5) { + return; + } + if(oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance.convexCast(this.convex,shape._geom,this.begin,shape._transform,this.translation,this.zero,this.rayCastHit)) { + this.callback.process(shape,this.rayCastHit); + } + } +} +oimo.dynamics._World.AabbTestWrapper = class oimo_dynamics__$World_AabbTestWrapper extends oimo.collision.broadphase.BroadPhaseProxyCallback { + constructor() { + super(); + this._aabb = new oimo.collision.geometry.Aabb(); + this._callback = null; + } + process(proxy) { + let shape = proxy.userData; + let shapeAabb = shape._aabb; + if(shapeAabb._minX < this._aabb._maxX && shapeAabb._maxX > this._aabb._minX && shapeAabb._minY < this._aabb._maxY && shapeAabb._maxY > this._aabb._minY && shapeAabb._minZ < this._aabb._maxZ && shapeAabb._maxZ > this._aabb._minZ) { + this._callback.process(shape); + } + } +} +if(!oimo.dynamics.callback) oimo.dynamics.callback = {}; +oimo.dynamics.callback.AabbTestCallback = class oimo_dynamics_callback_AabbTestCallback { + constructor() { + } + process(shape) { + } +} +oimo.dynamics.callback.ContactCallback = class oimo_dynamics_callback_ContactCallback { + constructor() { + } + beginContact(c) { + } + preSolve(c) { + } + postSolve(c) { + } + endContact(c) { + } +} +oimo.dynamics.callback.RayCastCallback = class oimo_dynamics_callback_RayCastCallback { + constructor() { + } + process(shape,hit) { + } +} +oimo.dynamics.callback.RayCastClosest = class oimo_dynamics_callback_RayCastClosest extends oimo.dynamics.callback.RayCastCallback { + constructor() { + super(); + this.position = new oimo.common.Vec3(); + this.normal = new oimo.common.Vec3(); + this.shape = null; + this.fraction = 1; + this.position.zero(); + this.normal.zero(); + this.hit = false; + } + clear() { + this.shape = null; + this.fraction = 1; + this.position.zero(); + this.normal.zero(); + this.hit = false; + } + process(shape,hit) { + if(hit.fraction < this.fraction) { + this.shape = shape; + this.hit = true; + this.fraction = hit.fraction; + let _this = this.position; + let v = hit.position; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let _this1 = this.normal; + let v1 = hit.normal; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + } + } +} +if(!oimo.dynamics.common) oimo.dynamics.common = {}; +oimo.dynamics.common.DebugDraw = class oimo_dynamics_common_DebugDraw { + constructor() { + this.p = new oimo.common.Pool(); + this.wireframe = false; + this.drawShapes = true; + this.drawBvh = false; + this.drawBvhMinLevel = 0; + this.drawBvhMaxLevel = 65536; + this.drawAabbs = false; + this.drawBases = false; + this.drawPairs = false; + this.drawContacts = false; + this.drawJoints = true; + this.drawJointLimits = false; + this.sphereCoords = new Array(5); + this.tmpSphereVerts = new Array(5); + this.tmpSphereNorms = new Array(5); + let _g = 0; + while(_g < 5) { + let i = _g++; + let num = i == 0 || i == 4 ? 1 : 8; + this.sphereCoords[i] = new Array(num); + this.tmpSphereVerts[i] = new Array(num); + this.tmpSphereNorms[i] = new Array(num); + let _g1 = 0; + while(_g1 < 8) { + let j = _g1++; + let theta = i * 0.7853981633974475; + let phi = j * 0.7853981633974475; + this.sphereCoords[i][j] = new oimo.common.Vec3(Math.sin(theta) * Math.cos(phi),Math.cos(theta),-Math.sin(theta) * Math.sin(phi)); + this.tmpSphereVerts[i][j] = new oimo.common.Vec3(); + this.tmpSphereNorms[i][j] = new oimo.common.Vec3(); + } + } + this.circleCoords = new Array(8); + this.circleCoordsShift = new Array(8); + this.tmpCircleVerts1 = new Array(8); + this.tmpCircleVerts2 = new Array(8); + this.tmpCircleNorms = new Array(8); + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + this.circleCoords[i] = new oimo.common.Vec3(Math.cos(i * 0.7853981633974475),0,-Math.sin(i * 0.7853981633974475)); + this.circleCoordsShift[i] = new oimo.common.Vec3(Math.cos((i + 0.5) * 0.7853981633974475),0,-Math.sin((i + 0.5) * 0.7853981633974475)); + this.tmpCircleVerts1[i] = new oimo.common.Vec3(); + this.tmpCircleVerts2[i] = new oimo.common.Vec3(); + this.tmpCircleNorms[i] = new oimo.common.Vec3(); + } + this.style = new oimo.dynamics.common.DebugDrawStyle(); + } + aabb(min,max,color) { + let _this = this.p; + let v1 = (_this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]).init(min.x,min.y,min.z); + let _this1 = this.p; + let v2 = (_this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]).init(min.x,min.y,max.z); + let _this2 = this.p; + let v3 = (_this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]).init(min.x,max.y,min.z); + let _this3 = this.p; + let v4 = (_this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]).init(min.x,max.y,max.z); + let _this4 = this.p; + let v5 = (_this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]).init(max.x,min.y,min.z); + let _this5 = this.p; + let v6 = (_this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]).init(max.x,min.y,max.z); + let _this6 = this.p; + let v7 = (_this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]).init(max.x,max.y,min.z); + let _this7 = this.p; + let v8 = (_this7.sizeVec3 == 0 ? new oimo.common.Vec3() : _this7.stackVec3[--_this7.sizeVec3]).init(max.x,max.y,max.z); + this.line(v1,v2,color); + this.line(v3,v4,color); + this.line(v5,v6,color); + this.line(v7,v8,color); + this.line(v1,v3,color); + this.line(v2,v4,color); + this.line(v5,v7,color); + this.line(v6,v8,color); + this.line(v1,v5,color); + this.line(v2,v6,color); + this.line(v3,v7,color); + this.line(v4,v8,color); + let _this8 = this.p; + if(v1 != null) { + v1.zero(); + if(_this8.sizeVec3 == _this8.stackVec3.length) { + let newArray = new Array(_this8.sizeVec3 << 1); + let _g = 0; + let _g1 = _this8.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this8.stackVec3[i]; + _this8.stackVec3[i] = null; + } + _this8.stackVec3 = newArray; + } + _this8.stackVec3[_this8.sizeVec3++] = v1; + } + let _this9 = this.p; + if(v2 != null) { + v2.zero(); + if(_this9.sizeVec3 == _this9.stackVec3.length) { + let newArray = new Array(_this9.sizeVec3 << 1); + let _g = 0; + let _g1 = _this9.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackVec3[i]; + _this9.stackVec3[i] = null; + } + _this9.stackVec3 = newArray; + } + _this9.stackVec3[_this9.sizeVec3++] = v2; + } + let _this10 = this.p; + if(v3 != null) { + v3.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = v3; + } + let _this11 = this.p; + if(v4 != null) { + v4.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = v4; + } + let _this12 = this.p; + if(v5 != null) { + v5.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = v5; + } + let _this13 = this.p; + if(v6 != null) { + v6.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = v6; + } + let _this14 = this.p; + if(v7 != null) { + v7.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = v7; + } + let _this15 = this.p; + if(v8 != null) { + v8.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = v8; + } + } + basis(transform,length,colorX,colorY,colorZ) { + let _this = this.p; + let pos = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let rot = _this1.sizeMat3 == 0 ? new oimo.common.Mat3() : _this1.stackMat3[--_this1.sizeMat3]; + let _this2 = this.p; + let ex = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let ey = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let ez = _this4.sizeVec3 == 0 ? new oimo.common.Vec3() : _this4.stackVec3[--_this4.sizeVec3]; + let v = pos; + v.x = transform._positionX; + v.y = transform._positionY; + v.z = transform._positionZ; + let m = rot; + m.e00 = transform._rotation00; + m.e01 = transform._rotation01; + m.e02 = transform._rotation02; + m.e10 = transform._rotation10; + m.e11 = transform._rotation11; + m.e12 = transform._rotation12; + m.e20 = transform._rotation20; + m.e21 = transform._rotation21; + m.e22 = transform._rotation22; + ex.init(rot.e00,rot.e10,rot.e20); + ey.init(rot.e01,rot.e11,rot.e21); + ez.init(rot.e02,rot.e12,rot.e22); + ex.x *= length; + ex.y *= length; + ex.z *= length; + let _this5 = ex; + _this5.x += pos.x; + _this5.y += pos.y; + _this5.z += pos.z; + ey.x *= length; + ey.y *= length; + ey.z *= length; + let _this6 = ey; + _this6.x += pos.x; + _this6.y += pos.y; + _this6.z += pos.z; + ez.x *= length; + ez.y *= length; + ez.z *= length; + let _this7 = ez; + _this7.x += pos.x; + _this7.y += pos.y; + _this7.z += pos.z; + this.line(pos,ex,colorX); + this.line(pos,ey,colorY); + this.line(pos,ez,colorZ); + let _this8 = this.p; + if(pos != null) { + pos.zero(); + if(_this8.sizeVec3 == _this8.stackVec3.length) { + let newArray = new Array(_this8.sizeVec3 << 1); + let _g = 0; + let _g1 = _this8.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this8.stackVec3[i]; + _this8.stackVec3[i] = null; + } + _this8.stackVec3 = newArray; + } + _this8.stackVec3[_this8.sizeVec3++] = pos; + } + let _this9 = this.p; + if(rot != null) { + rot.e00 = 1; + rot.e01 = 0; + rot.e02 = 0; + rot.e10 = 0; + rot.e11 = 1; + rot.e12 = 0; + rot.e20 = 0; + rot.e21 = 0; + rot.e22 = 1; + if(_this9.sizeMat3 == _this9.stackMat3.length) { + let newArray = new Array(_this9.sizeMat3 << 1); + let _g = 0; + let _g1 = _this9.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackMat3[i]; + _this9.stackMat3[i] = null; + } + _this9.stackMat3 = newArray; + } + _this9.stackMat3[_this9.sizeMat3++] = rot; + } + let _this10 = this.p; + if(ex != null) { + ex.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = ex; + } + let _this11 = this.p; + if(ey != null) { + ey.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = ey; + } + let _this12 = this.p; + if(ez != null) { + ez.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = ez; + } + } + ellipse(center,ex,ey,radiusX,radiusY,color) { + this.arc(center,ex,ey,radiusX,radiusY,0,6.28318530717958,false,color); + } + arc(center,ex,ey,radiusX,radiusY,startAngle,endAngle,drawSector,color) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ex.x; + _this1.y = ex.y; + _this1.z = ex.z; + let _this2 = _this1; + _this2.x *= radiusX; + _this2.y *= radiusX; + _this2.z *= radiusX; + + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = ey.x; + _this4.y = ey.y; + _this4.z = ey.z; + let _this5 = _this4; + _this5.x *= radiusY; + _this5.y *= radiusY; + _this5.z *= radiusY; + + let angDiff = endAngle - startAngle; + if(angDiff < 0) { + angDiff = -angDiff; + } + let n = angDiff / 0.52359877559829837 + 0.5 | 0; + if(n == 0) { + n = 1; + } + let theta = startAngle; + let dt = (endAngle - startAngle) / n; + let _this6 = this.p; + let _this7 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this7.x = center.x; + _this7.y = center.y; + _this7.z = center.z; + let _this8 = _this7; + let s = Math.cos(startAngle); + _this8.x += _this2.x * s; + _this8.y += _this2.y * s; + _this8.z += _this2.z * s; + let s1 = Math.sin(startAngle); + _this8.x += _this5.x * s1; + _this8.y += _this5.y * s1; + _this8.z += _this5.z * s1; + let prevV = _this8; + if(drawSector) { + this.line(center,_this8,color); + } + let _g = 0; + let _g1 = n; + while(_g < _g1) { + ++_g; + theta += dt; + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = center.x; + _this1.y = center.y; + _this1.z = center.z; + let _this3 = _this1; + let s = Math.cos(theta); + _this3.x += _this2.x * s; + _this3.y += _this2.y * s; + _this3.z += _this2.z * s; + let s1 = Math.sin(theta); + _this3.x += _this5.x * s1; + _this3.y += _this5.y * s1; + _this3.z += _this5.z * s1; + this.line(prevV,_this3,color); + let _this4 = this.p; + if(prevV != null) { + prevV.zero(); + if(_this4.sizeVec3 == _this4.stackVec3.length) { + let newArray = new Array(_this4.sizeVec3 << 1); + let _g = 0; + let _g1 = _this4.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this4.stackVec3[i]; + _this4.stackVec3[i] = null; + } + _this4.stackVec3 = newArray; + } + _this4.stackVec3[_this4.sizeVec3++] = prevV; + } + prevV = _this3; + } + if(drawSector) { + this.line(center,prevV,color); + } + let _this9 = this.p; + if(prevV != null) { + prevV.zero(); + if(_this9.sizeVec3 == _this9.stackVec3.length) { + let newArray = new Array(_this9.sizeVec3 << 1); + let _g = 0; + let _g1 = _this9.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this9.stackVec3[i]; + _this9.stackVec3[i] = null; + } + _this9.stackVec3 = newArray; + } + _this9.stackVec3[_this9.sizeVec3++] = prevV; + } + let _this10 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this10.sizeVec3 == _this10.stackVec3.length) { + let newArray = new Array(_this10.sizeVec3 << 1); + let _g = 0; + let _g1 = _this10.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this10.stackVec3[i]; + _this10.stackVec3[i] = null; + } + _this10.stackVec3 = newArray; + } + _this10.stackVec3[_this10.sizeVec3++] = _this2; + } + let _this11 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this5; + } + } + cone(tf,radius,halfHeight,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + _this7.x += ey.x * halfHeight; + _this7.y += ey.y * halfHeight; + _this7.z += ey.z * halfHeight; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s = -halfHeight; + _this10.x += ey.x * s; + _this10.y += ey.y * s; + _this10.z += ey.z * s; + if(this.wireframe) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = _this10.x; + _this1.y = _this10.y; + _this1.z = _this10.z; + let _this2 = _this1; + let s = -radius; + _this2.x += ex.x * s; + _this2.y += ex.y * s; + _this2.z += ex.z * s; + _this2.x += ez.x * 0; + _this2.y += ez.y * 0; + _this2.z += ez.z * 0; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = _this10.x; + _this4.y = _this10.y; + _this4.z = _this10.z; + let _this5 = _this4; + _this5.x += ex.x * radius; + _this5.y += ex.y * radius; + _this5.z += ex.z * radius; + _this5.x += ez.x * 0; + _this5.y += ez.y * 0; + _this5.z += ez.z * 0; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = _this10.x; + _this8.y = _this10.y; + _this8.z = _this10.z; + let _this9 = _this8; + _this9.x += ex.x * 0; + _this9.y += ex.y * 0; + _this9.z += ex.z * 0; + let s1 = -radius; + _this9.x += ez.x * s1; + _this9.y += ez.y * s1; + _this9.z += ez.z * s1; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = _this10.x; + _this12.y = _this10.y; + _this12.z = _this10.z; + let _this13 = _this12; + _this13.x += ex.x * 0; + _this13.y += ex.y * 0; + _this13.z += ex.z * 0; + _this13.x += ez.x * radius; + _this13.y += ez.y * radius; + _this13.z += ez.z * radius; + this.ellipse(_this10,ex,ez,radius,radius,color); + this.line(_this7,_this2,color); + this.line(_this7,_this5,color); + this.line(_this7,_this9,color); + this.line(_this7,_this13,color); + let _this14 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = _this2; + } + let _this15 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = _this5; + } + let _this16 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = _this9; + } + let _this17 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = _this13; + } + } else { + let invDenom = 1 / Math.sqrt(radius * radius + 4 * halfHeight * halfHeight); + let cos = 2 * halfHeight * invDenom; + let sin = radius * invDenom; + let _g = 0; + while(_g < 8) { + let i = _g++; + let _this = this.tmpCircleNorms[i]; + let v = this.circleCoords[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x *= cos; + _this.y *= cos; + _this.z *= cos; + _this.y += sin; + let _this1 = this.tmpCircleNorms[i]; + let y = _this1.x * m.e10 + _this1.y * m.e11 + _this1.z * m.e12; + let z = _this1.x * m.e20 + _this1.y * m.e21 + _this1.z * m.e22; + _this1.x = _this1.x * m.e00 + _this1.y * m.e01 + _this1.z * m.e02; + _this1.y = y; + _this1.z = z; + let _this2 = this.tmpCircleVerts1[i]; + let v1 = this.circleCoordsShift[i]; + _this2.x = v1.x; + _this2.y = v1.y; + _this2.z = v1.z; + _this2.x *= cos; + _this2.y *= cos; + _this2.z *= cos; + _this2.y += sin; + let _this3 = this.tmpCircleVerts1[i]; + let y1 = _this3.x * m.e10 + _this3.y * m.e11 + _this3.z * m.e12; + let z1 = _this3.x * m.e20 + _this3.y * m.e21 + _this3.z * m.e22; + _this3.x = _this3.x * m.e00 + _this3.y * m.e01 + _this3.z * m.e02; + _this3.y = y1; + _this3.z = z1; + let _this4 = this.tmpCircleVerts2[i]; + let v2 = this.circleCoords[i]; + _this4.x = v2.x; + _this4.y = v2.y; + _this4.z = v2.z; + let y2 = _this4.x * m.e10 + _this4.y * m.e11 + _this4.z * m.e12; + let z2 = _this4.x * m.e20 + _this4.y * m.e21 + _this4.z * m.e22; + _this4.x = _this4.x * m.e00 + _this4.y * m.e01 + _this4.z * m.e02; + _this4.y = y2; + _this4.z = z2; + _this4.x *= radius; + _this4.y *= radius; + _this4.z *= radius; + _this4.x += o.x; + _this4.y += o.y; + _this4.z += o.z; + let _this5 = this.tmpCircleVerts2[i]; + let s = -halfHeight; + _this5.x += ey.x * s; + _this5.y += ey.y * s; + _this5.z += ey.z * s; + } + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + let v2 = this.tmpCircleVerts2[i]; + let v3 = this.tmpCircleVerts2[(i + 1) % 8]; + let n1 = this.tmpCircleVerts1[i]; + this.triangle(_this7,v2,v3,n1,this.tmpCircleNorms[i],this.tmpCircleNorms[(i + 1) % 8],color); + v2 = this.tmpCircleVerts2[(i + 1) % 8]; + v3 = this.tmpCircleVerts2[i]; + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ey.x; + _this1.y = ey.y; + _this1.z = ey.z; + let _this2 = _this1; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + + this.triangle(_this10,v2,v3,_this2,_this2,_this2,color); + let _this3 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = _this2; + } + } + } + let _this11 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this7; + } + let _this12 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this10; + } + let _this13 = this.p; + if(o != null) { + o.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = o; + } + let _this14 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this14.sizeMat3 == _this14.stackMat3.length) { + let newArray = new Array(_this14.sizeMat3 << 1); + let _g = 0; + let _g1 = _this14.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackMat3[i]; + _this14.stackMat3[i] = null; + } + _this14.stackMat3 = newArray; + } + _this14.stackMat3[_this14.sizeMat3++] = m; + } + let _this15 = this.p; + if(ex != null) { + ex.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = ex; + } + let _this16 = this.p; + if(ey != null) { + ey.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = ey; + } + let _this17 = this.p; + if(ez != null) { + ez.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = ez; + } + } + cylinder(tf,radius,halfHeight,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + _this7.x += ey.x * halfHeight; + _this7.y += ey.y * halfHeight; + _this7.z += ey.z * halfHeight; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s = -halfHeight; + _this10.x += ey.x * s; + _this10.y += ey.y * s; + _this10.z += ey.z * s; + if(this.wireframe) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = _this7.x; + _this1.y = _this7.y; + _this1.z = _this7.z; + let _this2 = _this1; + let s = -radius; + _this2.x += ex.x * s; + _this2.y += ex.y * s; + _this2.z += ex.z * s; + _this2.x += ez.x * 0; + _this2.y += ez.y * 0; + _this2.z += ez.z * 0; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = _this7.x; + _this4.y = _this7.y; + _this4.z = _this7.z; + let _this5 = _this4; + _this5.x += ex.x * radius; + _this5.y += ex.y * radius; + _this5.z += ex.z * radius; + _this5.x += ez.x * 0; + _this5.y += ez.y * 0; + _this5.z += ez.z * 0; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = _this7.x; + _this8.y = _this7.y; + _this8.z = _this7.z; + let _this9 = _this8; + _this9.x += ex.x * 0; + _this9.y += ex.y * 0; + _this9.z += ex.z * 0; + let s1 = -radius; + _this9.x += ez.x * s1; + _this9.y += ez.y * s1; + _this9.z += ez.z * s1; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = _this7.x; + _this12.y = _this7.y; + _this12.z = _this7.z; + let _this13 = _this12; + _this13.x += ex.x * 0; + _this13.y += ex.y * 0; + _this13.z += ex.z * 0; + _this13.x += ez.x * radius; + _this13.y += ez.y * radius; + _this13.z += ez.z * radius; + let _this14 = this.p; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = _this10.x; + _this15.y = _this10.y; + _this15.z = _this10.z; + let _this16 = _this15; + let s2 = -radius; + _this16.x += ex.x * s2; + _this16.y += ex.y * s2; + _this16.z += ex.z * s2; + _this16.x += ez.x * 0; + _this16.y += ez.y * 0; + _this16.z += ez.z * 0; + let _this17 = this.p; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = _this10.x; + _this18.y = _this10.y; + _this18.z = _this10.z; + let _this19 = _this18; + _this19.x += ex.x * radius; + _this19.y += ex.y * radius; + _this19.z += ex.z * radius; + _this19.x += ez.x * 0; + _this19.y += ez.y * 0; + _this19.z += ez.z * 0; + let _this20 = this.p; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = _this10.x; + _this21.y = _this10.y; + _this21.z = _this10.z; + let _this22 = _this21; + _this22.x += ex.x * 0; + _this22.y += ex.y * 0; + _this22.z += ex.z * 0; + let s3 = -radius; + _this22.x += ez.x * s3; + _this22.y += ez.y * s3; + _this22.z += ez.z * s3; + let _this23 = this.p; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = _this10.x; + _this24.y = _this10.y; + _this24.z = _this10.z; + let _this25 = _this24; + _this25.x += ex.x * 0; + _this25.y += ex.y * 0; + _this25.z += ex.z * 0; + _this25.x += ez.x * radius; + _this25.y += ez.y * radius; + _this25.z += ez.z * radius; + this.ellipse(_this7,ex,ez,radius,radius,color); + this.ellipse(_this10,ex,ez,radius,radius,color); + this.line(_this2,_this16,color); + this.line(_this5,_this19,color); + this.line(_this9,_this22,color); + this.line(_this13,_this25,color); + let _this26 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = _this2; + } + let _this27 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = _this5; + } + let _this28 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = _this9; + } + let _this29 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this13; + } + let _this30 = this.p; + if(_this16 != null) { + _this16.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this16; + } + let _this31 = this.p; + if(_this19 != null) { + _this19.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this19; + } + let _this32 = this.p; + if(_this22 != null) { + _this22.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this22; + } + let _this33 = this.p; + if(_this25 != null) { + _this25.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this25; + } + } else { + let _g = 0; + while(_g < 8) { + let i = _g++; + let _this = this.tmpCircleNorms[i]; + let v = this.circleCoords[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + let _this1 = this.tmpCircleVerts1[i]; + let v1 = this.tmpCircleNorms[i]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + _this1.x *= radius; + _this1.y *= radius; + _this1.z *= radius; + _this1.x += o.x; + _this1.y += o.y; + _this1.z += o.z; + let _this2 = this.tmpCircleVerts2[i]; + let v2 = this.tmpCircleVerts1[i]; + _this2.x = v2.x; + _this2.y = v2.y; + _this2.z = v2.z; + let _this3 = this.tmpCircleVerts1[i]; + _this3.x += ey.x * halfHeight; + _this3.y += ey.y * halfHeight; + _this3.z += ey.z * halfHeight; + let _this4 = this.tmpCircleVerts2[i]; + let s = -halfHeight; + _this4.x += ey.x * s; + _this4.y += ey.y * s; + _this4.z += ey.z * s; + } + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + let v1; + let v2 = this.tmpCircleVerts1[i]; + let v3 = this.tmpCircleVerts1[(i + 1) % 8]; + let n1 = ey; + this.triangle(_this7,v2,v3,n1,n1,n1,color); + + v2 = this.tmpCircleVerts2[(i + 1) % 8]; + v3 = this.tmpCircleVerts2[i]; + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ey.x; + _this1.y = ey.y; + _this1.z = ey.z; + let _this2 = _this1; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + + this.triangle(_this10,v2,v3,_this2,_this2,_this2,color); + let _this3 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this3.sizeVec3 == _this3.stackVec3.length) { + let newArray = new Array(_this3.sizeVec3 << 1); + let _g = 0; + let _g1 = _this3.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackVec3[i]; + _this3.stackVec3[i] = null; + } + _this3.stackVec3 = newArray; + } + _this3.stackVec3[_this3.sizeVec3++] = _this2; + } + v1 = this.tmpCircleVerts1[i]; + v2 = this.tmpCircleVerts2[i]; + v3 = this.tmpCircleVerts2[(i + 1) % 8]; + n1 = this.tmpCircleNorms[i]; + let n2 = this.tmpCircleNorms[(i + 1) % 8]; + this.rect(v1,v2,v3,this.tmpCircleVerts1[(i + 1) % 8],n1,n1,n2,n2,color); + } + } + let _this11 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this7; + } + let _this12 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this10; + } + let _this13 = this.p; + if(o != null) { + o.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = o; + } + let _this14 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this14.sizeMat3 == _this14.stackMat3.length) { + let newArray = new Array(_this14.sizeMat3 << 1); + let _g = 0; + let _g1 = _this14.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackMat3[i]; + _this14.stackMat3[i] = null; + } + _this14.stackMat3 = newArray; + } + _this14.stackMat3[_this14.sizeMat3++] = m; + } + let _this15 = this.p; + if(ex != null) { + ex.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = ex; + } + let _this16 = this.p; + if(ey != null) { + ey.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = ey; + } + let _this17 = this.p; + if(ez != null) { + ez.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = ez; + } + } + capsule(tf,radius,halfHeight,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let vs = this.tmpSphereVerts; + let ns = this.tmpSphereNorms; + let _g = 0; + while(_g < 5) { + let i2 = _g++; + let n = this.tmpSphereVerts[i2].length; + let _g1 = 0; + while(_g1 < n) { + let j2 = _g1++; + let _this = ns[i2][j2]; + let v = this.sphereCoords[i2][j2]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + } + } + let _g1 = 0; + while(_g1 < 4) { + let i = _g1++; + if(i == 0) { + let _g = 0; + while(_g < 3) { + let i2 = _g++; + let n = this.tmpSphereVerts[i2].length; + let _g1 = 0; + while(_g1 < n) { + let j2 = _g1++; + let _this = vs[i2][j2]; + let v = ns[i2][j2]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x *= radius; + _this.y *= radius; + _this.z *= radius; + _this.x += o.x; + _this.y += o.y; + _this.z += o.z; + _this.x += ey.x * halfHeight; + _this.y += ey.y * halfHeight; + _this.z += ey.z * halfHeight; + } + } + } + if(i == 2) { + let _g = 2; + while(_g < 5) { + let i2 = _g++; + let n = this.tmpSphereVerts[i2].length; + let _g1 = 0; + while(_g1 < n) { + let j2 = _g1++; + let _this = vs[i2][j2]; + let v = ns[i2][j2]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + _this.x *= radius; + _this.y *= radius; + _this.z *= radius; + _this.x += o.x; + _this.y += o.y; + _this.z += o.z; + let s = -halfHeight; + _this.x += ey.x * s; + _this.y += ey.y * s; + _this.z += ey.z * s; + } + } + } + let _g = 0; + while(_g < 8) { + let j = _g++; + let v1; + let v2; + let v3; + let v4; + let n1; + let n2; + let n3; + let n4; + if(i == 0) { + if(this.wireframe) { + v1 = vs[0][0]; + v2 = vs[1][j]; + this.line(v1,v2,color); + } else { + v1 = vs[0][0]; + v2 = vs[1][j]; + v3 = vs[1][(j + 1) % 8]; + n1 = ns[0][0]; + n2 = ns[1][j]; + n3 = ns[1][(j + 1) % 8]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(i == 3) { + if(this.wireframe) { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + this.line(v1,v2,color); + this.line(v2,v3,color); + } else { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + n1 = ns[4][0]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i][j]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(this.wireframe) { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + this.line(v1,v2,color); + this.line(v1,v3,color); + } else { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + v4 = vs[i + 1][(j + 1) % 8]; + n1 = ns[i][j]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i + 1][j]; + n4 = ns[i + 1][(j + 1) % 8]; + this.rect(v1,v3,v4,v2,n1,n3,n4,n2,color); + } + } + } + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + _this7.x += ey.x * halfHeight; + _this7.y += ey.y * halfHeight; + _this7.z += ey.z * halfHeight; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s = -halfHeight; + _this10.x += ey.x * s; + _this10.y += ey.y * s; + _this10.z += ey.z * s; + if(this.wireframe) { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = _this7.x; + _this1.y = _this7.y; + _this1.z = _this7.z; + let _this2 = _this1; + let s = -radius; + _this2.x += ex.x * s; + _this2.y += ex.y * s; + _this2.z += ex.z * s; + _this2.x += ez.x * 0; + _this2.y += ez.y * 0; + _this2.z += ez.z * 0; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = _this7.x; + _this4.y = _this7.y; + _this4.z = _this7.z; + let _this5 = _this4; + _this5.x += ex.x * radius; + _this5.y += ex.y * radius; + _this5.z += ex.z * radius; + _this5.x += ez.x * 0; + _this5.y += ez.y * 0; + _this5.z += ez.z * 0; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = _this7.x; + _this8.y = _this7.y; + _this8.z = _this7.z; + let _this9 = _this8; + _this9.x += ex.x * 0; + _this9.y += ex.y * 0; + _this9.z += ex.z * 0; + let s1 = -radius; + _this9.x += ez.x * s1; + _this9.y += ez.y * s1; + _this9.z += ez.z * s1; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = _this7.x; + _this12.y = _this7.y; + _this12.z = _this7.z; + let _this13 = _this12; + _this13.x += ex.x * 0; + _this13.y += ex.y * 0; + _this13.z += ex.z * 0; + _this13.x += ez.x * radius; + _this13.y += ez.y * radius; + _this13.z += ez.z * radius; + let _this14 = this.p; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = _this10.x; + _this15.y = _this10.y; + _this15.z = _this10.z; + let _this16 = _this15; + let s2 = -radius; + _this16.x += ex.x * s2; + _this16.y += ex.y * s2; + _this16.z += ex.z * s2; + _this16.x += ez.x * 0; + _this16.y += ez.y * 0; + _this16.z += ez.z * 0; + let _this17 = this.p; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = _this10.x; + _this18.y = _this10.y; + _this18.z = _this10.z; + let _this19 = _this18; + _this19.x += ex.x * radius; + _this19.y += ex.y * radius; + _this19.z += ex.z * radius; + _this19.x += ez.x * 0; + _this19.y += ez.y * 0; + _this19.z += ez.z * 0; + let _this20 = this.p; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = _this10.x; + _this21.y = _this10.y; + _this21.z = _this10.z; + let _this22 = _this21; + _this22.x += ex.x * 0; + _this22.y += ex.y * 0; + _this22.z += ex.z * 0; + let s3 = -radius; + _this22.x += ez.x * s3; + _this22.y += ez.y * s3; + _this22.z += ez.z * s3; + let _this23 = this.p; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = _this10.x; + _this24.y = _this10.y; + _this24.z = _this10.z; + let _this25 = _this24; + _this25.x += ex.x * 0; + _this25.y += ex.y * 0; + _this25.z += ex.z * 0; + _this25.x += ez.x * radius; + _this25.y += ez.y * radius; + _this25.z += ez.z * radius; + this.ellipse(_this7,ex,ez,radius,radius,color); + this.ellipse(_this10,ex,ez,radius,radius,color); + this.line(_this2,_this16,color); + this.line(_this5,_this19,color); + this.line(_this9,_this22,color); + this.line(_this13,_this25,color); + let _this26 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this26.sizeVec3 == _this26.stackVec3.length) { + let newArray = new Array(_this26.sizeVec3 << 1); + let _g = 0; + let _g1 = _this26.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this26.stackVec3[i]; + _this26.stackVec3[i] = null; + } + _this26.stackVec3 = newArray; + } + _this26.stackVec3[_this26.sizeVec3++] = _this2; + } + let _this27 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this27.sizeVec3 == _this27.stackVec3.length) { + let newArray = new Array(_this27.sizeVec3 << 1); + let _g = 0; + let _g1 = _this27.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this27.stackVec3[i]; + _this27.stackVec3[i] = null; + } + _this27.stackVec3 = newArray; + } + _this27.stackVec3[_this27.sizeVec3++] = _this5; + } + let _this28 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this28.sizeVec3 == _this28.stackVec3.length) { + let newArray = new Array(_this28.sizeVec3 << 1); + let _g = 0; + let _g1 = _this28.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this28.stackVec3[i]; + _this28.stackVec3[i] = null; + } + _this28.stackVec3 = newArray; + } + _this28.stackVec3[_this28.sizeVec3++] = _this9; + } + let _this29 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this13; + } + let _this30 = this.p; + if(_this16 != null) { + _this16.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this16; + } + let _this31 = this.p; + if(_this19 != null) { + _this19.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this19; + } + let _this32 = this.p; + if(_this22 != null) { + _this22.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this22; + } + let _this33 = this.p; + if(_this25 != null) { + _this25.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this25; + } + } else { + let _g = 0; + while(_g < 8) { + let i = _g++; + let _this = this.tmpCircleNorms[i]; + let v = this.circleCoords[i]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + let _this1 = this.tmpCircleVerts1[i]; + let v1 = this.tmpCircleNorms[i]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + _this1.x *= radius; + _this1.y *= radius; + _this1.z *= radius; + _this1.x += o.x; + _this1.y += o.y; + _this1.z += o.z; + let _this2 = this.tmpCircleVerts2[i]; + let v2 = this.tmpCircleVerts1[i]; + _this2.x = v2.x; + _this2.y = v2.y; + _this2.z = v2.z; + let _this3 = this.tmpCircleVerts1[i]; + _this3.x += ey.x * halfHeight; + _this3.y += ey.y * halfHeight; + _this3.z += ey.z * halfHeight; + let _this4 = this.tmpCircleVerts2[i]; + let s = -halfHeight; + _this4.x += ey.x * s; + _this4.y += ey.y * s; + _this4.z += ey.z * s; + } + let _g1 = 0; + while(_g1 < 8) { + let i = _g1++; + let n1 = this.tmpCircleNorms[i]; + let n2 = this.tmpCircleNorms[(i + 1) % 8]; + this.rect(this.tmpCircleVerts1[i],this.tmpCircleVerts2[i],this.tmpCircleVerts2[(i + 1) % 8],this.tmpCircleVerts1[(i + 1) % 8],n1,n1,n2,n2,color); + } + } + let _this11 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this7; + } + let _this12 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this10; + } + let _this13 = this.p; + if(o != null) { + o.zero(); + if(_this13.sizeVec3 == _this13.stackVec3.length) { + let newArray = new Array(_this13.sizeVec3 << 1); + let _g = 0; + let _g1 = _this13.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this13.stackVec3[i]; + _this13.stackVec3[i] = null; + } + _this13.stackVec3 = newArray; + } + _this13.stackVec3[_this13.sizeVec3++] = o; + } + let _this14 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this14.sizeMat3 == _this14.stackMat3.length) { + let newArray = new Array(_this14.sizeMat3 << 1); + let _g = 0; + let _g1 = _this14.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackMat3[i]; + _this14.stackMat3[i] = null; + } + _this14.stackMat3 = newArray; + } + _this14.stackMat3[_this14.sizeMat3++] = m; + } + let _this15 = this.p; + if(ex != null) { + ex.zero(); + if(_this15.sizeVec3 == _this15.stackVec3.length) { + let newArray = new Array(_this15.sizeVec3 << 1); + let _g = 0; + let _g1 = _this15.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this15.stackVec3[i]; + _this15.stackVec3[i] = null; + } + _this15.stackVec3 = newArray; + } + _this15.stackVec3[_this15.sizeVec3++] = ex; + } + let _this16 = this.p; + if(ey != null) { + ey.zero(); + if(_this16.sizeVec3 == _this16.stackVec3.length) { + let newArray = new Array(_this16.sizeVec3 << 1); + let _g = 0; + let _g1 = _this16.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this16.stackVec3[i]; + _this16.stackVec3[i] = null; + } + _this16.stackVec3 = newArray; + } + _this16.stackVec3[_this16.sizeVec3++] = ey; + } + let _this17 = this.p; + if(ez != null) { + ez.zero(); + if(_this17.sizeVec3 == _this17.stackVec3.length) { + let newArray = new Array(_this17.sizeVec3 << 1); + let _g = 0; + let _g1 = _this17.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this17.stackVec3[i]; + _this17.stackVec3[i] = null; + } + _this17.stackVec3 = newArray; + } + _this17.stackVec3[_this17.sizeVec3++] = ez; + } + } + sphere(tf,radius,color) { + let _this = this.p; + let o = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let m = _this1.sizeMat3 == 0 ? new oimo.common.Mat3() : _this1.stackMat3[--_this1.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + let vs = this.tmpSphereVerts; + let ns = this.tmpSphereNorms; + let _g = 0; + while(_g < 5) { + let i = _g++; + let n = this.tmpSphereVerts[i].length; + let _g1 = 0; + while(_g1 < n) { + let j = _g1++; + let _this = ns[i][j]; + let v = this.sphereCoords[i][j]; + _this.x = v.x; + _this.y = v.y; + _this.z = v.z; + let y = _this.x * m.e10 + _this.y * m.e11 + _this.z * m.e12; + let z = _this.x * m.e20 + _this.y * m.e21 + _this.z * m.e22; + _this.x = _this.x * m.e00 + _this.y * m.e01 + _this.z * m.e02; + _this.y = y; + _this.z = z; + let _this1 = vs[i][j]; + let v1 = ns[i][j]; + _this1.x = v1.x; + _this1.y = v1.y; + _this1.z = v1.z; + _this1.x *= radius; + _this1.y *= radius; + _this1.z *= radius; + _this1.x += o.x; + _this1.y += o.y; + _this1.z += o.z; + } + } + let _g1 = 0; + while(_g1 < 4) { + let i = _g1++; + let _g = 0; + while(_g < 8) { + let j = _g++; + let v1; + let v2; + let v3; + let v4; + let n1; + let n2; + let n3; + let n4; + if(i == 0) { + if(this.wireframe) { + v1 = vs[0][0]; + v2 = vs[1][j]; + this.line(v1,v2,color); + } else { + v1 = vs[0][0]; + v2 = vs[1][j]; + v3 = vs[1][(j + 1) % 8]; + n1 = ns[0][0]; + n2 = ns[1][j]; + n3 = ns[1][(j + 1) % 8]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(i == 3) { + if(this.wireframe) { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + this.line(v1,v2,color); + this.line(v2,v3,color); + } else { + v1 = vs[4][0]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i][j]; + n1 = ns[4][0]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i][j]; + this.triangle(v1,v2,v3,n1,n2,n3,color); + } + } else if(this.wireframe) { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + this.line(v1,v2,color); + this.line(v1,v3,color); + } else { + v1 = vs[i][j]; + v2 = vs[i][(j + 1) % 8]; + v3 = vs[i + 1][j]; + v4 = vs[i + 1][(j + 1) % 8]; + n1 = ns[i][j]; + n2 = ns[i][(j + 1) % 8]; + n3 = ns[i + 1][j]; + n4 = ns[i + 1][(j + 1) % 8]; + this.rect(v1,v3,v4,v2,n1,n3,n4,n2,color); + } + } + } + let _this2 = this.p; + if(o != null) { + o.zero(); + if(_this2.sizeVec3 == _this2.stackVec3.length) { + let newArray = new Array(_this2.sizeVec3 << 1); + let _g = 0; + let _g1 = _this2.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this2.stackVec3[i]; + _this2.stackVec3[i] = null; + } + _this2.stackVec3 = newArray; + } + _this2.stackVec3[_this2.sizeVec3++] = o; + } + let _this3 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this3.sizeMat3 == _this3.stackMat3.length) { + let newArray = new Array(_this3.sizeMat3 << 1); + let _g = 0; + let _g1 = _this3.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this3.stackMat3[i]; + _this3.stackMat3[i] = null; + } + _this3.stackMat3 = newArray; + } + _this3.stackMat3[_this3.sizeMat3++] = m; + } + } + box(tf,halfExtents,color) { + let _this = this.p; + let ex = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + let _this1 = this.p; + let ey = _this1.sizeVec3 == 0 ? new oimo.common.Vec3() : _this1.stackVec3[--_this1.sizeVec3]; + let _this2 = this.p; + let ez = _this2.sizeVec3 == 0 ? new oimo.common.Vec3() : _this2.stackVec3[--_this2.sizeVec3]; + let _this3 = this.p; + let o = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + let _this4 = this.p; + let m = _this4.sizeMat3 == 0 ? new oimo.common.Mat3() : _this4.stackMat3[--_this4.sizeMat3]; + let v = o; + v.x = tf._positionX; + v.y = tf._positionY; + v.z = tf._positionZ; + let m1 = m; + m1.e00 = tf._rotation00; + m1.e01 = tf._rotation01; + m1.e02 = tf._rotation02; + m1.e10 = tf._rotation10; + m1.e11 = tf._rotation11; + m1.e12 = tf._rotation12; + m1.e20 = tf._rotation20; + m1.e21 = tf._rotation21; + m1.e22 = tf._rotation22; + ex.init(m.e00,m.e10,m.e20); + ey.init(m.e01,m.e11,m.e21); + ez.init(m.e02,m.e12,m.e22); + let hx = halfExtents.x; + let hy = halfExtents.y; + let hz = halfExtents.z; + let _this5 = this.p; + let _this6 = _this5.sizeVec3 == 0 ? new oimo.common.Vec3() : _this5.stackVec3[--_this5.sizeVec3]; + _this6.x = o.x; + _this6.y = o.y; + _this6.z = o.z; + let _this7 = _this6; + let s = -hx; + _this7.x += ex.x * s; + _this7.y += ex.y * s; + _this7.z += ex.z * s; + let s1 = -hy; + _this7.x += ey.x * s1; + _this7.y += ey.y * s1; + _this7.z += ey.z * s1; + let s2 = -hz; + _this7.x += ez.x * s2; + _this7.y += ez.y * s2; + _this7.z += ez.z * s2; + let _this8 = this.p; + let _this9 = _this8.sizeVec3 == 0 ? new oimo.common.Vec3() : _this8.stackVec3[--_this8.sizeVec3]; + _this9.x = o.x; + _this9.y = o.y; + _this9.z = o.z; + let _this10 = _this9; + let s3 = -hx; + _this10.x += ex.x * s3; + _this10.y += ex.y * s3; + _this10.z += ex.z * s3; + let s4 = -hy; + _this10.x += ey.x * s4; + _this10.y += ey.y * s4; + _this10.z += ey.z * s4; + _this10.x += ez.x * hz; + _this10.y += ez.y * hz; + _this10.z += ez.z * hz; + let _this11 = this.p; + let _this12 = _this11.sizeVec3 == 0 ? new oimo.common.Vec3() : _this11.stackVec3[--_this11.sizeVec3]; + _this12.x = o.x; + _this12.y = o.y; + _this12.z = o.z; + let _this13 = _this12; + let s5 = -hx; + _this13.x += ex.x * s5; + _this13.y += ex.y * s5; + _this13.z += ex.z * s5; + _this13.x += ey.x * hy; + _this13.y += ey.y * hy; + _this13.z += ey.z * hy; + let s6 = -hz; + _this13.x += ez.x * s6; + _this13.y += ez.y * s6; + _this13.z += ez.z * s6; + let _this14 = this.p; + let _this15 = _this14.sizeVec3 == 0 ? new oimo.common.Vec3() : _this14.stackVec3[--_this14.sizeVec3]; + _this15.x = o.x; + _this15.y = o.y; + _this15.z = o.z; + let _this16 = _this15; + let s7 = -hx; + _this16.x += ex.x * s7; + _this16.y += ex.y * s7; + _this16.z += ex.z * s7; + _this16.x += ey.x * hy; + _this16.y += ey.y * hy; + _this16.z += ey.z * hy; + _this16.x += ez.x * hz; + _this16.y += ez.y * hz; + _this16.z += ez.z * hz; + let _this17 = this.p; + let _this18 = _this17.sizeVec3 == 0 ? new oimo.common.Vec3() : _this17.stackVec3[--_this17.sizeVec3]; + _this18.x = o.x; + _this18.y = o.y; + _this18.z = o.z; + let _this19 = _this18; + _this19.x += ex.x * hx; + _this19.y += ex.y * hx; + _this19.z += ex.z * hx; + let s8 = -hy; + _this19.x += ey.x * s8; + _this19.y += ey.y * s8; + _this19.z += ey.z * s8; + let s9 = -hz; + _this19.x += ez.x * s9; + _this19.y += ez.y * s9; + _this19.z += ez.z * s9; + let _this20 = this.p; + let _this21 = _this20.sizeVec3 == 0 ? new oimo.common.Vec3() : _this20.stackVec3[--_this20.sizeVec3]; + _this21.x = o.x; + _this21.y = o.y; + _this21.z = o.z; + let _this22 = _this21; + _this22.x += ex.x * hx; + _this22.y += ex.y * hx; + _this22.z += ex.z * hx; + let s10 = -hy; + _this22.x += ey.x * s10; + _this22.y += ey.y * s10; + _this22.z += ey.z * s10; + _this22.x += ez.x * hz; + _this22.y += ez.y * hz; + _this22.z += ez.z * hz; + let _this23 = this.p; + let _this24 = _this23.sizeVec3 == 0 ? new oimo.common.Vec3() : _this23.stackVec3[--_this23.sizeVec3]; + _this24.x = o.x; + _this24.y = o.y; + _this24.z = o.z; + let _this25 = _this24; + _this25.x += ex.x * hx; + _this25.y += ex.y * hx; + _this25.z += ex.z * hx; + _this25.x += ey.x * hy; + _this25.y += ey.y * hy; + _this25.z += ey.z * hy; + let s11 = -hz; + _this25.x += ez.x * s11; + _this25.y += ez.y * s11; + _this25.z += ez.z * s11; + let _this26 = this.p; + let _this27 = _this26.sizeVec3 == 0 ? new oimo.common.Vec3() : _this26.stackVec3[--_this26.sizeVec3]; + _this27.x = o.x; + _this27.y = o.y; + _this27.z = o.z; + let _this28 = _this27; + _this28.x += ex.x * hx; + _this28.y += ex.y * hx; + _this28.z += ex.z * hx; + _this28.x += ey.x * hy; + _this28.y += ey.y * hy; + _this28.z += ey.z * hy; + _this28.x += ez.x * hz; + _this28.y += ez.y * hz; + _this28.z += ez.z * hz; + if(this.wireframe) { + this.line(_this7,_this10,color); + this.line(_this13,_this16,color); + this.line(_this19,_this22,color); + this.line(_this25,_this28,color); + this.line(_this7,_this13,color); + this.line(_this10,_this16,color); + this.line(_this19,_this25,color); + this.line(_this22,_this28,color); + this.line(_this7,_this19,color); + this.line(_this10,_this22,color); + this.line(_this13,_this25,color); + this.line(_this16,_this28,color); + } else { + let _this = this.p; + let _this1 = _this.sizeVec3 == 0 ? new oimo.common.Vec3() : _this.stackVec3[--_this.sizeVec3]; + _this1.x = ex.x; + _this1.y = ex.y; + _this1.z = ex.z; + let _this2 = _this1; + _this2.x = -_this2.x; + _this2.y = -_this2.y; + _this2.z = -_this2.z; + let _this3 = this.p; + let _this4 = _this3.sizeVec3 == 0 ? new oimo.common.Vec3() : _this3.stackVec3[--_this3.sizeVec3]; + _this4.x = ey.x; + _this4.y = ey.y; + _this4.z = ey.z; + let _this5 = _this4; + _this5.x = -_this5.x; + _this5.y = -_this5.y; + _this5.z = -_this5.z; + let _this6 = this.p; + let _this8 = _this6.sizeVec3 == 0 ? new oimo.common.Vec3() : _this6.stackVec3[--_this6.sizeVec3]; + _this8.x = ez.x; + _this8.y = ez.y; + _this8.z = ez.z; + let _this9 = _this8; + _this9.x = -_this9.x; + _this9.y = -_this9.y; + _this9.z = -_this9.z; + this.rect(_this7,_this10,_this16,_this13,_this2,_this2,_this2,_this2,color); + this.rect(_this19,_this25,_this28,_this22,ex,ex,ex,ex,color); + this.rect(_this7,_this19,_this22,_this10,_this5,_this5,_this5,_this5,color); + this.rect(_this13,_this16,_this28,_this25,ey,ey,ey,ey,color); + this.rect(_this7,_this13,_this25,_this19,_this9,_this9,_this9,_this9,color); + this.rect(_this10,_this22,_this28,_this16,ez,ez,ez,ez,color); + let _this11 = this.p; + if(_this2 != null) { + _this2.zero(); + if(_this11.sizeVec3 == _this11.stackVec3.length) { + let newArray = new Array(_this11.sizeVec3 << 1); + let _g = 0; + let _g1 = _this11.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this11.stackVec3[i]; + _this11.stackVec3[i] = null; + } + _this11.stackVec3 = newArray; + } + _this11.stackVec3[_this11.sizeVec3++] = _this2; + } + let _this12 = this.p; + if(_this5 != null) { + _this5.zero(); + if(_this12.sizeVec3 == _this12.stackVec3.length) { + let newArray = new Array(_this12.sizeVec3 << 1); + let _g = 0; + let _g1 = _this12.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this12.stackVec3[i]; + _this12.stackVec3[i] = null; + } + _this12.stackVec3 = newArray; + } + _this12.stackVec3[_this12.sizeVec3++] = _this5; + } + let _this14 = this.p; + if(_this9 != null) { + _this9.zero(); + if(_this14.sizeVec3 == _this14.stackVec3.length) { + let newArray = new Array(_this14.sizeVec3 << 1); + let _g = 0; + let _g1 = _this14.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this14.stackVec3[i]; + _this14.stackVec3[i] = null; + } + _this14.stackVec3 = newArray; + } + _this14.stackVec3[_this14.sizeVec3++] = _this9; + } + } + let _this29 = this.p; + if(_this7 != null) { + _this7.zero(); + if(_this29.sizeVec3 == _this29.stackVec3.length) { + let newArray = new Array(_this29.sizeVec3 << 1); + let _g = 0; + let _g1 = _this29.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this29.stackVec3[i]; + _this29.stackVec3[i] = null; + } + _this29.stackVec3 = newArray; + } + _this29.stackVec3[_this29.sizeVec3++] = _this7; + } + let _this30 = this.p; + if(_this10 != null) { + _this10.zero(); + if(_this30.sizeVec3 == _this30.stackVec3.length) { + let newArray = new Array(_this30.sizeVec3 << 1); + let _g = 0; + let _g1 = _this30.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this30.stackVec3[i]; + _this30.stackVec3[i] = null; + } + _this30.stackVec3 = newArray; + } + _this30.stackVec3[_this30.sizeVec3++] = _this10; + } + let _this31 = this.p; + if(_this13 != null) { + _this13.zero(); + if(_this31.sizeVec3 == _this31.stackVec3.length) { + let newArray = new Array(_this31.sizeVec3 << 1); + let _g = 0; + let _g1 = _this31.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this31.stackVec3[i]; + _this31.stackVec3[i] = null; + } + _this31.stackVec3 = newArray; + } + _this31.stackVec3[_this31.sizeVec3++] = _this13; + } + let _this32 = this.p; + if(_this16 != null) { + _this16.zero(); + if(_this32.sizeVec3 == _this32.stackVec3.length) { + let newArray = new Array(_this32.sizeVec3 << 1); + let _g = 0; + let _g1 = _this32.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this32.stackVec3[i]; + _this32.stackVec3[i] = null; + } + _this32.stackVec3 = newArray; + } + _this32.stackVec3[_this32.sizeVec3++] = _this16; + } + let _this33 = this.p; + if(_this19 != null) { + _this19.zero(); + if(_this33.sizeVec3 == _this33.stackVec3.length) { + let newArray = new Array(_this33.sizeVec3 << 1); + let _g = 0; + let _g1 = _this33.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this33.stackVec3[i]; + _this33.stackVec3[i] = null; + } + _this33.stackVec3 = newArray; + } + _this33.stackVec3[_this33.sizeVec3++] = _this19; + } + let _this34 = this.p; + if(_this22 != null) { + _this22.zero(); + if(_this34.sizeVec3 == _this34.stackVec3.length) { + let newArray = new Array(_this34.sizeVec3 << 1); + let _g = 0; + let _g1 = _this34.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this34.stackVec3[i]; + _this34.stackVec3[i] = null; + } + _this34.stackVec3 = newArray; + } + _this34.stackVec3[_this34.sizeVec3++] = _this22; + } + let _this35 = this.p; + if(_this25 != null) { + _this25.zero(); + if(_this35.sizeVec3 == _this35.stackVec3.length) { + let newArray = new Array(_this35.sizeVec3 << 1); + let _g = 0; + let _g1 = _this35.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this35.stackVec3[i]; + _this35.stackVec3[i] = null; + } + _this35.stackVec3 = newArray; + } + _this35.stackVec3[_this35.sizeVec3++] = _this25; + } + let _this36 = this.p; + if(_this28 != null) { + _this28.zero(); + if(_this36.sizeVec3 == _this36.stackVec3.length) { + let newArray = new Array(_this36.sizeVec3 << 1); + let _g = 0; + let _g1 = _this36.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this36.stackVec3[i]; + _this36.stackVec3[i] = null; + } + _this36.stackVec3 = newArray; + } + _this36.stackVec3[_this36.sizeVec3++] = _this28; + } + let _this37 = this.p; + if(o != null) { + o.zero(); + if(_this37.sizeVec3 == _this37.stackVec3.length) { + let newArray = new Array(_this37.sizeVec3 << 1); + let _g = 0; + let _g1 = _this37.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this37.stackVec3[i]; + _this37.stackVec3[i] = null; + } + _this37.stackVec3 = newArray; + } + _this37.stackVec3[_this37.sizeVec3++] = o; + } + let _this38 = this.p; + if(m != null) { + m.e00 = 1; + m.e01 = 0; + m.e02 = 0; + m.e10 = 0; + m.e11 = 1; + m.e12 = 0; + m.e20 = 0; + m.e21 = 0; + m.e22 = 1; + if(_this38.sizeMat3 == _this38.stackMat3.length) { + let newArray = new Array(_this38.sizeMat3 << 1); + let _g = 0; + let _g1 = _this38.sizeMat3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this38.stackMat3[i]; + _this38.stackMat3[i] = null; + } + _this38.stackMat3 = newArray; + } + _this38.stackMat3[_this38.sizeMat3++] = m; + } + let _this39 = this.p; + if(ex != null) { + ex.zero(); + if(_this39.sizeVec3 == _this39.stackVec3.length) { + let newArray = new Array(_this39.sizeVec3 << 1); + let _g = 0; + let _g1 = _this39.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this39.stackVec3[i]; + _this39.stackVec3[i] = null; + } + _this39.stackVec3 = newArray; + } + _this39.stackVec3[_this39.sizeVec3++] = ex; + } + let _this40 = this.p; + if(ey != null) { + ey.zero(); + if(_this40.sizeVec3 == _this40.stackVec3.length) { + let newArray = new Array(_this40.sizeVec3 << 1); + let _g = 0; + let _g1 = _this40.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this40.stackVec3[i]; + _this40.stackVec3[i] = null; + } + _this40.stackVec3 = newArray; + } + _this40.stackVec3[_this40.sizeVec3++] = ey; + } + let _this41 = this.p; + if(ez != null) { + ez.zero(); + if(_this41.sizeVec3 == _this41.stackVec3.length) { + let newArray = new Array(_this41.sizeVec3 << 1); + let _g = 0; + let _g1 = _this41.sizeVec3; + while(_g < _g1) { + let i = _g++; + newArray[i] = _this41.stackVec3[i]; + _this41.stackVec3[i] = null; + } + _this41.stackVec3 = newArray; + } + _this41.stackVec3[_this41.sizeVec3++] = ez; + } + } + rect(v1,v2,v3,v4,n1,n2,n3,n4,color) { + this.triangle(v1,v2,v3,n1,n2,n3,color); + this.triangle(v1,v3,v4,n1,n3,n4,color); + } + point(v,color) { + } + triangle(v1,v2,v3,n1,n2,n3,color) { + } + line(v1,v2,color) { + } +} +oimo.dynamics.common.DebugDrawStyle = class oimo_dynamics_common_DebugDrawStyle { + constructor() { + this.basisColorZ = new oimo.common.Vec3(0.0,0.0,1.0); + this.basisColorY = new oimo.common.Vec3(0.0,1.0,0.0); + this.basisColorX = new oimo.common.Vec3(1.0,0.0,0.0); + this.basisLength = 0.5; + this.jointRotationalConstraintRadius = 0.3; + this.jointErrorColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.jointLineColor = new oimo.common.Vec3(0.8,0.8,0.8); + this.contactBinormalLength = 0.5; + this.contactTangentLength = 0.5; + this.contactNormalLength = 0.5; + this.contactBinormalColor = new oimo.common.Vec3(0.2,0.2,1.0); + this.contactTangentColor = new oimo.common.Vec3(0.1,0.8,0.1); + this.contactNormalColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.disabledContactColor = new oimo.common.Vec3(0.5,0.1,0.1); + this.newContactColor = new oimo.common.Vec3(1.0,1.0,0.1); + this.contactColor4 = new oimo.common.Vec3(0.8,0.1,1.0); + this.contactColor3 = new oimo.common.Vec3(0.1,0.8,0.6); + this.contactColor2 = new oimo.common.Vec3(1.0,0.6,0.1); + this.contactColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.pairColor = new oimo.common.Vec3(1.0,1.0,0.1); + this.bvhNodeColor = new oimo.common.Vec3(0.4,0.4,0.4); + this.aabbColor = new oimo.common.Vec3(1.0,0.1,0.1); + this.kinematicShapeColor = new oimo.common.Vec3(1.0,0.5,0.1); + this.staticShapeColor = new oimo.common.Vec3(0.7,0.7,0.7); + this.sleepingShapeColor2 = new oimo.common.Vec3(0.2,0.8,0.5); + this.sleepingShapeColor1 = new oimo.common.Vec3(0.3,0.3,0.8); + this.sleepyShapeColor2 = new oimo.common.Vec3(0.6,0.8,0.3); + this.sleepyShapeColor1 = new oimo.common.Vec3(0.5,0.25,0.6); + this.shapeColor2 = new oimo.common.Vec3(1.0,0.8,0.1); + this.shapeColor1 = new oimo.common.Vec3(0.7,0.2,0.4); + } +} +oimo.dynamics.common.Performance = class oimo_dynamics_common_Performance { +} +if(!oimo.dynamics.constraint) oimo.dynamics.constraint = {}; +oimo.dynamics.constraint.ConstraintSolver = class oimo_dynamics_constraint_ConstraintSolver { + constructor() { + this._b1 = null; + this._b2 = null; + this._addedToIsland = false; + } + preSolveVelocity(timeStep) { + } + warmStart(timeStep) { + } + solveVelocity() { + } + postSolveVelocity(timeStep) { + } + preSolvePosition(timeStep) { + } + solvePositionSplitImpulse() { + } + solvePositionNgs(timeStep) { + } + postSolve() { + } +} +oimo.dynamics.constraint.PositionCorrectionAlgorithm = class oimo_dynamics_constraint_PositionCorrectionAlgorithm { +} +if(!oimo.dynamics.constraint.contact) oimo.dynamics.constraint.contact = {}; +oimo.dynamics.constraint.contact.ContactConstraint = class oimo_dynamics_constraint_contact_ContactConstraint { + constructor(manifold) { + this._solver = new oimo.dynamics.constraint.solver.pgs.PgsContactConstraintSolver(this); + this._manifold = manifold; + } + _getVelocitySolverInfo(timeStep,info) { + info.b1 = this._b1; + info.b2 = this._b2; + let normalX; + let normalY; + let normalZ; + let tangentX; + let tangentY; + let tangentZ; + let binormalX; + let binormalY; + let binormalZ; + normalX = this._manifold._normalX; + normalY = this._manifold._normalY; + normalZ = this._manifold._normalZ; + tangentX = this._manifold._tangentX; + tangentY = this._manifold._tangentY; + tangentZ = this._manifold._tangentZ; + binormalX = this._manifold._binormalX; + binormalY = this._manifold._binormalY; + binormalZ = this._manifold._binormalZ; + let friction = Math.sqrt(this._s1._friction * this._s2._friction); + let restitution = Math.sqrt(this._s1._restitution * this._s2._restitution); + let num = this._manifold._numPoints; + info.numRows = 0; + let _g = 0; + while(_g < num) { + let p = this._manifold._points[_g++]; + if(p._depth < 0) { + p._disabled = true; + let _this = p._impulse; + _this.impulseN = 0; + _this.impulseT = 0; + _this.impulseB = 0; + _this.impulseP = 0; + _this.impulseLX = 0; + _this.impulseLY = 0; + _this.impulseLZ = 0; + continue; + } else { + p._disabled = false; + } + let row = info.rows[info.numRows++]; + row.friction = friction; + row.cfm = 0; + let j = row.jacobianN; + j.lin1X = normalX; + j.lin1Y = normalY; + j.lin1Z = normalZ; + j.lin2X = normalX; + j.lin2Y = normalY; + j.lin2Z = normalZ; + j.ang1X = p._relPos1Y * normalZ - p._relPos1Z * normalY; + j.ang1Y = p._relPos1Z * normalX - p._relPos1X * normalZ; + j.ang1Z = p._relPos1X * normalY - p._relPos1Y * normalX; + j.ang2X = p._relPos2Y * normalZ - p._relPos2Z * normalY; + j.ang2Y = p._relPos2Z * normalX - p._relPos2X * normalZ; + j.ang2Z = p._relPos2X * normalY - p._relPos2Y * normalX; + j = row.jacobianT; + j.lin1X = tangentX; + j.lin1Y = tangentY; + j.lin1Z = tangentZ; + j.lin2X = tangentX; + j.lin2Y = tangentY; + j.lin2Z = tangentZ; + j.ang1X = p._relPos1Y * tangentZ - p._relPos1Z * tangentY; + j.ang1Y = p._relPos1Z * tangentX - p._relPos1X * tangentZ; + j.ang1Z = p._relPos1X * tangentY - p._relPos1Y * tangentX; + j.ang2X = p._relPos2Y * tangentZ - p._relPos2Z * tangentY; + j.ang2Y = p._relPos2Z * tangentX - p._relPos2X * tangentZ; + j.ang2Z = p._relPos2X * tangentY - p._relPos2Y * tangentX; + j = row.jacobianB; + j.lin1X = binormalX; + j.lin1Y = binormalY; + j.lin1Z = binormalZ; + j.lin2X = binormalX; + j.lin2Y = binormalY; + j.lin2Z = binormalZ; + j.ang1X = p._relPos1Y * binormalZ - p._relPos1Z * binormalY; + j.ang1Y = p._relPos1Z * binormalX - p._relPos1X * binormalZ; + j.ang1Z = p._relPos1X * binormalY - p._relPos1Y * binormalX; + j.ang2X = p._relPos2Y * binormalZ - p._relPos2Z * binormalY; + j.ang2Y = p._relPos2Z * binormalX - p._relPos2X * binormalZ; + j.ang2Z = p._relPos2X * binormalY - p._relPos2Y * binormalX; + j = row.jacobianN; + let rvn = j.lin1X * this._b1._velX + j.lin1Y * this._b1._velY + j.lin1Z * this._b1._velZ + (j.ang1X * this._b1._angVelX + j.ang1Y * this._b1._angVelY + j.ang1Z * this._b1._angVelZ) - (j.lin2X * this._b2._velX + j.lin2Y * this._b2._velY + j.lin2Z * this._b2._velZ + (j.ang2X * this._b2._angVelX + j.ang2Y * this._b2._angVelY + j.ang2Z * this._b2._angVelZ)); + if(rvn < -oimo.common.Setting.contactEnableBounceThreshold && !p._warmStarted) { + row.rhs = -rvn * restitution; + } else { + row.rhs = 0; + } + if(this._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE) { + if(p._depth > oimo.common.Setting.linearSlop) { + let minRhs = (p._depth - oimo.common.Setting.linearSlop) * oimo.common.Setting.velocityBaumgarte * timeStep.invDt; + if(row.rhs < minRhs) { + row.rhs = minRhs; + } + } + } + if(!p._warmStarted) { + let _this = p._impulse; + _this.impulseN = 0; + _this.impulseT = 0; + _this.impulseB = 0; + _this.impulseP = 0; + _this.impulseLX = 0; + _this.impulseLY = 0; + _this.impulseLZ = 0; + } + row.impulse = p._impulse; + } + } + _getPositionSolverInfo(info) { + info.b1 = this._b1; + info.b2 = this._b2; + let normalX; + let normalY; + let normalZ; + normalX = this._manifold._normalX; + normalY = this._manifold._normalY; + normalZ = this._manifold._normalZ; + let num = this._manifold._numPoints; + info.numRows = 0; + let _g = 0; + while(_g < num) { + let p = this._manifold._points[_g++]; + if(p._disabled) { + continue; + } + let row = info.rows[info.numRows++]; + let j = row.jacobianN; + j.lin1X = normalX; + j.lin1Y = normalY; + j.lin1Z = normalZ; + j.lin2X = normalX; + j.lin2Y = normalY; + j.lin2Z = normalZ; + j.ang1X = p._relPos1Y * normalZ - p._relPos1Z * normalY; + j.ang1Y = p._relPos1Z * normalX - p._relPos1X * normalZ; + j.ang1Z = p._relPos1X * normalY - p._relPos1Y * normalX; + j.ang2X = p._relPos2Y * normalZ - p._relPos2Z * normalY; + j.ang2Y = p._relPos2Z * normalX - p._relPos2X * normalZ; + j.ang2Z = p._relPos2X * normalY - p._relPos2Y * normalX; + row.rhs = p._depth - oimo.common.Setting.linearSlop; + if(row.rhs < 0) { + row.rhs = 0; + } + row.impulse = p._impulse; + } + } + _syncManifold() { + this._manifold._updateDepthsAndPositions(this._tf1,this._tf2); + } + getShape1() { + return this._s1; + } + getShape2() { + return this._s2; + } + getManifold() { + return this._manifold; + } + isTouching() { + let _g = 0; + let _g1 = this._manifold._numPoints; + while(_g < _g1) if(this._manifold._points[_g++]._depth >= 0) { + return true; + } + return false; + } +} +oimo.dynamics.constraint.contact.ContactImpulse = class oimo_dynamics_constraint_contact_ContactImpulse { + constructor() { + this.impulseN = 0; + this.impulseT = 0; + this.impulseB = 0; + this.impulseP = 0; + this.impulseLX = 0; + this.impulseLY = 0; + this.impulseLZ = 0; + } + copyFrom(imp) { + this.impulseN = imp.impulseN; + this.impulseT = imp.impulseT; + this.impulseB = imp.impulseB; + this.impulseLX = imp.impulseLX; + this.impulseLY = imp.impulseLY; + this.impulseLZ = imp.impulseLZ; + } +} +oimo.dynamics.constraint.contact.Manifold = class oimo_dynamics_constraint_contact_Manifold { + constructor() { + this._normalX = 0; + this._normalY = 0; + this._normalZ = 0; + this._tangentX = 0; + this._tangentY = 0; + this._tangentZ = 0; + this._binormalX = 0; + this._binormalY = 0; + this._binormalZ = 0; + this._numPoints = 0; + this._points = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = oimo.common.Setting.maxManifoldPoints; + while(_g < _g1) this._points[_g++] = new oimo.dynamics.constraint.contact.ManifoldPoint(); + } + _clear() { + let _g = 0; + let _g1 = this._numPoints; + while(_g < _g1) { + let _this = this._points[_g++]; + _this._localPos1X = 0; + _this._localPos1Y = 0; + _this._localPos1Z = 0; + _this._localPos2X = 0; + _this._localPos2Y = 0; + _this._localPos2Z = 0; + _this._relPos1X = 0; + _this._relPos1Y = 0; + _this._relPos1Z = 0; + _this._relPos2X = 0; + _this._relPos2Y = 0; + _this._relPos2Z = 0; + _this._pos1X = 0; + _this._pos1Y = 0; + _this._pos1Z = 0; + _this._pos2X = 0; + _this._pos2Y = 0; + _this._pos2Z = 0; + _this._depth = 0; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._warmStarted = false; + _this._disabled = false; + _this._id = -1; + } + this._numPoints = 0; + } + _buildBasis(normal) { + this._normalX = normal.x; + this._normalY = normal.y; + this._normalZ = normal.z; + let nx = normal.x; + let ny = normal.y; + let nz = normal.z; + let nx2 = nx * nx; + let ny2 = ny * ny; + let nz2 = nz * nz; + let tx; + let ty; + let tz; + let bx; + let by; + let bz; + if(nx2 < ny2) { + if(nx2 < nz2) { + let invL = 1 / Math.sqrt(ny2 + nz2); + tx = 0; + ty = -nz * invL; + tz = ny * invL; + bx = ny * tz - nz * ty; + by = -nx * tz; + bz = nx * ty; + } else { + let invL = 1 / Math.sqrt(nx2 + ny2); + tx = -ny * invL; + ty = nx * invL; + tz = 0; + bx = -nz * ty; + by = nz * tx; + bz = nx * ty - ny * tx; + } + } else if(ny2 < nz2) { + let invL = 1 / Math.sqrt(nx2 + nz2); + tx = nz * invL; + ty = 0; + tz = -nx * invL; + bx = ny * tz; + by = nz * tx - nx * tz; + bz = -ny * tx; + } else { + let invL = 1 / Math.sqrt(nx2 + ny2); + tx = -ny * invL; + ty = nx * invL; + tz = 0; + bx = -nz * ty; + by = nz * tx; + bz = nx * ty - ny * tx; + } + this._tangentX = tx; + this._tangentY = ty; + this._tangentZ = tz; + this._binormalX = bx; + this._binormalY = by; + this._binormalZ = bz; + } + _updateDepthsAndPositions(tf1,tf2) { + let _g = 0; + let _g1 = this._numPoints; + while(_g < _g1) { + let p = this._points[_g++]; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * p._localPos1X + tf1._rotation01 * p._localPos1Y + tf1._rotation02 * p._localPos1Z; + __tmp__Y = tf1._rotation10 * p._localPos1X + tf1._rotation11 * p._localPos1Y + tf1._rotation12 * p._localPos1Z; + __tmp__Z = tf1._rotation20 * p._localPos1X + tf1._rotation21 * p._localPos1Y + tf1._rotation22 * p._localPos1Z; + p._relPos1X = __tmp__X; + p._relPos1Y = __tmp__Y; + p._relPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * p._localPos2X + tf2._rotation01 * p._localPos2Y + tf2._rotation02 * p._localPos2Z; + __tmp__Y1 = tf2._rotation10 * p._localPos2X + tf2._rotation11 * p._localPos2Y + tf2._rotation12 * p._localPos2Z; + __tmp__Z1 = tf2._rotation20 * p._localPos2X + tf2._rotation21 * p._localPos2Y + tf2._rotation22 * p._localPos2Z; + p._relPos2X = __tmp__X1; + p._relPos2Y = __tmp__Y1; + p._relPos2Z = __tmp__Z1; + p._pos1X = p._relPos1X + tf1._positionX; + p._pos1Y = p._relPos1Y + tf1._positionY; + p._pos1Z = p._relPos1Z + tf1._positionZ; + p._pos2X = p._relPos2X + tf2._positionX; + p._pos2Y = p._relPos2Y + tf2._positionY; + p._pos2Z = p._relPos2Z + tf2._positionZ; + let diffX; + let diffY; + let diffZ; + diffX = p._pos1X - p._pos2X; + diffY = p._pos1Y - p._pos2Y; + diffZ = p._pos1Z - p._pos2Z; + p._depth = -(diffX * this._normalX + diffY * this._normalY + diffZ * this._normalZ); + } + } + getNormal() { + let v = new oimo.common.Vec3(); + v.x = this._normalX; + v.y = this._normalY; + v.z = this._normalZ; + return v; + } + getNormalTo(normal) { + normal.x = this._normalX; + normal.y = this._normalY; + normal.z = this._normalZ; + } + getTangent() { + let v = new oimo.common.Vec3(); + v.x = this._tangentX; + v.y = this._tangentY; + v.z = this._tangentZ; + return v; + } + getTangentTo(tangent) { + tangent.x = this._tangentX; + tangent.y = this._tangentY; + tangent.z = this._tangentZ; + } + getBinormal() { + let v = new oimo.common.Vec3(); + v.x = this._binormalX; + v.y = this._binormalY; + v.z = this._binormalZ; + return v; + } + getBinormalTo(binormal) { + binormal.x = this._binormalX; + binormal.y = this._binormalY; + binormal.z = this._binormalZ; + } + getPoints() { + return this._points; + } + getNumPoints() { + return this._numPoints; + } +} +oimo.dynamics.constraint.contact.ManifoldPoint = class oimo_dynamics_constraint_contact_ManifoldPoint { + constructor() { + this._localPos1X = 0; + this._localPos1Y = 0; + this._localPos1Z = 0; + this._localPos2X = 0; + this._localPos2Y = 0; + this._localPos2Z = 0; + this._relPos1X = 0; + this._relPos1Y = 0; + this._relPos1Z = 0; + this._relPos2X = 0; + this._relPos2Y = 0; + this._relPos2Z = 0; + this._pos1X = 0; + this._pos1Y = 0; + this._pos1Z = 0; + this._pos2X = 0; + this._pos2Y = 0; + this._pos2Z = 0; + this._depth = 0; + this._impulse = new oimo.dynamics.constraint.contact.ContactImpulse(); + this._warmStarted = false; + this._disabled = false; + this._id = -1; + } + getPosition1() { + let v = new oimo.common.Vec3(); + v.x = this._pos1X; + v.y = this._pos1Y; + v.z = this._pos1Z; + return v; + } + getPosition1To(position) { + position.x = this._pos1X; + position.y = this._pos1Y; + position.z = this._pos1Z; + } + getPosition2() { + let v = new oimo.common.Vec3(); + v.x = this._pos2X; + v.y = this._pos2Y; + v.z = this._pos2Z; + return v; + } + getPosition2To(position) { + position.x = this._pos2X; + position.y = this._pos2Y; + position.z = this._pos2Z; + } + getDepth() { + return this._depth; + } + isWarmStarted() { + return this._warmStarted; + } + getNormalImpulse() { + return this._impulse.impulseN; + } + getTangentImpulse() { + return this._impulse.impulseT; + } + getBinormalImpulse() { + return this._impulse.impulseB; + } + isEnabled() { + return !this._disabled; + } +} +oimo.dynamics.constraint.contact.ManifoldUpdater = class oimo_dynamics_constraint_contact_ManifoldUpdater { + constructor(manifold) { + this._manifold = manifold; + this.numOldPoints = 0; + this.oldPoints = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = oimo.common.Setting.maxManifoldPoints; + while(_g < _g1) this.oldPoints[_g++] = new oimo.dynamics.constraint.contact.ManifoldPoint(); + } + removeOutdatedPoints() { + let index = this._manifold._numPoints; + while(--index >= 0) { + let p = this._manifold._points[index]; + let diffX; + let diffY; + let diffZ; + diffX = p._pos1X - p._pos2X; + diffY = p._pos1Y - p._pos2Y; + diffZ = p._pos1Z - p._pos2Z; + let dotN = this._manifold._normalX * diffX + this._manifold._normalY * diffY + this._manifold._normalZ * diffZ; + if(dotN > oimo.common.Setting.contactPersistenceThreshold) { + this.removeManifoldPoint(index); + continue; + } + diffX += this._manifold._normalX * -dotN; + diffY += this._manifold._normalY * -dotN; + diffZ += this._manifold._normalZ * -dotN; + if(diffX * diffX + diffY * diffY + diffZ * diffZ > oimo.common.Setting.contactPersistenceThreshold * oimo.common.Setting.contactPersistenceThreshold) { + this.removeManifoldPoint(index); + continue; + } + } + } + removeManifoldPoint(index) { + let lastIndex = --this._manifold._numPoints; + if(index != lastIndex) { + let tmp = this._manifold._points[index]; + this._manifold._points[index] = this._manifold._points[lastIndex]; + this._manifold._points[lastIndex] = tmp; + } + let _this = this._manifold._points[lastIndex]; + _this._localPos1X = 0; + _this._localPos1Y = 0; + _this._localPos1Z = 0; + _this._localPos2X = 0; + _this._localPos2Y = 0; + _this._localPos2Z = 0; + _this._relPos1X = 0; + _this._relPos1Y = 0; + _this._relPos1Z = 0; + _this._relPos2X = 0; + _this._relPos2Y = 0; + _this._relPos2Z = 0; + _this._pos1X = 0; + _this._pos1Y = 0; + _this._pos1Z = 0; + _this._pos2X = 0; + _this._pos2Y = 0; + _this._pos2Z = 0; + _this._depth = 0; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._warmStarted = false; + _this._disabled = false; + _this._id = -1; + } + addManifoldPoint(point,tf1,tf2) { + let num = this._manifold._numPoints; + if(num == oimo.common.Setting.maxManifoldPoints) { + let targetIndex = this.computeTargetIndex(point,tf1,tf2); + let _this = this._manifold._points[targetIndex]; + let v = point.position1; + _this._pos1X = v.x; + _this._pos1Y = v.y; + _this._pos1Z = v.z; + let v1 = point.position2; + _this._pos2X = v1.x; + _this._pos2Y = v1.y; + _this._pos2Z = v1.z; + _this._relPos1X = _this._pos1X - tf1._positionX; + _this._relPos1Y = _this._pos1Y - tf1._positionY; + _this._relPos1Z = _this._pos1Z - tf1._positionZ; + _this._relPos2X = _this._pos2X - tf2._positionX; + _this._relPos2Y = _this._pos2Y - tf2._positionY; + _this._relPos2Z = _this._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * _this._relPos1X + tf1._rotation10 * _this._relPos1Y + tf1._rotation20 * _this._relPos1Z; + __tmp__Y = tf1._rotation01 * _this._relPos1X + tf1._rotation11 * _this._relPos1Y + tf1._rotation21 * _this._relPos1Z; + __tmp__Z = tf1._rotation02 * _this._relPos1X + tf1._rotation12 * _this._relPos1Y + tf1._rotation22 * _this._relPos1Z; + _this._localPos1X = __tmp__X; + _this._localPos1Y = __tmp__Y; + _this._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * _this._relPos2X + tf2._rotation10 * _this._relPos2Y + tf2._rotation20 * _this._relPos2Z; + __tmp__Y1 = tf2._rotation01 * _this._relPos2X + tf2._rotation11 * _this._relPos2Y + tf2._rotation21 * _this._relPos2Z; + __tmp__Z1 = tf2._rotation02 * _this._relPos2X + tf2._rotation12 * _this._relPos2Y + tf2._rotation22 * _this._relPos2Z; + _this._localPos2X = __tmp__X1; + _this._localPos2Y = __tmp__Y1; + _this._localPos2Z = __tmp__Z1; + _this._depth = point.depth; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._id = point.id; + _this._warmStarted = false; + _this._disabled = false; + return; + } + let _this = this._manifold._points[num]; + let v = point.position1; + _this._pos1X = v.x; + _this._pos1Y = v.y; + _this._pos1Z = v.z; + let v1 = point.position2; + _this._pos2X = v1.x; + _this._pos2Y = v1.y; + _this._pos2Z = v1.z; + _this._relPos1X = _this._pos1X - tf1._positionX; + _this._relPos1Y = _this._pos1Y - tf1._positionY; + _this._relPos1Z = _this._pos1Z - tf1._positionZ; + _this._relPos2X = _this._pos2X - tf2._positionX; + _this._relPos2Y = _this._pos2Y - tf2._positionY; + _this._relPos2Z = _this._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * _this._relPos1X + tf1._rotation10 * _this._relPos1Y + tf1._rotation20 * _this._relPos1Z; + __tmp__Y = tf1._rotation01 * _this._relPos1X + tf1._rotation11 * _this._relPos1Y + tf1._rotation21 * _this._relPos1Z; + __tmp__Z = tf1._rotation02 * _this._relPos1X + tf1._rotation12 * _this._relPos1Y + tf1._rotation22 * _this._relPos1Z; + _this._localPos1X = __tmp__X; + _this._localPos1Y = __tmp__Y; + _this._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * _this._relPos2X + tf2._rotation10 * _this._relPos2Y + tf2._rotation20 * _this._relPos2Z; + __tmp__Y1 = tf2._rotation01 * _this._relPos2X + tf2._rotation11 * _this._relPos2Y + tf2._rotation21 * _this._relPos2Z; + __tmp__Z1 = tf2._rotation02 * _this._relPos2X + tf2._rotation12 * _this._relPos2Y + tf2._rotation22 * _this._relPos2Z; + _this._localPos2X = __tmp__X1; + _this._localPos2Y = __tmp__Y1; + _this._localPos2Z = __tmp__Z1; + _this._depth = point.depth; + let _this1 = _this._impulse; + _this1.impulseN = 0; + _this1.impulseT = 0; + _this1.impulseB = 0; + _this1.impulseP = 0; + _this1.impulseLX = 0; + _this1.impulseLY = 0; + _this1.impulseLZ = 0; + _this._id = point.id; + _this._warmStarted = false; + _this._disabled = false; + this._manifold._numPoints++; + } + computeTargetIndex(newPoint,tf1,tf2) { + let p1 = this._manifold._points[0]; + let p2 = this._manifold._points[1]; + let p3 = this._manifold._points[2]; + let p4 = this._manifold._points[3]; + let maxDepth = p1._depth; + let maxDepthIndex = 0; + if(p2._depth > maxDepth) { + maxDepth = p2._depth; + maxDepthIndex = 1; + } + if(p3._depth > maxDepth) { + maxDepth = p3._depth; + maxDepthIndex = 2; + } + if(p4._depth > maxDepth) { + + maxDepthIndex = 3; + } + let rp1X; + let rp1Y; + let rp1Z; + let v = newPoint.position1; + rp1X = v.x; + rp1Y = v.y; + rp1Z = v.z; + rp1X -= tf1._positionX; + rp1Y -= tf1._positionY; + rp1Z -= tf1._positionZ; + let p1X = p2._relPos1X; + let p1Y = p2._relPos1Y; + let p1Z = p2._relPos1Z; + let p2X = p3._relPos1X; + let p2Y = p3._relPos1Y; + let p2Z = p3._relPos1Z; + let p3X = p4._relPos1X; + let p3Y = p4._relPos1Y; + let p3Z = p4._relPos1Z; + let v12X; + let v12Y; + let v12Z; + let v34X; + let v34Y; + let v34Z; + let v13X; + let v13Y; + let v13Z; + let v24X; + let v24Y; + let v24Z; + let v14X; + let v14Y; + let v14Z; + let v23X; + let v23Y; + let v23Z; + v12X = p2X - p1X; + v12Y = p2Y - p1Y; + v12Z = p2Z - p1Z; + v34X = rp1X - p3X; + v34Y = rp1Y - p3Y; + v34Z = rp1Z - p3Z; + v13X = p3X - p1X; + v13Y = p3Y - p1Y; + v13Z = p3Z - p1Z; + v24X = rp1X - p2X; + v24Y = rp1Y - p2Y; + v24Z = rp1Z - p2Z; + v14X = rp1X - p1X; + v14Y = rp1Y - p1Y; + v14Z = rp1Z - p1Z; + v23X = p3X - p2X; + v23Y = p3Y - p2Y; + v23Z = p3Z - p2Z; + let cross1X; + let cross1Y; + let cross1Z; + let cross2X; + let cross2Y; + let cross2Z; + let cross3X; + let cross3Y; + let cross3Z; + cross1X = v12Y * v34Z - v12Z * v34Y; + cross1Y = v12Z * v34X - v12X * v34Z; + cross1Z = v12X * v34Y - v12Y * v34X; + cross2X = v13Y * v24Z - v13Z * v24Y; + cross2Y = v13Z * v24X - v13X * v24Z; + cross2Z = v13X * v24Y - v13Y * v24X; + cross3X = v14Y * v23Z - v14Z * v23Y; + cross3Y = v14Z * v23X - v14X * v23Z; + cross3Z = v14X * v23Y - v14Y * v23X; + let a1 = cross1X * cross1X + cross1Y * cross1Y + cross1Z * cross1Z; + let a2 = cross2X * cross2X + cross2Y * cross2Y + cross2Z * cross2Z; + let a3 = cross3X * cross3X + cross3Y * cross3Y + cross3Z * cross3Z; + let p1X1 = p1._relPos1X; + let p1Y1 = p1._relPos1Y; + let p1Z1 = p1._relPos1Z; + let p2X1 = p3._relPos1X; + let p2Y1 = p3._relPos1Y; + let p2Z1 = p3._relPos1Z; + let p3X1 = p4._relPos1X; + let p3Y1 = p4._relPos1Y; + let p3Z1 = p4._relPos1Z; + let v12X1; + let v12Y1; + let v12Z1; + let v34X1; + let v34Y1; + let v34Z1; + let v13X1; + let v13Y1; + let v13Z1; + let v24X1; + let v24Y1; + let v24Z1; + let v14X1; + let v14Y1; + let v14Z1; + let v23X1; + let v23Y1; + let v23Z1; + v12X1 = p2X1 - p1X1; + v12Y1 = p2Y1 - p1Y1; + v12Z1 = p2Z1 - p1Z1; + v34X1 = rp1X - p3X1; + v34Y1 = rp1Y - p3Y1; + v34Z1 = rp1Z - p3Z1; + v13X1 = p3X1 - p1X1; + v13Y1 = p3Y1 - p1Y1; + v13Z1 = p3Z1 - p1Z1; + v24X1 = rp1X - p2X1; + v24Y1 = rp1Y - p2Y1; + v24Z1 = rp1Z - p2Z1; + v14X1 = rp1X - p1X1; + v14Y1 = rp1Y - p1Y1; + v14Z1 = rp1Z - p1Z1; + v23X1 = p3X1 - p2X1; + v23Y1 = p3Y1 - p2Y1; + v23Z1 = p3Z1 - p2Z1; + let cross1X1; + let cross1Y1; + let cross1Z1; + let cross2X1; + let cross2Y1; + let cross2Z1; + let cross3X1; + let cross3Y1; + let cross3Z1; + cross1X1 = v12Y1 * v34Z1 - v12Z1 * v34Y1; + cross1Y1 = v12Z1 * v34X1 - v12X1 * v34Z1; + cross1Z1 = v12X1 * v34Y1 - v12Y1 * v34X1; + cross2X1 = v13Y1 * v24Z1 - v13Z1 * v24Y1; + cross2Y1 = v13Z1 * v24X1 - v13X1 * v24Z1; + cross2Z1 = v13X1 * v24Y1 - v13Y1 * v24X1; + cross3X1 = v14Y1 * v23Z1 - v14Z1 * v23Y1; + cross3Y1 = v14Z1 * v23X1 - v14X1 * v23Z1; + cross3Z1 = v14X1 * v23Y1 - v14Y1 * v23X1; + let a11 = cross1X1 * cross1X1 + cross1Y1 * cross1Y1 + cross1Z1 * cross1Z1; + let a21 = cross2X1 * cross2X1 + cross2Y1 * cross2Y1 + cross2Z1 * cross2Z1; + let a31 = cross3X1 * cross3X1 + cross3Y1 * cross3Y1 + cross3Z1 * cross3Z1; + let a22 = a11 > a21 ? a11 > a31 ? a11 : a31 : a21 > a31 ? a21 : a31; + let p1X2 = p1._relPos1X; + let p1Y2 = p1._relPos1Y; + let p1Z2 = p1._relPos1Z; + let p2X2 = p2._relPos1X; + let p2Y2 = p2._relPos1Y; + let p2Z2 = p2._relPos1Z; + let p3X2 = p4._relPos1X; + let p3Y2 = p4._relPos1Y; + let p3Z2 = p4._relPos1Z; + let v12X2; + let v12Y2; + let v12Z2; + let v34X2; + let v34Y2; + let v34Z2; + let v13X2; + let v13Y2; + let v13Z2; + let v24X2; + let v24Y2; + let v24Z2; + let v14X2; + let v14Y2; + let v14Z2; + let v23X2; + let v23Y2; + let v23Z2; + v12X2 = p2X2 - p1X2; + v12Y2 = p2Y2 - p1Y2; + v12Z2 = p2Z2 - p1Z2; + v34X2 = rp1X - p3X2; + v34Y2 = rp1Y - p3Y2; + v34Z2 = rp1Z - p3Z2; + v13X2 = p3X2 - p1X2; + v13Y2 = p3Y2 - p1Y2; + v13Z2 = p3Z2 - p1Z2; + v24X2 = rp1X - p2X2; + v24Y2 = rp1Y - p2Y2; + v24Z2 = rp1Z - p2Z2; + v14X2 = rp1X - p1X2; + v14Y2 = rp1Y - p1Y2; + v14Z2 = rp1Z - p1Z2; + v23X2 = p3X2 - p2X2; + v23Y2 = p3Y2 - p2Y2; + v23Z2 = p3Z2 - p2Z2; + let cross1X2; + let cross1Y2; + let cross1Z2; + let cross2X2; + let cross2Y2; + let cross2Z2; + let cross3X2; + let cross3Y2; + let cross3Z2; + cross1X2 = v12Y2 * v34Z2 - v12Z2 * v34Y2; + cross1Y2 = v12Z2 * v34X2 - v12X2 * v34Z2; + cross1Z2 = v12X2 * v34Y2 - v12Y2 * v34X2; + cross2X2 = v13Y2 * v24Z2 - v13Z2 * v24Y2; + cross2Y2 = v13Z2 * v24X2 - v13X2 * v24Z2; + cross2Z2 = v13X2 * v24Y2 - v13Y2 * v24X2; + cross3X2 = v14Y2 * v23Z2 - v14Z2 * v23Y2; + cross3Y2 = v14Z2 * v23X2 - v14X2 * v23Z2; + cross3Z2 = v14X2 * v23Y2 - v14Y2 * v23X2; + let a12 = cross1X2 * cross1X2 + cross1Y2 * cross1Y2 + cross1Z2 * cross1Z2; + let a23 = cross2X2 * cross2X2 + cross2Y2 * cross2Y2 + cross2Z2 * cross2Z2; + let a32 = cross3X2 * cross3X2 + cross3Y2 * cross3Y2 + cross3Z2 * cross3Z2; + let a33 = a12 > a23 ? a12 > a32 ? a12 : a32 : a23 > a32 ? a23 : a32; + let p1X3 = p1._relPos1X; + let p1Y3 = p1._relPos1Y; + let p1Z3 = p1._relPos1Z; + let p2X3 = p2._relPos1X; + let p2Y3 = p2._relPos1Y; + let p2Z3 = p2._relPos1Z; + let p3X3 = p3._relPos1X; + let p3Y3 = p3._relPos1Y; + let p3Z3 = p3._relPos1Z; + let v12X3; + let v12Y3; + let v12Z3; + let v34X3; + let v34Y3; + let v34Z3; + let v13X3; + let v13Y3; + let v13Z3; + let v24X3; + let v24Y3; + let v24Z3; + let v14X3; + let v14Y3; + let v14Z3; + let v23X3; + let v23Y3; + let v23Z3; + v12X3 = p2X3 - p1X3; + v12Y3 = p2Y3 - p1Y3; + v12Z3 = p2Z3 - p1Z3; + v34X3 = rp1X - p3X3; + v34Y3 = rp1Y - p3Y3; + v34Z3 = rp1Z - p3Z3; + v13X3 = p3X3 - p1X3; + v13Y3 = p3Y3 - p1Y3; + v13Z3 = p3Z3 - p1Z3; + v24X3 = rp1X - p2X3; + v24Y3 = rp1Y - p2Y3; + v24Z3 = rp1Z - p2Z3; + v14X3 = rp1X - p1X3; + v14Y3 = rp1Y - p1Y3; + v14Z3 = rp1Z - p1Z3; + v23X3 = p3X3 - p2X3; + v23Y3 = p3Y3 - p2Y3; + v23Z3 = p3Z3 - p2Z3; + let cross1X3; + let cross1Y3; + let cross1Z3; + let cross2X3; + let cross2Y3; + let cross2Z3; + let cross3X3; + let cross3Y3; + let cross3Z3; + cross1X3 = v12Y3 * v34Z3 - v12Z3 * v34Y3; + cross1Y3 = v12Z3 * v34X3 - v12X3 * v34Z3; + cross1Z3 = v12X3 * v34Y3 - v12Y3 * v34X3; + cross2X3 = v13Y3 * v24Z3 - v13Z3 * v24Y3; + cross2Y3 = v13Z3 * v24X3 - v13X3 * v24Z3; + cross2Z3 = v13X3 * v24Y3 - v13Y3 * v24X3; + cross3X3 = v14Y3 * v23Z3 - v14Z3 * v23Y3; + cross3Y3 = v14Z3 * v23X3 - v14X3 * v23Z3; + cross3Z3 = v14X3 * v23Y3 - v14Y3 * v23X3; + let a13 = cross1X3 * cross1X3 + cross1Y3 * cross1Y3 + cross1Z3 * cross1Z3; + let a24 = cross2X3 * cross2X3 + cross2Y3 * cross2Y3 + cross2Z3 * cross2Z3; + let a34 = cross3X3 * cross3X3 + cross3Y3 * cross3Y3 + cross3Z3 * cross3Z3; + let a4 = a13 > a24 ? a13 > a34 ? a13 : a34 : a24 > a34 ? a24 : a34; + let max = a1 > a2 ? a1 > a3 ? a1 : a3 : a2 > a3 ? a2 : a3; + let target = 0; + if(a22 > max && maxDepthIndex != 1 || maxDepthIndex == 0) { + max = a22; + target = 1; + } + if(a33 > max && maxDepthIndex != 2) { + max = a33; + target = 2; + } + if(a4 > max && maxDepthIndex != 3) { + + target = 3; + } + return target; + } + computeRelativePositions(tf1,tf2) { + let num = this._manifold._numPoints; + let _g = 0; + while(_g < num) { + let p = this._manifold._points[_g++]; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * p._localPos1X + tf1._rotation01 * p._localPos1Y + tf1._rotation02 * p._localPos1Z; + __tmp__Y = tf1._rotation10 * p._localPos1X + tf1._rotation11 * p._localPos1Y + tf1._rotation12 * p._localPos1Z; + __tmp__Z = tf1._rotation20 * p._localPos1X + tf1._rotation21 * p._localPos1Y + tf1._rotation22 * p._localPos1Z; + p._relPos1X = __tmp__X; + p._relPos1Y = __tmp__Y; + p._relPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * p._localPos2X + tf2._rotation01 * p._localPos2Y + tf2._rotation02 * p._localPos2Z; + __tmp__Y1 = tf2._rotation10 * p._localPos2X + tf2._rotation11 * p._localPos2Y + tf2._rotation12 * p._localPos2Z; + __tmp__Z1 = tf2._rotation20 * p._localPos2X + tf2._rotation21 * p._localPos2Y + tf2._rotation22 * p._localPos2Z; + p._relPos2X = __tmp__X1; + p._relPos2Y = __tmp__Y1; + p._relPos2Z = __tmp__Z1; + p._warmStarted = true; + } + } + findNearestContactPointIndex(target,tf1,tf2) { + let nearestSq = oimo.common.Setting.contactPersistenceThreshold * oimo.common.Setting.contactPersistenceThreshold; + let idx = -1; + let _g = 0; + let _g1 = this._manifold._numPoints; + while(_g < _g1) { + let i = _g++; + let mp = this._manifold._points[i]; + let rp1X; + let rp1Y; + let rp1Z; + let rp2X; + let rp2Y; + let rp2Z; + let v = target.position1; + rp1X = v.x; + rp1Y = v.y; + rp1Z = v.z; + let v1 = target.position2; + rp2X = v1.x; + rp2Y = v1.y; + rp2Z = v1.z; + rp1X -= tf1._positionX; + rp1Y -= tf1._positionY; + rp1Z -= tf1._positionZ; + rp2X -= tf2._positionX; + rp2Y -= tf2._positionY; + rp2Z -= tf2._positionZ; + let diff1X; + let diff1Y; + let diff1Z; + let diff2X; + let diff2Y; + let diff2Z; + diff1X = mp._relPos1X - rp1X; + diff1Y = mp._relPos1Y - rp1Y; + diff1Z = mp._relPos1Z - rp1Z; + diff2X = mp._relPos2X - rp2X; + diff2Y = mp._relPos2Y - rp2Y; + diff2Z = mp._relPos2Z - rp2Z; + let sq1 = diff1X * diff1X + diff1Y * diff1Y + diff1Z * diff1Z; + let sq2 = diff2X * diff2X + diff2Y * diff2Y + diff2Z * diff2Z; + let d = sq1 < sq2 ? sq1 : sq2; + if(d < nearestSq) { + nearestSq = d; + idx = i; + } + } + return idx; + } + totalUpdate(result,tf1,tf2) { + this.numOldPoints = this._manifold._numPoints; + let _g = 0; + let _g1 = this.numOldPoints; + while(_g < _g1) { + let i = _g++; + let _this = this.oldPoints[i]; + let cp = this._manifold._points[i]; + _this._localPos1X = cp._localPos1X; + _this._localPos1Y = cp._localPos1Y; + _this._localPos1Z = cp._localPos1Z; + _this._localPos2X = cp._localPos2X; + _this._localPos2Y = cp._localPos2Y; + _this._localPos2Z = cp._localPos2Z; + _this._relPos1X = cp._relPos1X; + _this._relPos1Y = cp._relPos1Y; + _this._relPos1Z = cp._relPos1Z; + _this._relPos2X = cp._relPos2X; + _this._relPos2Y = cp._relPos2Y; + _this._relPos2Z = cp._relPos2Z; + _this._pos1X = cp._pos1X; + _this._pos1Y = cp._pos1Y; + _this._pos1Z = cp._pos1Z; + _this._pos2X = cp._pos2X; + _this._pos2Y = cp._pos2Y; + _this._pos2Z = cp._pos2Z; + _this._depth = cp._depth; + _this._impulse.copyFrom(cp._impulse); + _this._id = cp._id; + _this._warmStarted = cp._warmStarted; + _this._disabled = false; + } + let num = result.numPoints; + this._manifold._numPoints = num; + let _g2 = 0; + while(_g2 < num) { + let i = _g2++; + let p = this._manifold._points[i]; + let ref = result.points[i]; + let v = ref.position1; + p._pos1X = v.x; + p._pos1Y = v.y; + p._pos1Z = v.z; + let v1 = ref.position2; + p._pos2X = v1.x; + p._pos2Y = v1.y; + p._pos2Z = v1.z; + p._relPos1X = p._pos1X - tf1._positionX; + p._relPos1Y = p._pos1Y - tf1._positionY; + p._relPos1Z = p._pos1Z - tf1._positionZ; + p._relPos2X = p._pos2X - tf2._positionX; + p._relPos2Y = p._pos2Y - tf2._positionY; + p._relPos2Z = p._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * p._relPos1X + tf1._rotation10 * p._relPos1Y + tf1._rotation20 * p._relPos1Z; + __tmp__Y = tf1._rotation01 * p._relPos1X + tf1._rotation11 * p._relPos1Y + tf1._rotation21 * p._relPos1Z; + __tmp__Z = tf1._rotation02 * p._relPos1X + tf1._rotation12 * p._relPos1Y + tf1._rotation22 * p._relPos1Z; + p._localPos1X = __tmp__X; + p._localPos1Y = __tmp__Y; + p._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * p._relPos2X + tf2._rotation10 * p._relPos2Y + tf2._rotation20 * p._relPos2Z; + __tmp__Y1 = tf2._rotation01 * p._relPos2X + tf2._rotation11 * p._relPos2Y + tf2._rotation21 * p._relPos2Z; + __tmp__Z1 = tf2._rotation02 * p._relPos2X + tf2._rotation12 * p._relPos2Y + tf2._rotation22 * p._relPos2Z; + p._localPos2X = __tmp__X1; + p._localPos2Y = __tmp__Y1; + p._localPos2Z = __tmp__Z1; + p._depth = ref.depth; + let _this = p._impulse; + _this.impulseN = 0; + _this.impulseT = 0; + _this.impulseB = 0; + _this.impulseP = 0; + _this.impulseLX = 0; + _this.impulseLY = 0; + _this.impulseLZ = 0; + p._id = ref.id; + p._warmStarted = false; + p._disabled = false; + let _g = 0; + let _g1 = this.numOldPoints; + while(_g < _g1) { + let ocp = this.oldPoints[_g++]; + if(p._id == ocp._id) { + p._impulse.copyFrom(ocp._impulse); + p._warmStarted = true; + break; + } + } + } + } + incrementalUpdate(result,tf1,tf2) { + this._manifold._updateDepthsAndPositions(tf1,tf2); + let _g = 0; + let _g1 = this._manifold._numPoints; + while(_g < _g1) this._manifold._points[_g++]._warmStarted = true; + let newPoint = result.points[0]; + let index = this.findNearestContactPointIndex(newPoint,tf1,tf2); + if(index == -1) { + this.addManifoldPoint(newPoint,tf1,tf2); + } else { + let cp = this._manifold._points[index]; + let v = newPoint.position1; + cp._pos1X = v.x; + cp._pos1Y = v.y; + cp._pos1Z = v.z; + let v1 = newPoint.position2; + cp._pos2X = v1.x; + cp._pos2Y = v1.y; + cp._pos2Z = v1.z; + cp._relPos1X = cp._pos1X - tf1._positionX; + cp._relPos1Y = cp._pos1Y - tf1._positionY; + cp._relPos1Z = cp._pos1Z - tf1._positionZ; + cp._relPos2X = cp._pos2X - tf2._positionX; + cp._relPos2Y = cp._pos2Y - tf2._positionY; + cp._relPos2Z = cp._pos2Z - tf2._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * cp._relPos1X + tf1._rotation10 * cp._relPos1Y + tf1._rotation20 * cp._relPos1Z; + __tmp__Y = tf1._rotation01 * cp._relPos1X + tf1._rotation11 * cp._relPos1Y + tf1._rotation21 * cp._relPos1Z; + __tmp__Z = tf1._rotation02 * cp._relPos1X + tf1._rotation12 * cp._relPos1Y + tf1._rotation22 * cp._relPos1Z; + cp._localPos1X = __tmp__X; + cp._localPos1Y = __tmp__Y; + cp._localPos1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * cp._relPos2X + tf2._rotation10 * cp._relPos2Y + tf2._rotation20 * cp._relPos2Z; + __tmp__Y1 = tf2._rotation01 * cp._relPos2X + tf2._rotation11 * cp._relPos2Y + tf2._rotation21 * cp._relPos2Z; + __tmp__Z1 = tf2._rotation02 * cp._relPos2X + tf2._rotation12 * cp._relPos2Y + tf2._rotation22 * cp._relPos2Z; + cp._localPos2X = __tmp__X1; + cp._localPos2Y = __tmp__Y1; + cp._localPos2Z = __tmp__Z1; + cp._depth = newPoint.depth; + } + this.removeOutdatedPoints(); + } +} +if(!oimo.dynamics.constraint.info) oimo.dynamics.constraint.info = {}; +oimo.dynamics.constraint.info.JacobianRow = class oimo_dynamics_constraint_info_JacobianRow { + constructor() { + this.lin1X = 0; + this.lin1Y = 0; + this.lin1Z = 0; + this.lin2X = 0; + this.lin2Y = 0; + this.lin2Z = 0; + this.ang1X = 0; + this.ang1Y = 0; + this.ang1Z = 0; + this.ang2X = 0; + this.ang2Y = 0; + this.ang2Z = 0; + this.flag = 0; + } + updateSparsity() { + this.flag = 0; + if(!(this.lin1X == 0 && this.lin1Y == 0 && this.lin1Z == 0) || !(this.lin2X == 0 && this.lin2Y == 0 && this.lin2Z == 0)) { + this.flag |= 1; + } + if(!(this.ang1X == 0 && this.ang1Y == 0 && this.ang1Z == 0) || !(this.ang2X == 0 && this.ang2Y == 0 && this.ang2Z == 0)) { + this.flag |= 2; + } + } +} +if(!oimo.dynamics.constraint.info.contact) oimo.dynamics.constraint.info.contact = {}; +oimo.dynamics.constraint.info.contact.ContactSolverInfo = class oimo_dynamics_constraint_info_contact_ContactSolverInfo { + constructor() { + this.b1 = null; + this.b2 = null; + this.numRows = 0; + this.rows = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = this.rows.length; + while(_g < _g1) this.rows[_g++] = new oimo.dynamics.constraint.info.contact.ContactSolverInfoRow(); + } +} +oimo.dynamics.constraint.info.contact.ContactSolverInfoRow = class oimo_dynamics_constraint_info_contact_ContactSolverInfoRow { + constructor() { + this.jacobianN = new oimo.dynamics.constraint.info.JacobianRow(); + this.jacobianT = new oimo.dynamics.constraint.info.JacobianRow(); + this.jacobianB = new oimo.dynamics.constraint.info.JacobianRow(); + this.rhs = 0; + this.cfm = 0; + this.friction = 0; + this.impulse = null; + } +} +if(!oimo.dynamics.constraint.info.joint) oimo.dynamics.constraint.info.joint = {}; +oimo.dynamics.constraint.info.joint.JointSolverInfo = class oimo_dynamics_constraint_info_joint_JointSolverInfo { + constructor() { + this.b1 = null; + this.b2 = null; + this.numRows = 0; + this.rows = new Array(oimo.common.Setting.maxJacobianRows); + let _g = 0; + let _g1 = this.rows.length; + while(_g < _g1) this.rows[_g++] = new oimo.dynamics.constraint.info.joint.JointSolverInfoRow(); + } +} +oimo.dynamics.constraint.info.joint.JointSolverInfoRow = class oimo_dynamics_constraint_info_joint_JointSolverInfoRow { + constructor() { + this.jacobian = new oimo.dynamics.constraint.info.JacobianRow(); + this.rhs = 0; + this.cfm = 0; + this.minImpulse = 0; + this.maxImpulse = 0; + this.motorSpeed = 0; + this.motorMaxImpulse = 0; + this.impulse = null; + } +} +if(!oimo.dynamics.constraint.joint) oimo.dynamics.constraint.joint = {}; +oimo.dynamics.constraint.joint.BasisTracker = class oimo_dynamics_constraint_joint_BasisTracker { + constructor(joint) { + this.joint = joint; + this.xX = 0; + this.xY = 0; + this.xZ = 0; + this.yX = 0; + this.yY = 0; + this.yZ = 0; + this.zX = 0; + this.zY = 0; + this.zZ = 0; + } +} +oimo.dynamics.constraint.joint.Joint = class oimo_dynamics_constraint_joint_Joint { + constructor(config,type) { + this._link1 = new oimo.dynamics.constraint.joint.JointLink(this); + this._link2 = new oimo.dynamics.constraint.joint.JointLink(this); + this._positionCorrectionAlgorithm = oimo.common.Setting.defaultJointPositionCorrectionAlgorithm; + this._type = type; + this._world = null; + this._b1 = config.rigidBody1; + this._b2 = config.rigidBody2; + this._allowCollision = config.allowCollision; + this._breakForce = config.breakForce; + this._breakTorque = config.breakTorque; + switch(config.solverType) { + case 0: + this._solver = new oimo.dynamics.constraint.solver.pgs.PgsJointConstraintSolver(this); + break; + case 1: + this._solver = new oimo.dynamics.constraint.solver.direct.DirectJointConstraintSolver(this); + break; + } + let v = config.localAnchor1; + this._localAnchor1X = v.x; + this._localAnchor1Y = v.y; + this._localAnchor1Z = v.z; + let v1 = config.localAnchor2; + this._localAnchor2X = v1.x; + this._localAnchor2Y = v1.y; + this._localAnchor2Z = v1.z; + this._relativeAnchor1X = 0; + this._relativeAnchor1Y = 0; + this._relativeAnchor1Z = 0; + this._relativeAnchor2X = 0; + this._relativeAnchor2Y = 0; + this._relativeAnchor2Z = 0; + this._anchor1X = 0; + this._anchor1Y = 0; + this._anchor1Z = 0; + this._anchor2X = 0; + this._anchor2Y = 0; + this._anchor2Z = 0; + this._localBasisX1X = 0; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + this._localBasisY1X = 0; + this._localBasisY1Y = 0; + this._localBasisY1Z = 0; + this._localBasisZ1X = 0; + this._localBasisZ1Y = 0; + this._localBasisZ1Z = 0; + this._localBasisX2X = 0; + this._localBasisX2Y = 0; + this._localBasisX2Z = 0; + this._localBasisY2X = 0; + this._localBasisY2Y = 0; + this._localBasisY2Z = 0; + this._localBasisZ2X = 0; + this._localBasisZ2Y = 0; + this._localBasisZ2Z = 0; + this._impulses = new Array(oimo.common.Setting.maxJacobianRows); + let _g = 0; + let _g1 = oimo.common.Setting.maxJacobianRows; + while(_g < _g1) this._impulses[_g++] = new oimo.dynamics.constraint.joint.JointImpulse(); + } + buildLocalBasesFromX() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + if(this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z == 0) { + this._localBasisX2X = 1; + this._localBasisX2Y = 0; + this._localBasisX2Z = 0; + } else { + let l = this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX2X *= l; + this._localBasisX2Y *= l; + this._localBasisX2Z *= l; + } + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let d = this._localBasisX1X * this._localBasisX2X + this._localBasisX1Y * this._localBasisX2Y + this._localBasisX1Z * this._localBasisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = this._localBasisX1Y * this._localBasisX2Z - this._localBasisX1Z * this._localBasisX2Y; + cY = this._localBasisX1Z * this._localBasisX2X - this._localBasisX1X * this._localBasisX2Z; + cZ = this._localBasisX1X * this._localBasisX2Y - this._localBasisX1Y * this._localBasisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x21 = x1 * x1; + let y21 = y1 * y1; + let z21 = z1 * z1; + let d1; + if(x21 < y21) { + if(x21 < z21) { + d1 = 1 / Math.sqrt(y21 + z21); + this._localBasisY1X = 0; + this._localBasisY1Y = z1 * d1; + this._localBasisY1Z = -y1 * d1; + } else { + d1 = 1 / Math.sqrt(x21 + y21); + this._localBasisY1X = y1 * d1; + this._localBasisY1Y = -x1 * d1; + this._localBasisY1Z = 0; + } + } else if(y21 < z21) { + d1 = 1 / Math.sqrt(z21 + x21); + this._localBasisY1X = -z1 * d1; + this._localBasisY1Y = 0; + this._localBasisY1Z = x1 * d1; + } else { + d1 = 1 / Math.sqrt(x21 + y21); + this._localBasisY1X = y1 * d1; + this._localBasisY1Y = -x1 * d1; + this._localBasisY1Z = 0; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * this._localBasisX1X + slerpM01 * this._localBasisX1Y + slerpM02 * this._localBasisX1Z; + __tmp__Y = slerpM10 * this._localBasisX1X + slerpM11 * this._localBasisX1Y + slerpM12 * this._localBasisX1Z; + __tmp__Z = slerpM20 * this._localBasisX1X + slerpM21 * this._localBasisX1Y + slerpM22 * this._localBasisX1Z; + this._localBasisX2X = __tmp__X; + this._localBasisX2Y = __tmp__Y; + this._localBasisX2Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * this._localBasisY1X + slerpM01 * this._localBasisY1Y + slerpM02 * this._localBasisY1Z; + __tmp__Y1 = slerpM10 * this._localBasisY1X + slerpM11 * this._localBasisY1Y + slerpM12 * this._localBasisY1Z; + __tmp__Z1 = slerpM20 * this._localBasisY1X + slerpM21 * this._localBasisY1Y + slerpM22 * this._localBasisY1Z; + this._localBasisY2X = __tmp__X1; + this._localBasisY2Y = __tmp__Y1; + this._localBasisY2Z = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = slerpM00 * this._localBasisZ1X + slerpM01 * this._localBasisZ1Y + slerpM02 * this._localBasisZ1Z; + __tmp__Y2 = slerpM10 * this._localBasisZ1X + slerpM11 * this._localBasisZ1Y + slerpM12 * this._localBasisZ1Z; + __tmp__Z2 = slerpM20 * this._localBasisZ1X + slerpM21 * this._localBasisZ1Y + slerpM22 * this._localBasisZ1Z; + this._localBasisZ2X = __tmp__X2; + this._localBasisZ2Y = __tmp__Y2; + this._localBasisZ2Z = __tmp__Z2; + } + buildLocalBasesFromXY() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + if(this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z == 0) { + this._localBasisX2X = 1; + this._localBasisX2Y = 0; + this._localBasisX2Z = 0; + } else { + let l = this._localBasisX2X * this._localBasisX2X + this._localBasisX2Y * this._localBasisX2Y + this._localBasisX2Z * this._localBasisX2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX2X *= l; + this._localBasisX2Y *= l; + this._localBasisX2Z *= l; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + this._localBasisZ2X = this._localBasisX2Y * this._localBasisY2Z - this._localBasisX2Z * this._localBasisY2Y; + this._localBasisZ2Y = this._localBasisX2Z * this._localBasisY2X - this._localBasisX2X * this._localBasisY2Z; + this._localBasisZ2Z = this._localBasisX2X * this._localBasisY2Y - this._localBasisX2Y * this._localBasisY2X; + if(this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z == 0) { + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this._localBasisY1X = 0; + this._localBasisY1Y = z1 * d; + this._localBasisY1Z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this._localBasisY1X = -z1 * d; + this._localBasisY1Y = 0; + this._localBasisY1Z = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + } else { + let l = this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ1X *= l; + this._localBasisZ1Y *= l; + this._localBasisZ1Z *= l; + this._localBasisY1X = this._localBasisZ1Y * this._localBasisX1Z - this._localBasisZ1Z * this._localBasisX1Y; + this._localBasisY1Y = this._localBasisZ1Z * this._localBasisX1X - this._localBasisZ1X * this._localBasisX1Z; + this._localBasisY1Z = this._localBasisZ1X * this._localBasisX1Y - this._localBasisZ1Y * this._localBasisX1X; + } + if(this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z == 0) { + let x1 = this._localBasisX2X; + let y1 = this._localBasisX2Y; + let z1 = this._localBasisX2Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this._localBasisY2X = 0; + this._localBasisY2Y = z1 * d; + this._localBasisY2Z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY2X = y1 * d; + this._localBasisY2Y = -x1 * d; + this._localBasisY2Z = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this._localBasisY2X = -z1 * d; + this._localBasisY2Y = 0; + this._localBasisY2Z = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY2X = y1 * d; + this._localBasisY2Y = -x1 * d; + this._localBasisY2Z = 0; + } + this._localBasisZ2X = this._localBasisX2Y * this._localBasisY2Z - this._localBasisX2Z * this._localBasisY2Y; + this._localBasisZ2Y = this._localBasisX2Z * this._localBasisY2X - this._localBasisX2X * this._localBasisY2Z; + this._localBasisZ2Z = this._localBasisX2X * this._localBasisY2Y - this._localBasisX2Y * this._localBasisY2X; + } else { + let l = this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ2X *= l; + this._localBasisZ2Y *= l; + this._localBasisZ2Z *= l; + this._localBasisY2X = this._localBasisZ2Y * this._localBasisX2Z - this._localBasisZ2Z * this._localBasisX2Y; + this._localBasisY2Y = this._localBasisZ2Z * this._localBasisX2X - this._localBasisZ2X * this._localBasisX2Z; + this._localBasisY2Z = this._localBasisZ2X * this._localBasisX2Y - this._localBasisZ2Y * this._localBasisX2X; + } + } + buildLocalBasesFromX1Z2() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + if(this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z == 0) { + this._localBasisZ2X = 0; + this._localBasisZ2Y = 0; + this._localBasisZ2Z = 1; + } else { + let l = this._localBasisZ2X * this._localBasisZ2X + this._localBasisZ2Y * this._localBasisZ2Y + this._localBasisZ2Z * this._localBasisZ2Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ2X *= l; + this._localBasisZ2Y *= l; + this._localBasisZ2Z *= l; + } + let tf1 = this._b1._transform; + let tf2 = this._b2._transform; + let worldX1X; + let worldX1Y; + let worldX1Z; + let worldZ1X; + let worldZ1Y; + let worldZ1Z; + let worldYX; + let worldYY; + let worldYZ; + let worldX2X; + let worldX2Y; + let worldX2Z; + let worldZ2X; + let worldZ2Y; + let worldZ2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * this._localBasisX1X + tf1._rotation01 * this._localBasisX1Y + tf1._rotation02 * this._localBasisX1Z; + __tmp__Y = tf1._rotation10 * this._localBasisX1X + tf1._rotation11 * this._localBasisX1Y + tf1._rotation12 * this._localBasisX1Z; + __tmp__Z = tf1._rotation20 * this._localBasisX1X + tf1._rotation21 * this._localBasisX1Y + tf1._rotation22 * this._localBasisX1Z; + worldX1X = __tmp__X; + worldX1Y = __tmp__Y; + worldX1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * this._localBasisZ2X + tf2._rotation01 * this._localBasisZ2Y + tf2._rotation02 * this._localBasisZ2Z; + __tmp__Y1 = tf2._rotation10 * this._localBasisZ2X + tf2._rotation11 * this._localBasisZ2Y + tf2._rotation12 * this._localBasisZ2Z; + __tmp__Z1 = tf2._rotation20 * this._localBasisZ2X + tf2._rotation21 * this._localBasisZ2Y + tf2._rotation22 * this._localBasisZ2Z; + worldZ2X = __tmp__X1; + worldZ2Y = __tmp__Y1; + worldZ2Z = __tmp__Z1; + worldYX = worldZ2Y * worldX1Z - worldZ2Z * worldX1Y; + worldYY = worldZ2Z * worldX1X - worldZ2X * worldX1Z; + worldYZ = worldZ2X * worldX1Y - worldZ2Y * worldX1X; + if(worldYX * worldYX + worldYY * worldYY + worldYZ * worldYZ == 0) { + let x1 = worldX1X; + let y1 = worldX1Y; + let z1 = worldX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + worldYX = 0; + worldYY = z1 * d; + worldYZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + worldYX = y1 * d; + worldYY = -x1 * d; + worldYZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + worldYX = -z1 * d; + worldYY = 0; + worldYZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + worldYX = y1 * d; + worldYY = -x1 * d; + worldYZ = 0; + } + } + worldZ1X = worldX1Y * worldYZ - worldX1Z * worldYY; + worldZ1Y = worldX1Z * worldYX - worldX1X * worldYZ; + worldZ1Z = worldX1X * worldYY - worldX1Y * worldYX; + worldX2X = worldYY * worldZ2Z - worldYZ * worldZ2Y; + worldX2Y = worldYZ * worldZ2X - worldYX * worldZ2Z; + worldX2Z = worldYX * worldZ2Y - worldYY * worldZ2X; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf1._rotation00 * worldX1X + tf1._rotation10 * worldX1Y + tf1._rotation20 * worldX1Z; + __tmp__Y2 = tf1._rotation01 * worldX1X + tf1._rotation11 * worldX1Y + tf1._rotation21 * worldX1Z; + __tmp__Z2 = tf1._rotation02 * worldX1X + tf1._rotation12 * worldX1Y + tf1._rotation22 * worldX1Z; + this._localBasisX1X = __tmp__X2; + this._localBasisX1Y = __tmp__Y2; + this._localBasisX1Z = __tmp__Z2; + let __tmp__X3; + let __tmp__Y3; + let __tmp__Z3; + __tmp__X3 = tf1._rotation00 * worldYX + tf1._rotation10 * worldYY + tf1._rotation20 * worldYZ; + __tmp__Y3 = tf1._rotation01 * worldYX + tf1._rotation11 * worldYY + tf1._rotation21 * worldYZ; + __tmp__Z3 = tf1._rotation02 * worldYX + tf1._rotation12 * worldYY + tf1._rotation22 * worldYZ; + this._localBasisY1X = __tmp__X3; + this._localBasisY1Y = __tmp__Y3; + this._localBasisY1Z = __tmp__Z3; + let __tmp__X4; + let __tmp__Y4; + let __tmp__Z4; + __tmp__X4 = tf1._rotation00 * worldZ1X + tf1._rotation10 * worldZ1Y + tf1._rotation20 * worldZ1Z; + __tmp__Y4 = tf1._rotation01 * worldZ1X + tf1._rotation11 * worldZ1Y + tf1._rotation21 * worldZ1Z; + __tmp__Z4 = tf1._rotation02 * worldZ1X + tf1._rotation12 * worldZ1Y + tf1._rotation22 * worldZ1Z; + this._localBasisZ1X = __tmp__X4; + this._localBasisZ1Y = __tmp__Y4; + this._localBasisZ1Z = __tmp__Z4; + let __tmp__X5; + let __tmp__Y5; + let __tmp__Z5; + __tmp__X5 = tf2._rotation00 * worldX2X + tf2._rotation10 * worldX2Y + tf2._rotation20 * worldX2Z; + __tmp__Y5 = tf2._rotation01 * worldX2X + tf2._rotation11 * worldX2Y + tf2._rotation21 * worldX2Z; + __tmp__Z5 = tf2._rotation02 * worldX2X + tf2._rotation12 * worldX2Y + tf2._rotation22 * worldX2Z; + this._localBasisX2X = __tmp__X5; + this._localBasisX2Y = __tmp__Y5; + this._localBasisX2Z = __tmp__Z5; + let __tmp__X6; + let __tmp__Y6; + let __tmp__Z6; + __tmp__X6 = tf2._rotation00 * worldYX + tf2._rotation10 * worldYY + tf2._rotation20 * worldYZ; + __tmp__Y6 = tf2._rotation01 * worldYX + tf2._rotation11 * worldYY + tf2._rotation21 * worldYZ; + __tmp__Z6 = tf2._rotation02 * worldYX + tf2._rotation12 * worldYY + tf2._rotation22 * worldYZ; + this._localBasisY2X = __tmp__X6; + this._localBasisY2Y = __tmp__Y6; + this._localBasisY2Z = __tmp__Z6; + let __tmp__X7; + let __tmp__Y7; + let __tmp__Z7; + __tmp__X7 = tf2._rotation00 * worldZ2X + tf2._rotation10 * worldZ2Y + tf2._rotation20 * worldZ2Z; + __tmp__Y7 = tf2._rotation01 * worldZ2X + tf2._rotation11 * worldZ2Y + tf2._rotation21 * worldZ2Z; + __tmp__Z7 = tf2._rotation02 * worldZ2X + tf2._rotation12 * worldZ2Y + tf2._rotation22 * worldZ2Z; + this._localBasisZ2X = __tmp__X7; + this._localBasisZ2Y = __tmp__Y7; + this._localBasisZ2Z = __tmp__Z7; + } + buildLocalBasesFromXY1X2() { + if(this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z == 0) { + this._localBasisX1X = 1; + this._localBasisX1Y = 0; + this._localBasisX1Z = 0; + } else { + let l = this._localBasisX1X * this._localBasisX1X + this._localBasisX1Y * this._localBasisX1Y + this._localBasisX1Z * this._localBasisX1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisX1X *= l; + this._localBasisX1Y *= l; + this._localBasisX1Z *= l; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + if(this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z == 0) { + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + this._localBasisY1X = 0; + this._localBasisY1Y = z1 * d; + this._localBasisY1Z = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + this._localBasisY1X = -z1 * d; + this._localBasisY1Y = 0; + this._localBasisY1Z = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + this._localBasisY1X = y1 * d; + this._localBasisY1Y = -x1 * d; + this._localBasisY1Z = 0; + } + this._localBasisZ1X = this._localBasisX1Y * this._localBasisY1Z - this._localBasisX1Z * this._localBasisY1Y; + this._localBasisZ1Y = this._localBasisX1Z * this._localBasisY1X - this._localBasisX1X * this._localBasisY1Z; + this._localBasisZ1Z = this._localBasisX1X * this._localBasisY1Y - this._localBasisX1Y * this._localBasisY1X; + } else { + let l = this._localBasisZ1X * this._localBasisZ1X + this._localBasisZ1Y * this._localBasisZ1Y + this._localBasisZ1Z * this._localBasisZ1Z; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._localBasisZ1X *= l; + this._localBasisZ1Y *= l; + this._localBasisZ1Z *= l; + this._localBasisY1X = this._localBasisZ1Y * this._localBasisX1Z - this._localBasisZ1Z * this._localBasisX1Y; + this._localBasisY1Y = this._localBasisZ1Z * this._localBasisX1X - this._localBasisZ1X * this._localBasisX1Z; + this._localBasisY1Z = this._localBasisZ1X * this._localBasisX1Y - this._localBasisZ1Y * this._localBasisX1X; + } + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let d = this._localBasisX1X * this._localBasisX2X + this._localBasisX1Y * this._localBasisX2Y + this._localBasisX1Z * this._localBasisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = this._localBasisX1X; + let y1 = this._localBasisX1Y; + let z1 = this._localBasisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = this._localBasisX1Y * this._localBasisX2Z - this._localBasisX1Z * this._localBasisX2Y; + cY = this._localBasisX1Z * this._localBasisX2X - this._localBasisX1X * this._localBasisX2Z; + cZ = this._localBasisX1X * this._localBasisX2Y - this._localBasisX1Y * this._localBasisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * this._localBasisX1X + slerpM01 * this._localBasisX1Y + slerpM02 * this._localBasisX1Z; + __tmp__Y = slerpM10 * this._localBasisX1X + slerpM11 * this._localBasisX1Y + slerpM12 * this._localBasisX1Z; + __tmp__Z = slerpM20 * this._localBasisX1X + slerpM21 * this._localBasisX1Y + slerpM22 * this._localBasisX1Z; + this._localBasisX2X = __tmp__X; + this._localBasisX2Y = __tmp__Y; + this._localBasisX2Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * this._localBasisY1X + slerpM01 * this._localBasisY1Y + slerpM02 * this._localBasisY1Z; + __tmp__Y1 = slerpM10 * this._localBasisY1X + slerpM11 * this._localBasisY1Y + slerpM12 * this._localBasisY1Z; + __tmp__Z1 = slerpM20 * this._localBasisY1X + slerpM21 * this._localBasisY1Y + slerpM22 * this._localBasisY1Z; + this._localBasisY2X = __tmp__X1; + this._localBasisY2Y = __tmp__Y1; + this._localBasisY2Z = __tmp__Z1; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = slerpM00 * this._localBasisZ1X + slerpM01 * this._localBasisZ1Y + slerpM02 * this._localBasisZ1Z; + __tmp__Y2 = slerpM10 * this._localBasisZ1X + slerpM11 * this._localBasisZ1Y + slerpM12 * this._localBasisZ1Z; + __tmp__Z2 = slerpM20 * this._localBasisZ1X + slerpM21 * this._localBasisZ1Y + slerpM22 * this._localBasisZ1Z; + this._localBasisZ2X = __tmp__X2; + this._localBasisZ2Y = __tmp__Y2; + this._localBasisZ2Z = __tmp__Z2; + } + setSolverInfoRowLinear(row,diff,lm,mass,sd,timeStep,isPositionPart) { + let cfmFactor; + let erp; + let slop = oimo.common.Setting.linearSlop; + if(isPositionPart) { + cfmFactor = 0; + erp = 1; + } else { + if(sd.frequency > 0) { + slop = 0; + let omega = 6.28318530717958 * sd.frequency; + let zeta = sd.dampingRatio; + if(zeta < oimo.common.Setting.minSpringDamperDampingRatio) { + zeta = oimo.common.Setting.minSpringDamperDampingRatio; + } + let h = timeStep.dt; + let c = 2 * zeta * omega; + let k = omega * omega; + if(sd.useSymplecticEuler) { + cfmFactor = 1 / (h * c); + erp = k / c; + } else { + cfmFactor = 1 / (h * (h * k + c)); + erp = k / (h * k + c); + } + } else { + cfmFactor = 0; + erp = this.getErp(timeStep,false); + } + if(lm.motorForce > 0) { + row.motorSpeed = lm.motorSpeed; + row.motorMaxImpulse = lm.motorForce * timeStep.dt; + } else { + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + } + } + let lower = lm.lowerLimit; + let upper = lm.upperLimit; + let minImp; + let maxImp; + let error; + if(lower > upper) { + minImp = 0; + maxImp = 0; + error = 0; + } else if(lower == upper) { + minImp = -1e65536; + maxImp = 1e65536; + error = diff - lower; + } else if(diff < lower) { + minImp = -1e65536; + maxImp = 0; + error = diff - lower + slop; + if(error > 0) { + error = 0; + } + } else if(diff > upper) { + minImp = 0; + maxImp = 1e65536; + error = diff - upper - slop; + if(error < 0) { + error = 0; + } + } else { + minImp = 0; + maxImp = 0; + error = 0; + } + row.minImpulse = minImp; + row.maxImpulse = maxImp; + row.cfm = cfmFactor * (mass == 0 ? 0 : 1 / mass); + row.rhs = error * erp; + } + setSolverInfoRowAngular(row,diff,lm,mass,sd,timeStep,isPositionPart) { + let cfmFactor; + let erp; + let slop = oimo.common.Setting.angularSlop; + if(isPositionPart) { + cfmFactor = 0; + erp = 1; + } else { + if(sd.frequency > 0) { + slop = 0; + let omega = 6.28318530717958 * sd.frequency; + let zeta = sd.dampingRatio; + if(zeta < oimo.common.Setting.minSpringDamperDampingRatio) { + zeta = oimo.common.Setting.minSpringDamperDampingRatio; + } + let h = timeStep.dt; + let c = 2 * zeta * omega; + let k = omega * omega; + if(sd.useSymplecticEuler) { + cfmFactor = 1 / (h * c); + erp = k / c; + } else { + cfmFactor = 1 / (h * (h * k + c)); + erp = k / (h * k + c); + } + } else { + cfmFactor = 0; + erp = this.getErp(timeStep,false); + } + if(lm.motorTorque > 0) { + row.motorSpeed = lm.motorSpeed; + row.motorMaxImpulse = lm.motorTorque * timeStep.dt; + } else { + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + } + } + let lower = lm.lowerLimit; + let upper = lm.upperLimit; + let mid = (lower + upper) * 0.5; + diff -= mid; + diff = ((diff + 3.14159265358979) % 6.28318530717958 + 6.28318530717958) % 6.28318530717958 - 3.14159265358979; + diff += mid; + let minImp; + let maxImp; + let error; + if(lower > upper) { + minImp = 0; + maxImp = 0; + error = 0; + } else if(lower == upper) { + minImp = -1e65536; + maxImp = 1e65536; + error = diff - lower; + } else if(diff < lower) { + minImp = -1e65536; + maxImp = 0; + error = diff - lower + slop; + if(error > 0) { + error = 0; + } + } else if(diff > upper) { + minImp = 0; + maxImp = 1e65536; + error = diff - upper - slop; + if(error < 0) { + error = 0; + } + } else { + minImp = 0; + maxImp = 0; + error = 0; + } + row.minImpulse = minImp; + row.maxImpulse = maxImp; + row.cfm = cfmFactor * (mass == 0 ? 0 : 1 / mass); + row.rhs = error * erp; + } + getErp(timeStep,isPositionPart) { + if(isPositionPart) { + return 1; + } else if(this._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE) { + return timeStep.invDt * oimo.common.Setting.velocityBaumgarte; + } else { + return 0; + } + } + computeEffectiveInertiaMoment(axisX,axisY,axisZ) { + let ia1X; + let ia1Y; + let ia1Z; + let ia2X; + let ia2Y; + let ia2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._b1._invInertia00 * axisX + this._b1._invInertia01 * axisY + this._b1._invInertia02 * axisZ; + __tmp__Y = this._b1._invInertia10 * axisX + this._b1._invInertia11 * axisY + this._b1._invInertia12 * axisZ; + __tmp__Z = this._b1._invInertia20 * axisX + this._b1._invInertia21 * axisY + this._b1._invInertia22 * axisZ; + ia1X = __tmp__X; + ia1Y = __tmp__Y; + ia1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this._b2._invInertia00 * axisX + this._b2._invInertia01 * axisY + this._b2._invInertia02 * axisZ; + __tmp__Y1 = this._b2._invInertia10 * axisX + this._b2._invInertia11 * axisY + this._b2._invInertia12 * axisZ; + __tmp__Z1 = this._b2._invInertia20 * axisX + this._b2._invInertia21 * axisY + this._b2._invInertia22 * axisZ; + ia2X = __tmp__X1; + ia2Y = __tmp__Y1; + ia2Z = __tmp__Z1; + let invI1 = ia1X * axisX + ia1Y * axisY + ia1Z * axisZ; + let invI2 = ia2X * axisX + ia2Y * axisY + ia2Z * axisZ; + if(invI1 > 0) { + let dot = axisX * this._relativeAnchor1X + axisY * this._relativeAnchor1Y + axisZ * this._relativeAnchor1Z; + let projsq = this._relativeAnchor1X * this._relativeAnchor1X + this._relativeAnchor1Y * this._relativeAnchor1Y + this._relativeAnchor1Z * this._relativeAnchor1Z - dot * dot; + if(projsq > 0) { + if(this._b1._invMass > 0) { + invI1 = 1 / (1 / invI1 + this._b1._mass * projsq); + } else { + invI1 = 0; + } + } + } + if(invI2 > 0) { + let dot = axisX * this._relativeAnchor2X + axisY * this._relativeAnchor2Y + axisZ * this._relativeAnchor2Z; + let projsq = this._relativeAnchor2X * this._relativeAnchor2X + this._relativeAnchor2Y * this._relativeAnchor2Y + this._relativeAnchor2Z * this._relativeAnchor2Z - dot * dot; + if(projsq > 0) { + if(this._b2._invMass > 0) { + invI2 = 1 / (1 / invI2 + this._b2._mass * projsq); + } else { + invI2 = 0; + } + } + } + if(invI1 + invI2 == 0) { + return 0; + } else { + return 1 / (invI1 + invI2); + } + } + computeEffectiveInertiaMoment2(axis1X,axis1Y,axis1Z,axis2X,axis2Y,axis2Z) { + let ia1X; + let ia1Y; + let ia1Z; + let ia2X; + let ia2Y; + let ia2Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._b1._invInertia00 * axis1X + this._b1._invInertia01 * axis1Y + this._b1._invInertia02 * axis1Z; + __tmp__Y = this._b1._invInertia10 * axis1X + this._b1._invInertia11 * axis1Y + this._b1._invInertia12 * axis1Z; + __tmp__Z = this._b1._invInertia20 * axis1X + this._b1._invInertia21 * axis1Y + this._b1._invInertia22 * axis1Z; + ia1X = __tmp__X; + ia1Y = __tmp__Y; + ia1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = this._b2._invInertia00 * axis2X + this._b2._invInertia01 * axis2Y + this._b2._invInertia02 * axis2Z; + __tmp__Y1 = this._b2._invInertia10 * axis2X + this._b2._invInertia11 * axis2Y + this._b2._invInertia12 * axis2Z; + __tmp__Z1 = this._b2._invInertia20 * axis2X + this._b2._invInertia21 * axis2Y + this._b2._invInertia22 * axis2Z; + ia2X = __tmp__X1; + ia2Y = __tmp__Y1; + ia2Z = __tmp__Z1; + let invI1 = ia1X * axis1X + ia1Y * axis1Y + ia1Z * axis1Z; + let invI2 = ia2X * axis2X + ia2Y * axis2Y + ia2Z * axis2Z; + if(invI1 > 0) { + let rsq = this._relativeAnchor1X * this._relativeAnchor1X + this._relativeAnchor1Y * this._relativeAnchor1Y + this._relativeAnchor1Z * this._relativeAnchor1Z; + let dot = axis1X * this._relativeAnchor1X + axis1Y * this._relativeAnchor1Y + axis1Z * this._relativeAnchor1Z; + let projsq = rsq * rsq - dot * dot; + if(projsq > 0) { + if(this._b1._invMass > 0) { + invI1 = 1 / (1 / invI1 + this._b1._mass * projsq); + } else { + invI1 = 0; + } + } + } + if(invI2 > 0) { + let rsq = this._relativeAnchor2X * this._relativeAnchor2X + this._relativeAnchor2Y * this._relativeAnchor2Y + this._relativeAnchor2Z * this._relativeAnchor2Z; + let dot = axis2X * this._relativeAnchor2X + axis2Y * this._relativeAnchor2Y + axis2Z * this._relativeAnchor2Z; + let projsq = rsq * rsq - dot * dot; + if(projsq > 0) { + if(this._b2._invMass > 0) { + invI2 = 1 / (1 / invI2 + this._b2._mass * projsq); + } else { + invI2 = 0; + } + } + } + if(invI1 + invI2 == 0) { + return 0; + } else { + return 1 / (invI1 + invI2); + } + } + _syncAnchors() { + let tf1 = this._b1._transform; + let tf2 = this._b2._transform; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * this._localAnchor1X + tf1._rotation01 * this._localAnchor1Y + tf1._rotation02 * this._localAnchor1Z; + __tmp__Y = tf1._rotation10 * this._localAnchor1X + tf1._rotation11 * this._localAnchor1Y + tf1._rotation12 * this._localAnchor1Z; + __tmp__Z = tf1._rotation20 * this._localAnchor1X + tf1._rotation21 * this._localAnchor1Y + tf1._rotation22 * this._localAnchor1Z; + this._relativeAnchor1X = __tmp__X; + this._relativeAnchor1Y = __tmp__Y; + this._relativeAnchor1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * this._localAnchor2X + tf2._rotation01 * this._localAnchor2Y + tf2._rotation02 * this._localAnchor2Z; + __tmp__Y1 = tf2._rotation10 * this._localAnchor2X + tf2._rotation11 * this._localAnchor2Y + tf2._rotation12 * this._localAnchor2Z; + __tmp__Z1 = tf2._rotation20 * this._localAnchor2X + tf2._rotation21 * this._localAnchor2Y + tf2._rotation22 * this._localAnchor2Z; + this._relativeAnchor2X = __tmp__X1; + this._relativeAnchor2Y = __tmp__Y1; + this._relativeAnchor2Z = __tmp__Z1; + this._anchor1X = this._relativeAnchor1X + tf1._positionX; + this._anchor1Y = this._relativeAnchor1Y + tf1._positionY; + this._anchor1Z = this._relativeAnchor1Z + tf1._positionZ; + this._anchor2X = this._relativeAnchor2X + tf2._positionX; + this._anchor2Y = this._relativeAnchor2Y + tf2._positionY; + this._anchor2Z = this._relativeAnchor2Z + tf2._positionZ; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = tf1._rotation00 * this._localBasisX1X + tf1._rotation01 * this._localBasisX1Y + tf1._rotation02 * this._localBasisX1Z; + __tmp__Y2 = tf1._rotation10 * this._localBasisX1X + tf1._rotation11 * this._localBasisX1Y + tf1._rotation12 * this._localBasisX1Z; + __tmp__Z2 = tf1._rotation20 * this._localBasisX1X + tf1._rotation21 * this._localBasisX1Y + tf1._rotation22 * this._localBasisX1Z; + this._basisX1X = __tmp__X2; + this._basisX1Y = __tmp__Y2; + this._basisX1Z = __tmp__Z2; + let __tmp__X3; + let __tmp__Y3; + let __tmp__Z3; + __tmp__X3 = tf1._rotation00 * this._localBasisY1X + tf1._rotation01 * this._localBasisY1Y + tf1._rotation02 * this._localBasisY1Z; + __tmp__Y3 = tf1._rotation10 * this._localBasisY1X + tf1._rotation11 * this._localBasisY1Y + tf1._rotation12 * this._localBasisY1Z; + __tmp__Z3 = tf1._rotation20 * this._localBasisY1X + tf1._rotation21 * this._localBasisY1Y + tf1._rotation22 * this._localBasisY1Z; + this._basisY1X = __tmp__X3; + this._basisY1Y = __tmp__Y3; + this._basisY1Z = __tmp__Z3; + let __tmp__X4; + let __tmp__Y4; + let __tmp__Z4; + __tmp__X4 = tf1._rotation00 * this._localBasisZ1X + tf1._rotation01 * this._localBasisZ1Y + tf1._rotation02 * this._localBasisZ1Z; + __tmp__Y4 = tf1._rotation10 * this._localBasisZ1X + tf1._rotation11 * this._localBasisZ1Y + tf1._rotation12 * this._localBasisZ1Z; + __tmp__Z4 = tf1._rotation20 * this._localBasisZ1X + tf1._rotation21 * this._localBasisZ1Y + tf1._rotation22 * this._localBasisZ1Z; + this._basisZ1X = __tmp__X4; + this._basisZ1Y = __tmp__Y4; + this._basisZ1Z = __tmp__Z4; + let __tmp__X5; + let __tmp__Y5; + let __tmp__Z5; + __tmp__X5 = tf2._rotation00 * this._localBasisX2X + tf2._rotation01 * this._localBasisX2Y + tf2._rotation02 * this._localBasisX2Z; + __tmp__Y5 = tf2._rotation10 * this._localBasisX2X + tf2._rotation11 * this._localBasisX2Y + tf2._rotation12 * this._localBasisX2Z; + __tmp__Z5 = tf2._rotation20 * this._localBasisX2X + tf2._rotation21 * this._localBasisX2Y + tf2._rotation22 * this._localBasisX2Z; + this._basisX2X = __tmp__X5; + this._basisX2Y = __tmp__Y5; + this._basisX2Z = __tmp__Z5; + let __tmp__X6; + let __tmp__Y6; + let __tmp__Z6; + __tmp__X6 = tf2._rotation00 * this._localBasisY2X + tf2._rotation01 * this._localBasisY2Y + tf2._rotation02 * this._localBasisY2Z; + __tmp__Y6 = tf2._rotation10 * this._localBasisY2X + tf2._rotation11 * this._localBasisY2Y + tf2._rotation12 * this._localBasisY2Z; + __tmp__Z6 = tf2._rotation20 * this._localBasisY2X + tf2._rotation21 * this._localBasisY2Y + tf2._rotation22 * this._localBasisY2Z; + this._basisY2X = __tmp__X6; + this._basisY2Y = __tmp__Y6; + this._basisY2Z = __tmp__Z6; + let __tmp__X7; + let __tmp__Y7; + let __tmp__Z7; + __tmp__X7 = tf2._rotation00 * this._localBasisZ2X + tf2._rotation01 * this._localBasisZ2Y + tf2._rotation02 * this._localBasisZ2Z; + __tmp__Y7 = tf2._rotation10 * this._localBasisZ2X + tf2._rotation11 * this._localBasisZ2Y + tf2._rotation12 * this._localBasisZ2Z; + __tmp__Z7 = tf2._rotation20 * this._localBasisZ2X + tf2._rotation21 * this._localBasisZ2Y + tf2._rotation22 * this._localBasisZ2Z; + this._basisZ2X = __tmp__X7; + this._basisZ2Y = __tmp__Y7; + this._basisZ2Z = __tmp__Z7; + } + _getVelocitySolverInfo(timeStep,info) { + info.b1 = this._b1; + info.b2 = this._b2; + info.numRows = 0; + } + _getPositionSolverInfo(info) { + info.b1 = this._b1; + info.b2 = this._b2; + info.numRows = 0; + } + _checkDestruction() { + let torqueSq = this._appliedTorqueX * this._appliedTorqueX + this._appliedTorqueY * this._appliedTorqueY + this._appliedTorqueZ * this._appliedTorqueZ; + if(this._breakForce > 0 && this._appliedForceX * this._appliedForceX + this._appliedForceY * this._appliedForceY + this._appliedForceZ * this._appliedForceZ > this._breakForce * this._breakForce) { + this._world.removeJoint(this); + return; + } + if(this._breakTorque > 0 && torqueSq > this._breakTorque * this._breakTorque) { + this._world.removeJoint(this); + return; + } + } + getRigidBody1() { + return this._b1; + } + getRigidBody2() { + return this._b2; + } + getType() { + return this._type; + } + getAnchor1() { + let v = new oimo.common.Vec3(); + v.x = this._anchor1X; + v.y = this._anchor1Y; + v.z = this._anchor1Z; + return v; + } + getAnchor2() { + let v = new oimo.common.Vec3(); + v.x = this._anchor2X; + v.y = this._anchor2Y; + v.z = this._anchor2Z; + return v; + } + getAnchor1To(anchor) { + anchor.x = this._anchor1X; + anchor.y = this._anchor1Y; + anchor.z = this._anchor1Z; + } + getAnchor2To(anchor) { + anchor.x = this._anchor2X; + anchor.y = this._anchor2Y; + anchor.z = this._anchor2Z; + } + getLocalAnchor1() { + let v = new oimo.common.Vec3(); + v.x = this._localAnchor1X; + v.y = this._localAnchor1Y; + v.z = this._localAnchor1Z; + return v; + } + getLocalAnchor2() { + let v = new oimo.common.Vec3(); + v.x = this._localAnchor2X; + v.y = this._localAnchor2Y; + v.z = this._localAnchor2Z; + return v; + } + getLocalAnchor1To(localAnchor) { + localAnchor.x = this._localAnchor1X; + localAnchor.y = this._localAnchor1Y; + localAnchor.z = this._localAnchor1Z; + } + getLocalAnchor2To(localAnchor) { + localAnchor.x = this._localAnchor2X; + localAnchor.y = this._localAnchor2Y; + localAnchor.z = this._localAnchor2Z; + } + getBasis1() { + let m = new oimo.common.Mat3(); + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX1X; + b01 = this._basisY1X; + b02 = this._basisZ1X; + b10 = this._basisX1Y; + b11 = this._basisY1Y; + b12 = this._basisZ1Y; + b20 = this._basisX1Z; + b21 = this._basisY1Z; + b22 = this._basisZ1Z; + m.e00 = b00; + m.e01 = b01; + m.e02 = b02; + m.e10 = b10; + m.e11 = b11; + m.e12 = b12; + m.e20 = b20; + m.e21 = b21; + m.e22 = b22; + return m; + } + getBasis2() { + let m = new oimo.common.Mat3(); + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX2X; + b01 = this._basisY2X; + b02 = this._basisZ2X; + b10 = this._basisX2Y; + b11 = this._basisY2Y; + b12 = this._basisZ2Y; + b20 = this._basisX2Z; + b21 = this._basisY2Z; + b22 = this._basisZ2Z; + m.e00 = b00; + m.e01 = b01; + m.e02 = b02; + m.e10 = b10; + m.e11 = b11; + m.e12 = b12; + m.e20 = b20; + m.e21 = b21; + m.e22 = b22; + return m; + } + getBasis1To(basis) { + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX1X; + b01 = this._basisY1X; + b02 = this._basisZ1X; + b10 = this._basisX1Y; + b11 = this._basisY1Y; + b12 = this._basisZ1Y; + b20 = this._basisX1Z; + b21 = this._basisY1Z; + b22 = this._basisZ1Z; + basis.e00 = b00; + basis.e01 = b01; + basis.e02 = b02; + basis.e10 = b10; + basis.e11 = b11; + basis.e12 = b12; + basis.e20 = b20; + basis.e21 = b21; + basis.e22 = b22; + } + getBasis2To(basis) { + let b00; + let b01; + let b02; + let b10; + let b11; + let b12; + let b20; + let b21; + let b22; + b00 = this._basisX2X; + b01 = this._basisY2X; + b02 = this._basisZ2X; + b10 = this._basisX2Y; + b11 = this._basisY2Y; + b12 = this._basisZ2Y; + b20 = this._basisX2Z; + b21 = this._basisY2Z; + b22 = this._basisZ2Z; + basis.e00 = b00; + basis.e01 = b01; + basis.e02 = b02; + basis.e10 = b10; + basis.e11 = b11; + basis.e12 = b12; + basis.e20 = b20; + basis.e21 = b21; + basis.e22 = b22; + } + getAllowCollision() { + return this._allowCollision; + } + setAllowCollision(allowCollision) { + this._allowCollision = allowCollision; + } + getBreakForce() { + return this._breakForce; + } + setBreakForce(breakForce) { + this._breakForce = breakForce; + } + getBreakTorque() { + return this._breakTorque; + } + setBreakTorque(breakTorque) { + this._breakTorque = breakTorque; + } + getPositionCorrectionAlgorithm() { + return this._positionCorrectionAlgorithm; + } + setPositionCorrectionAlgorithm(positionCorrectionAlgorithm) { + switch(positionCorrectionAlgorithm) { + case 0:case 1:case 2: + break; + default: + throw new Error("invalid position correction algorithm id: " + positionCorrectionAlgorithm); + } + this._positionCorrectionAlgorithm = positionCorrectionAlgorithm; + } + getAppliedForce() { + let v = new oimo.common.Vec3(); + v.x = this._appliedForceX; + v.y = this._appliedForceY; + v.z = this._appliedForceZ; + return v; + } + getAppliedForceTo(appliedForce) { + appliedForce.x = this._appliedForceX; + appliedForce.y = this._appliedForceY; + appliedForce.z = this._appliedForceZ; + } + getAppliedTorque() { + let v = new oimo.common.Vec3(); + v.x = this._appliedTorqueX; + v.y = this._appliedTorqueY; + v.z = this._appliedTorqueZ; + return v; + } + getAppliedTorqueTo(appliedTorque) { + appliedTorque.x = this._appliedTorqueX; + appliedTorque.y = this._appliedTorqueY; + appliedTorque.z = this._appliedTorqueZ; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.constraint.joint.CylindricalJoint = class oimo_dynamics_constraint_joint_CylindricalJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,2); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisX2X = v1.x; + this._localBasisX2Y = v1.y; + this._localBasisX2Z = v1.z; + this.buildLocalBasesFromX(); + this.angle = 0; + this.angularErrorY = 0; + this.angularErrorZ = 0; + this.translation = 0; + this.linearErrorY = 0; + this.linearErrorZ = 0; + this._basis = new oimo.dynamics.constraint.joint.BasisTracker(this); + this._translSd = config.translationalSpringDamper.clone(); + this._translLm = config.translationalLimitMotor.clone(); + this._rotSd = config.rotationalSpringDamper.clone(); + this._rotLm = config.rotationalLimitMotor.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linRhsY = this.linearErrorY * erp; + let linRhsZ = this.linearErrorZ * erp; + let angRhsY = this.angularErrorY * erp; + let angRhsZ = this.angularErrorZ * erp; + let j; + let translationalMotorMass = 1 / (this._b1._invMass + this._b2._invMass); + let rotationalMotorMass = this.computeEffectiveInertiaMoment(this._basis.xX,this._basis.xY,this._basis.xZ); + if(this._translSd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translation,this._translLm,translationalMotorMass,this._translSd,timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basis.xX; + j.lin1Y = this._basis.xY; + j.lin1Z = this._basis.xZ; + j.lin2X = this._basis.xX; + j.lin2Y = this._basis.xY; + j.lin2Z = this._basis.xZ; + j.ang1X = this._relativeAnchor1Y * this._basis.xZ - this._relativeAnchor1Z * this._basis.xY; + j.ang1Y = this._relativeAnchor1Z * this._basis.xX - this._relativeAnchor1X * this._basis.xZ; + j.ang1Z = this._relativeAnchor1X * this._basis.xY - this._relativeAnchor1Y * this._basis.xX; + j.ang2X = this._relativeAnchor2Y * this._basis.xZ - this._relativeAnchor2Z * this._basis.xY; + j.ang2Y = this._relativeAnchor2Z * this._basis.xX - this._relativeAnchor2X * this._basis.xZ; + j.ang2Z = this._relativeAnchor2X * this._basis.xY - this._relativeAnchor2Y * this._basis.xX; + } + let impulse = this._impulses[1]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linRhsY; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + j = row.jacobian; + j.lin1X = this._basis.yX; + j.lin1Y = this._basis.yY; + j.lin1Z = this._basis.yZ; + j.lin2X = this._basis.yX; + j.lin2Y = this._basis.yY; + j.lin2Z = this._basis.yZ; + j.ang1X = this._relativeAnchor1Y * this._basis.yZ - this._relativeAnchor1Z * this._basis.yY; + j.ang1Y = this._relativeAnchor1Z * this._basis.yX - this._relativeAnchor1X * this._basis.yZ; + j.ang1Z = this._relativeAnchor1X * this._basis.yY - this._relativeAnchor1Y * this._basis.yX; + j.ang2X = this._relativeAnchor2Y * this._basis.yZ - this._relativeAnchor2Z * this._basis.yY; + j.ang2Y = this._relativeAnchor2Z * this._basis.yX - this._relativeAnchor2X * this._basis.yZ; + j.ang2Z = this._relativeAnchor2X * this._basis.yY - this._relativeAnchor2Y * this._basis.yX; + let impulse1 = this._impulses[2]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linRhsZ; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = this._basis.zX; + j.lin1Y = this._basis.zY; + j.lin1Z = this._basis.zZ; + j.lin2X = this._basis.zX; + j.lin2Y = this._basis.zY; + j.lin2Z = this._basis.zZ; + j.ang1X = this._relativeAnchor1Y * this._basis.zZ - this._relativeAnchor1Z * this._basis.zY; + j.ang1Y = this._relativeAnchor1Z * this._basis.zX - this._relativeAnchor1X * this._basis.zZ; + j.ang1Z = this._relativeAnchor1X * this._basis.zY - this._relativeAnchor1Y * this._basis.zX; + j.ang2X = this._relativeAnchor2Y * this._basis.zZ - this._relativeAnchor2Z * this._basis.zY; + j.ang2Y = this._relativeAnchor2Z * this._basis.zX - this._relativeAnchor2X * this._basis.zZ; + j.ang2Z = this._relativeAnchor2X * this._basis.zY - this._relativeAnchor2Y * this._basis.zX; + if(this._rotSd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this.angle,this._rotLm,rotationalMotorMass,this._rotSd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._basis.xX; + j.ang1Y = this._basis.xY; + j.ang1Z = this._basis.xZ; + j.ang2X = this._basis.xX; + j.ang2Y = this._basis.xY; + j.ang2Z = this._basis.xZ; + } + let impulse2 = this._impulses[4]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = angRhsY; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.ang1X = this._basis.yX; + j.ang1Y = this._basis.yY; + j.ang1Z = this._basis.yZ; + j.ang2X = this._basis.yX; + j.ang2Y = this._basis.yY; + j.ang2Z = this._basis.yZ; + let impulse3 = this._impulses[5]; + let row3 = info.rows[info.numRows++]; + let _this3 = row3.jacobian; + _this3.lin1X = 0; + _this3.lin1Y = 0; + _this3.lin1Z = 0; + _this3.lin2X = 0; + _this3.lin2Y = 0; + _this3.lin2Z = 0; + _this3.ang1X = 0; + _this3.ang1Y = 0; + _this3.ang1Z = 0; + _this3.ang2X = 0; + _this3.ang2Y = 0; + _this3.ang2Z = 0; + row3.rhs = 0; + row3.cfm = 0; + row3.minImpulse = 0; + row3.maxImpulse = 0; + row3.motorSpeed = 0; + row3.motorMaxImpulse = 0; + row3.impulse = null; + row3.impulse = impulse3; + row3.rhs = angRhsZ; + row3.cfm = 0; + row3.minImpulse = -1e65536; + row3.maxImpulse = 1e65536; + j = row3.jacobian; + j.ang1X = this._basis.zX; + j.ang1Y = this._basis.zY; + j.ang1Z = this._basis.zZ; + j.ang2X = this._basis.zX; + j.ang2Y = this._basis.zY; + j.ang2Z = this._basis.zZ; + } + _syncAnchors() { + super._syncAnchors(); + let _this = this._basis; + let invM1 = _this.joint._b1._invMass; + let invM2 = _this.joint._b2._invMass; + let qX; + let qY; + let qZ; + let qW; + let idQX; + let idQY; + let idQZ; + let idQW; + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let newXX; + let newXY; + let newXZ; + let newYX; + let newYY; + let newYZ; + let newZX; + let newZY; + let newZZ; + let prevXX; + let prevXY; + let prevXZ; + let prevYX; + let prevYY; + let prevYZ; + let d = _this.joint._basisX1X * _this.joint._basisX2X + _this.joint._basisX1Y * _this.joint._basisX2Y + _this.joint._basisX1Z * _this.joint._basisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = _this.joint._basisX1X; + let y1 = _this.joint._basisX1Y; + let z1 = _this.joint._basisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + qX = vX; + qY = vY; + qZ = vZ; + qW = 0; + } else { + let cX; + let cY; + let cZ; + cX = _this.joint._basisX1Y * _this.joint._basisX2Z - _this.joint._basisX1Z * _this.joint._basisX2Y; + cY = _this.joint._basisX1Z * _this.joint._basisX2X - _this.joint._basisX1X * _this.joint._basisX2Z; + cZ = _this.joint._basisX1X * _this.joint._basisX2Y - _this.joint._basisX1Y * _this.joint._basisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + qX = cX; + qY = cY; + qZ = cZ; + qW = w; + } + idQX = 0; + idQY = 0; + idQZ = 0; + idQW = 1; + let q1X; + let q1Y; + let q1Z; + let q1W; + let q2X; + let q2Y; + let q2Z; + let q2W; + q1X = idQX; + q1Y = idQY; + q1Z = idQZ; + q1W = idQW; + q2X = qX; + q2Y = qY; + q2Z = qZ; + q2W = qW; + let d1 = q1X * q2X + q1Y * q2Y + q1Z * q2Z + q1W * q2W; + if(d1 < 0) { + d1 = -d1; + q2X = -q2X; + q2Y = -q2Y; + q2Z = -q2Z; + q2W = -q2W; + } + if(d1 > 0.999999) { + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = q2X - q1X; + dqY = q2Y - q1Y; + dqZ = q2Z - q1Z; + dqW = q2W - q1W; + q2X = q1X + dqX * (invM1 / (invM1 + invM2)); + q2Y = q1Y + dqY * (invM1 / (invM1 + invM2)); + q2Z = q1Z + dqZ * (invM1 / (invM1 + invM2)); + q2W = q1W + dqW * (invM1 / (invM1 + invM2)); + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + slerpQX = q2X * l; + slerpQY = q2Y * l; + slerpQZ = q2Z * l; + slerpQW = q2W * l; + } else { + let theta = invM1 / (invM1 + invM2) * Math.acos(d1); + q2X += q1X * -d1; + q2Y += q1Y * -d1; + q2Z += q1Z * -d1; + q2W += q1W * -d1; + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + q2X *= l; + q2Y *= l; + q2Z *= l; + q2W *= l; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + q1X *= cos; + q1Y *= cos; + q1Z *= cos; + q1W *= cos; + slerpQX = q1X + q2X * sin; + slerpQY = q1Y + q2Y * sin; + slerpQZ = q1Z + q2Z * sin; + slerpQW = q1W + q2W * sin; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * _this.joint._basisX1X + slerpM01 * _this.joint._basisX1Y + slerpM02 * _this.joint._basisX1Z; + __tmp__Y = slerpM10 * _this.joint._basisX1X + slerpM11 * _this.joint._basisX1Y + slerpM12 * _this.joint._basisX1Z; + __tmp__Z = slerpM20 * _this.joint._basisX1X + slerpM21 * _this.joint._basisX1Y + slerpM22 * _this.joint._basisX1Z; + newXX = __tmp__X; + newXY = __tmp__Y; + newXZ = __tmp__Z; + prevXX = _this.xX; + prevXY = _this.xY; + prevXZ = _this.xZ; + prevYX = _this.yX; + prevYY = _this.yY; + prevYZ = _this.yZ; + let d2 = prevXX * newXX + prevXY * newXY + prevXZ * newXZ; + if(d2 < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = prevXX; + let y1 = prevXY; + let z1 = prevXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = prevXY * newXZ - prevXZ * newXY; + cY = prevXZ * newXX - prevXX * newXZ; + cZ = prevXX * newXY - prevXY * newXX; + let w = Math.sqrt((1 + d2) * 0.5); + d2 = 0.5 / w; + cX *= d2; + cY *= d2; + cZ *= d2; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x1 = slerpQX; + let y1 = slerpQY; + let z1 = slerpQZ; + let w1 = slerpQW; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + slerpM00 = 1 - yy1 - zz1; + slerpM01 = xy1 - wz1; + slerpM02 = xz1 + wy1; + slerpM10 = xy1 + wz1; + slerpM11 = 1 - xx1 - zz1; + slerpM12 = yz1 - wx1; + slerpM20 = xz1 - wy1; + slerpM21 = yz1 + wx1; + slerpM22 = 1 - xx1 - yy1; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * prevYX + slerpM01 * prevYY + slerpM02 * prevYZ; + __tmp__Y1 = slerpM10 * prevYX + slerpM11 * prevYY + slerpM12 * prevYZ; + __tmp__Z1 = slerpM20 * prevYX + slerpM21 * prevYY + slerpM22 * prevYZ; + newYX = __tmp__X1; + newYY = __tmp__Y1; + newYZ = __tmp__Z1; + newZX = newXY * newYZ - newXZ * newYY; + newZY = newXZ * newYX - newXX * newYZ; + newZZ = newXX * newYY - newXY * newYX; + if(newZX * newZX + newZY * newZY + newZZ * newZZ > 1e-6) { + let l = newZX * newZX + newZY * newZY + newZZ * newZZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + newZX *= l; + newZY *= l; + newZZ *= l; + } else { + let x1 = newXX; + let y1 = newXY; + let z1 = newXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + newZX = 0; + newZY = z1 * d; + newZZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + newZX = -z1 * d; + newZY = 0; + newZZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } + newYX = newZY * newXZ - newZZ * newXY; + newYY = newZZ * newXX - newZX * newXZ; + newYZ = newZX * newXY - newZY * newXX; + _this.xX = newXX; + _this.xY = newXY; + _this.xZ = newXZ; + _this.yX = newYX; + _this.yY = newYY; + _this.yZ = newYZ; + _this.zX = newZX; + _this.zY = newZY; + _this.zZ = newZZ; + let angErrorX; + let angErrorY; + let angErrorZ; + angErrorX = this._basisX1Y * this._basisX2Z - this._basisX1Z * this._basisX2Y; + angErrorY = this._basisX1Z * this._basisX2X - this._basisX1X * this._basisX2Z; + angErrorZ = this._basisX1X * this._basisX2Y - this._basisX1Y * this._basisX2X; + let cos = this._basisX1X * this._basisX2X + this._basisX1Y * this._basisX2Y + this._basisX1Z * this._basisX2Z; + let theta = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + let l = angErrorX * angErrorX + angErrorY * angErrorY + angErrorZ * angErrorZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + angErrorX *= l; + angErrorY *= l; + angErrorZ *= l; + angErrorX *= theta; + angErrorY *= theta; + angErrorZ *= theta; + this.angularErrorY = angErrorX * this._basis.yX + angErrorY * this._basis.yY + angErrorZ * this._basis.yZ; + this.angularErrorZ = angErrorX * this._basis.zX + angErrorY * this._basis.zY + angErrorZ * this._basis.zZ; + let perpCrossX; + let perpCrossY; + let perpCrossZ; + perpCrossX = this._basisY1Y * this._basisY2Z - this._basisY1Z * this._basisY2Y; + perpCrossY = this._basisY1Z * this._basisY2X - this._basisY1X * this._basisY2Z; + perpCrossZ = this._basisY1X * this._basisY2Y - this._basisY1Y * this._basisY2X; + cos = this._basisY1X * this._basisY2X + this._basisY1Y * this._basisY2Y + this._basisY1Z * this._basisY2Z; + this.angle = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + if(perpCrossX * this._basis.xX + perpCrossY * this._basis.xY + perpCrossZ * this._basis.xZ < 0) { + this.angle = -this.angle; + } + let anchorDiffX; + let anchorDiffY; + let anchorDiffZ; + anchorDiffX = this._anchor2X - this._anchor1X; + anchorDiffY = this._anchor2Y - this._anchor1Y; + anchorDiffZ = this._anchor2Z - this._anchor1Z; + this.translation = anchorDiffX * this._basis.xX + anchorDiffY * this._basis.xY + anchorDiffZ * this._basis.xZ; + this.linearErrorY = anchorDiffX * this._basis.yX + anchorDiffY * this._basis.yY + anchorDiffZ * this._basis.yZ; + this.linearErrorZ = anchorDiffX * this._basis.zX + anchorDiffY * this._basis.zY + anchorDiffZ * this._basis.zZ; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getTranslationalSpringDamper() { + return this._translSd; + } + getRotationalSpringDamper() { + return this._rotSd; + } + getTranslationalLimitMotor() { + return this._translLm; + } + getRotationalLimitMotor() { + return this._rotLm; + } + getAngle() { + return this.angle; + } + getTranslation() { + return this.translation; + } +} +oimo.dynamics.constraint.joint.JointConfig = class oimo_dynamics_constraint_joint_JointConfig { + constructor() { + this.rigidBody1 = null; + this.rigidBody2 = null; + this.localAnchor1 = new oimo.common.Vec3(); + this.localAnchor2 = new oimo.common.Vec3(); + this.allowCollision = false; + this.solverType = oimo.common.Setting.defaultJointConstraintSolverType; + this.positionCorrectionAlgorithm = oimo.common.Setting.defaultJointPositionCorrectionAlgorithm; + this.breakForce = 0; + this.breakTorque = 0; + } + _init(rb1,rb2,worldAnchor) { + this.rigidBody1 = rb1; + this.rigidBody2 = rb2; + let _this = this.rigidBody1; + let localPoint = this.localAnchor1; + let vX; + let vY; + let vZ; + vX = worldAnchor.x; + vY = worldAnchor.y; + vZ = worldAnchor.z; + vX -= _this._transform._positionX; + vY -= _this._transform._positionY; + vZ -= _this._transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = _this._transform._rotation00 * vX + _this._transform._rotation10 * vY + _this._transform._rotation20 * vZ; + __tmp__Y = _this._transform._rotation01 * vX + _this._transform._rotation11 * vY + _this._transform._rotation21 * vZ; + __tmp__Z = _this._transform._rotation02 * vX + _this._transform._rotation12 * vY + _this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localPoint.x = vX; + localPoint.y = vY; + localPoint.z = vZ; + let _this1 = this.rigidBody2; + let localPoint1 = this.localAnchor2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAnchor.x; + vY1 = worldAnchor.y; + vZ1 = worldAnchor.z; + vX1 -= _this1._transform._positionX; + vY1 -= _this1._transform._positionY; + vZ1 -= _this1._transform._positionZ; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = _this1._transform._rotation00 * vX1 + _this1._transform._rotation10 * vY1 + _this1._transform._rotation20 * vZ1; + __tmp__Y1 = _this1._transform._rotation01 * vX1 + _this1._transform._rotation11 * vY1 + _this1._transform._rotation21 * vZ1; + __tmp__Z1 = _this1._transform._rotation02 * vX1 + _this1._transform._rotation12 * vY1 + _this1._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localPoint1.x = vX1; + localPoint1.y = vY1; + localPoint1.z = vZ1; + } +} +oimo.dynamics.constraint.joint.CylindricalJointConfig = class oimo_dynamics_constraint_joint_CylindricalJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.translationalLimitMotor = new oimo.dynamics.constraint.joint.TranslationalLimitMotor(); + this.translationalSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.rotationalLimitMotor = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.rotationalSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis.x; + vY = worldAxis.y; + vZ = worldAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis.x; + vY1 = worldAxis.y; + vZ1 = worldAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +oimo.dynamics.constraint.joint.GenericJoint = class oimo_dynamics_constraint_joint_GenericJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.GENERIC); + let tmp; + let _this = config.localBasis1; + if(!(_this.e00 * (_this.e11 * _this.e22 - _this.e12 * _this.e21) - _this.e01 * (_this.e10 * _this.e22 - _this.e12 * _this.e20) + _this.e02 * (_this.e10 * _this.e21 - _this.e11 * _this.e20) < 0)) { + let _this = config.localBasis2; + tmp = _this.e00 * (_this.e11 * _this.e22 - _this.e12 * _this.e21) - _this.e01 * (_this.e10 * _this.e22 - _this.e12 * _this.e20) + _this.e02 * (_this.e10 * _this.e21 - _this.e11 * _this.e20) < 0; + } else { + tmp = true; + } + if(tmp) { + console.log("src/oimo/dynamics/constraint/joint/GenericJoint.hx:50:","[warning] joint basis must be right handed"); + } + let lb100; + let lb101; + let lb102; + let lb110; + let lb111; + let lb112; + let lb120; + let lb121; + let lb122; + let lb200; + let lb201; + let lb202; + let lb210; + let lb211; + let lb212; + let lb220; + let lb221; + let lb222; + let m = config.localBasis1; + lb100 = m.e00; + lb101 = m.e01; + lb102 = m.e02; + lb110 = m.e10; + lb111 = m.e11; + lb112 = m.e12; + lb120 = m.e20; + lb121 = m.e21; + lb122 = m.e22; + let m1 = config.localBasis2; + lb200 = m1.e00; + lb201 = m1.e01; + lb202 = m1.e02; + lb210 = m1.e10; + lb211 = m1.e11; + lb212 = m1.e12; + lb220 = m1.e20; + lb221 = m1.e21; + lb222 = m1.e22; + this._localBasisX1X = lb100; + this._localBasisX1Y = lb110; + this._localBasisX1Z = lb120; + this._localBasisY1X = lb101; + this._localBasisY1Y = lb111; + this._localBasisY1Z = lb121; + this._localBasisZ1X = lb102; + this._localBasisZ1Y = lb112; + this._localBasisZ1Z = lb122; + this._localBasisX2X = lb200; + this._localBasisX2Y = lb210; + this._localBasisX2Z = lb220; + this._localBasisY2X = lb201; + this._localBasisY2Y = lb211; + this._localBasisY2Z = lb221; + this._localBasisZ2X = lb202; + this._localBasisZ2Y = lb212; + this._localBasisZ2Z = lb222; + this._angleX = 0; + this._angleY = 0; + this._angleZ = 0; + this.translationX = 0; + this.translationY = 0; + this.translationZ = 0; + this.xSingular = false; + this.ySingular = false; + this.zSingular = false; + this._translLms = new Array(3); + this._translSds = new Array(3); + this._rotLms = new Array(3); + this._rotSds = new Array(3); + this._translLms[0] = config.translationalLimitMotors[0].clone(); + this._translLms[1] = config.translationalLimitMotors[1].clone(); + this._translLms[2] = config.translationalLimitMotors[2].clone(); + this._translSds[0] = config.translationalSpringDampers[0].clone(); + this._translSds[1] = config.translationalSpringDampers[1].clone(); + this._translSds[2] = config.translationalSpringDampers[2].clone(); + this._rotLms[0] = config.rotationalLimitMotors[0].clone(); + this._rotLms[1] = config.rotationalLimitMotors[1].clone(); + this._rotLms[2] = config.rotationalLimitMotors[2].clone(); + this._rotSds[0] = config.rotationalSpringDampers[0].clone(); + this._rotSds[1] = config.rotationalSpringDampers[1].clone(); + this._rotSds[2] = config.rotationalSpringDampers[2].clone(); + } + getInfo(info,timeStep,isPositionPart) { + let j; + let translMotorMass = 1 / (this._b1._invMass + this._b2._invMass); + let motorMassX = this.computeEffectiveInertiaMoment(this._axisXX,this._axisXY,this._axisXZ); + let motorMassY = this.computeEffectiveInertiaMoment(this._axisYX,this._axisYY,this._axisYZ); + let motorMassZ = this.computeEffectiveInertiaMoment(this._axisZX,this._axisZY,this._axisZZ); + if(this._translSds[0].frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translationX,this._translLms[0],translMotorMass,this._translSds[0],timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basisX1X; + j.lin1Y = this._basisX1Y; + j.lin1Z = this._basisX1Z; + j.lin2X = this._basisX1X; + j.lin2Y = this._basisX1Y; + j.lin2Z = this._basisX1Z; + j.ang1X = this._relativeAnchor1Y * this._basisX1Z - this._relativeAnchor1Z * this._basisX1Y; + j.ang1Y = this._relativeAnchor1Z * this._basisX1X - this._relativeAnchor1X * this._basisX1Z; + j.ang1Z = this._relativeAnchor1X * this._basisX1Y - this._relativeAnchor1Y * this._basisX1X; + j.ang2X = this._relativeAnchor2Y * this._basisX1Z - this._relativeAnchor2Z * this._basisX1Y; + j.ang2Y = this._relativeAnchor2Z * this._basisX1X - this._relativeAnchor2X * this._basisX1Z; + j.ang2Z = this._relativeAnchor2X * this._basisX1Y - this._relativeAnchor2Y * this._basisX1X; + } + if(this._translSds[1].frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[1]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translationY,this._translLms[1],translMotorMass,this._translSds[1],timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basisY1X; + j.lin1Y = this._basisY1Y; + j.lin1Z = this._basisY1Z; + j.lin2X = this._basisY1X; + j.lin2Y = this._basisY1Y; + j.lin2Z = this._basisY1Z; + j.ang1X = this._relativeAnchor1Y * this._basisY1Z - this._relativeAnchor1Z * this._basisY1Y; + j.ang1Y = this._relativeAnchor1Z * this._basisY1X - this._relativeAnchor1X * this._basisY1Z; + j.ang1Z = this._relativeAnchor1X * this._basisY1Y - this._relativeAnchor1Y * this._basisY1X; + j.ang2X = this._relativeAnchor2Y * this._basisY1Z - this._relativeAnchor2Z * this._basisY1Y; + j.ang2Y = this._relativeAnchor2Z * this._basisY1X - this._relativeAnchor2X * this._basisY1Z; + j.ang2Z = this._relativeAnchor2X * this._basisY1Y - this._relativeAnchor2Y * this._basisY1X; + } + if(this._translSds[2].frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[2]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translationZ,this._translLms[2],translMotorMass,this._translSds[2],timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basisZ1X; + j.lin1Y = this._basisZ1Y; + j.lin1Z = this._basisZ1Z; + j.lin2X = this._basisZ1X; + j.lin2Y = this._basisZ1Y; + j.lin2Z = this._basisZ1Z; + j.ang1X = this._relativeAnchor1Y * this._basisZ1Z - this._relativeAnchor1Z * this._basisZ1Y; + j.ang1Y = this._relativeAnchor1Z * this._basisZ1X - this._relativeAnchor1X * this._basisZ1Z; + j.ang1Z = this._relativeAnchor1X * this._basisZ1Y - this._relativeAnchor1Y * this._basisZ1X; + j.ang2X = this._relativeAnchor2Y * this._basisZ1Z - this._relativeAnchor2Z * this._basisZ1Y; + j.ang2Y = this._relativeAnchor2Z * this._basisZ1X - this._relativeAnchor2X * this._basisZ1Z; + j.ang2Z = this._relativeAnchor2X * this._basisZ1Y - this._relativeAnchor2Y * this._basisZ1X; + } + if(!this.xSingular && (this._rotSds[0].frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleX,this._rotLms[0],motorMassX,this._rotSds[0],timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisXX; + j.ang1Y = this._axisXY; + j.ang1Z = this._axisXZ; + j.ang2X = this._axisXX; + j.ang2Y = this._axisXY; + j.ang2Z = this._axisXZ; + } + if(!this.ySingular && (this._rotSds[1].frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[4]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleY,this._rotLms[1],motorMassY,this._rotSds[1],timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisYX; + j.ang1Y = this._axisYY; + j.ang1Z = this._axisYZ; + j.ang2X = this._axisYX; + j.ang2Y = this._axisYY; + j.ang2Z = this._axisYZ; + } + if(!this.zSingular && (this._rotSds[2].frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[5]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleZ,this._rotLms[2],motorMassZ,this._rotSds[2],timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisZX; + j.ang1Y = this._axisZY; + j.ang1Z = this._axisZZ; + j.ang2X = this._axisZX; + j.ang2Y = this._axisZY; + j.ang2Z = this._axisZZ; + } + } + _syncAnchors() { + super._syncAnchors(); + let angleAxisXX; + let angleAxisXY; + let angleAxisXZ; + let angleAxisYX; + let angleAxisYY; + let angleAxisYZ; + let angleAxisZX; + let angleAxisZY; + let angleAxisZZ; + angleAxisXX = this._basisX1X; + angleAxisXY = this._basisX1Y; + angleAxisXZ = this._basisX1Z; + angleAxisZX = this._basisZ2X; + angleAxisZY = this._basisZ2Y; + angleAxisZZ = this._basisZ2Z; + angleAxisYX = angleAxisZY * angleAxisXZ - angleAxisZZ * angleAxisXY; + angleAxisYY = angleAxisZZ * angleAxisXX - angleAxisZX * angleAxisXZ; + angleAxisYZ = angleAxisZX * angleAxisXY - angleAxisZY * angleAxisXX; + this._axisXX = angleAxisYY * angleAxisZZ - angleAxisYZ * angleAxisZY; + this._axisXY = angleAxisYZ * angleAxisZX - angleAxisYX * angleAxisZZ; + this._axisXZ = angleAxisYX * angleAxisZY - angleAxisYY * angleAxisZX; + this._axisYX = angleAxisYX; + this._axisYY = angleAxisYY; + this._axisYZ = angleAxisYZ; + this._axisZX = angleAxisXY * angleAxisYZ - angleAxisXZ * angleAxisYY; + this._axisZY = angleAxisXZ * angleAxisYX - angleAxisXX * angleAxisYZ; + this._axisZZ = angleAxisXX * angleAxisYY - angleAxisXY * angleAxisYX; + let l = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._axisXX *= l; + this._axisXY *= l; + this._axisXZ *= l; + let l1 = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ; + if(l1 > 0) { + l1 = 1 / Math.sqrt(l1); + } + this._axisYX *= l1; + this._axisYY *= l1; + this._axisYZ *= l1; + let l2 = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ; + if(l2 > 0) { + l2 = 1 / Math.sqrt(l2); + } + this._axisZX *= l2; + this._axisZY *= l2; + this._axisZZ *= l2; + this.xSingular = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ == 0; + this.ySingular = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ == 0; + this.zSingular = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ == 0; + let rot100; + let rot101; + let rot102; + let rot110; + let rot111; + let rot112; + let rot120; + let rot121; + let rot122; + let rot200; + let rot201; + let rot202; + let rot210; + let rot211; + let rot212; + let rot220; + let rot221; + let rot222; + rot100 = this._basisX1X; + rot101 = this._basisY1X; + rot102 = this._basisZ1X; + rot110 = this._basisX1Y; + rot111 = this._basisY1Y; + rot112 = this._basisZ1Y; + rot120 = this._basisX1Z; + rot121 = this._basisY1Z; + rot122 = this._basisZ1Z; + rot200 = this._basisX2X; + rot201 = this._basisY2X; + rot202 = this._basisZ2X; + rot210 = this._basisX2Y; + rot211 = this._basisY2Y; + rot212 = this._basisZ2Y; + rot220 = this._basisX2Z; + rot221 = this._basisY2Z; + rot222 = this._basisZ2Z; + let relRot00; + let relRot01; + let relRot02; + let relRot11; + let relRot12; + let relRot21; + let relRot22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__11; + let __tmp__12; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot100 * rot200 + rot110 * rot210 + rot120 * rot220; + __tmp__01 = rot100 * rot201 + rot110 * rot211 + rot120 * rot221; + __tmp__02 = rot100 * rot202 + rot110 * rot212 + rot120 * rot222; + __tmp__11 = rot101 * rot201 + rot111 * rot211 + rot121 * rot221; + __tmp__12 = rot101 * rot202 + rot111 * rot212 + rot121 * rot222; + __tmp__21 = rot102 * rot201 + rot112 * rot211 + rot122 * rot221; + __tmp__22 = rot102 * rot202 + rot112 * rot212 + rot122 * rot222; + relRot00 = __tmp__00; + relRot01 = __tmp__01; + relRot02 = __tmp__02; + relRot11 = __tmp__11; + relRot12 = __tmp__12; + relRot21 = __tmp__21; + relRot22 = __tmp__22; + let anglesX; + let anglesY; + let anglesZ; + let sy = relRot02; + if(sy <= -1) { + let xSubZ = Math.atan2(relRot21,relRot11); + anglesX = xSubZ * 0.5; + anglesY = -1.570796326794895; + anglesZ = -xSubZ * 0.5; + } else if(sy >= 1) { + let xAddZ = Math.atan2(relRot21,relRot11); + anglesX = xAddZ * 0.5; + anglesY = 1.570796326794895; + anglesZ = xAddZ * 0.5; + } else { + anglesX = Math.atan2(-relRot12,relRot22); + anglesY = Math.asin(sy); + anglesZ = Math.atan2(-relRot01,relRot00); + } + this._angleX = anglesX; + this._angleY = anglesY; + this._angleZ = anglesZ; + let anchorDiffX; + let anchorDiffY; + let anchorDiffZ; + anchorDiffX = this._anchor2X - this._anchor1X; + anchorDiffY = this._anchor2Y - this._anchor1Y; + anchorDiffZ = this._anchor2Z - this._anchor1Z; + this.translationX = anchorDiffX * this._basisX1X + anchorDiffY * this._basisX1Y + anchorDiffZ * this._basisX1Z; + this.translationY = anchorDiffX * this._basisY1X + anchorDiffY * this._basisY1Y + anchorDiffZ * this._basisY1Z; + this.translationZ = anchorDiffX * this._basisZ1X + anchorDiffY * this._basisZ1Y + anchorDiffZ * this._basisZ1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxisX() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxisY() { + let v = new oimo.common.Vec3(); + v.x = this._axisYX; + v.y = this._axisYY; + v.z = this._axisYZ; + return v; + } + getAxisZ() { + let v = new oimo.common.Vec3(); + v.x = this._basisZ2X; + v.y = this._basisZ2Y; + v.z = this._basisZ2Z; + return v; + } + getTranslationalSpringDampers() { + return this._translSds.slice(0); + } + getRotationalSpringDampers() { + return this._translSds.slice(0); + } + getTranslationalLimitMotors() { + return this._translLms.slice(0); + } + getRotationalLimitMotors() { + return this._rotLms.slice(0); + } + getAngles() { + return new oimo.common.Vec3(this._angleX,this._angleY,this._angleZ); + } + getTranslations() { + return new oimo.common.Vec3(this.translationX,this.translationY,this.translationZ); + } +} +oimo.dynamics.constraint.joint.GenericJointConfig = class oimo_dynamics_constraint_joint_GenericJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localBasis1 = new oimo.common.Mat3(); + this.localBasis2 = new oimo.common.Mat3(); + let _g = []; + _g.push(new oimo.dynamics.constraint.joint.TranslationalLimitMotor().setLimits(0,0)); + _g.push(new oimo.dynamics.constraint.joint.TranslationalLimitMotor().setLimits(0,0)); + _g.push(new oimo.dynamics.constraint.joint.TranslationalLimitMotor().setLimits(0,0)); + this.translationalLimitMotors = _g; + let _g1 = []; + _g1.push(new oimo.dynamics.constraint.joint.RotationalLimitMotor().setLimits(0,0)); + _g1.push(new oimo.dynamics.constraint.joint.RotationalLimitMotor().setLimits(0,0)); + _g1.push(new oimo.dynamics.constraint.joint.RotationalLimitMotor().setLimits(0,0)); + this.rotationalLimitMotors = _g1; + this.translationalSpringDampers = [new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper()]; + this.rotationalSpringDampers = [new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper(),new oimo.dynamics.constraint.joint.SpringDamper()]; + } + init(rigidBody1,rigidBody2,worldAnchor,worldBasis1,worldBasis2) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let tf1 = rigidBody1._transform; + let tf2 = rigidBody2._transform; + let wb100; + let wb101; + let wb102; + let wb110; + let wb111; + let wb112; + let wb120; + let wb121; + let wb122; + let wb200; + let wb201; + let wb202; + let wb210; + let wb211; + let wb212; + let wb220; + let wb221; + let wb222; + let lb100; + let lb101; + let lb102; + let lb110; + let lb111; + let lb112; + let lb120; + let lb121; + let lb122; + let lb200; + let lb201; + let lb202; + let lb210; + let lb211; + let lb212; + let lb220; + let lb221; + let lb222; + wb100 = worldBasis1.e00; + wb101 = worldBasis1.e01; + wb102 = worldBasis1.e02; + wb110 = worldBasis1.e10; + wb111 = worldBasis1.e11; + wb112 = worldBasis1.e12; + wb120 = worldBasis1.e20; + wb121 = worldBasis1.e21; + wb122 = worldBasis1.e22; + wb200 = worldBasis2.e00; + wb201 = worldBasis2.e01; + wb202 = worldBasis2.e02; + wb210 = worldBasis2.e10; + wb211 = worldBasis2.e11; + wb212 = worldBasis2.e12; + wb220 = worldBasis2.e20; + wb221 = worldBasis2.e21; + wb222 = worldBasis2.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * wb100 + tf1._rotation10 * wb110 + tf1._rotation20 * wb120; + __tmp__01 = tf1._rotation00 * wb101 + tf1._rotation10 * wb111 + tf1._rotation20 * wb121; + __tmp__02 = tf1._rotation00 * wb102 + tf1._rotation10 * wb112 + tf1._rotation20 * wb122; + __tmp__10 = tf1._rotation01 * wb100 + tf1._rotation11 * wb110 + tf1._rotation21 * wb120; + __tmp__11 = tf1._rotation01 * wb101 + tf1._rotation11 * wb111 + tf1._rotation21 * wb121; + __tmp__12 = tf1._rotation01 * wb102 + tf1._rotation11 * wb112 + tf1._rotation21 * wb122; + __tmp__20 = tf1._rotation02 * wb100 + tf1._rotation12 * wb110 + tf1._rotation22 * wb120; + __tmp__21 = tf1._rotation02 * wb101 + tf1._rotation12 * wb111 + tf1._rotation22 * wb121; + __tmp__22 = tf1._rotation02 * wb102 + tf1._rotation12 * wb112 + tf1._rotation22 * wb122; + lb100 = __tmp__00; + lb101 = __tmp__01; + lb102 = __tmp__02; + lb110 = __tmp__10; + lb111 = __tmp__11; + lb112 = __tmp__12; + lb120 = __tmp__20; + lb121 = __tmp__21; + lb122 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * wb200 + tf2._rotation10 * wb210 + tf2._rotation20 * wb220; + __tmp__011 = tf2._rotation00 * wb201 + tf2._rotation10 * wb211 + tf2._rotation20 * wb221; + __tmp__021 = tf2._rotation00 * wb202 + tf2._rotation10 * wb212 + tf2._rotation20 * wb222; + __tmp__101 = tf2._rotation01 * wb200 + tf2._rotation11 * wb210 + tf2._rotation21 * wb220; + __tmp__111 = tf2._rotation01 * wb201 + tf2._rotation11 * wb211 + tf2._rotation21 * wb221; + __tmp__121 = tf2._rotation01 * wb202 + tf2._rotation11 * wb212 + tf2._rotation21 * wb222; + __tmp__201 = tf2._rotation02 * wb200 + tf2._rotation12 * wb210 + tf2._rotation22 * wb220; + __tmp__211 = tf2._rotation02 * wb201 + tf2._rotation12 * wb211 + tf2._rotation22 * wb221; + __tmp__221 = tf2._rotation02 * wb202 + tf2._rotation12 * wb212 + tf2._rotation22 * wb222; + lb200 = __tmp__001; + lb201 = __tmp__011; + lb202 = __tmp__021; + lb210 = __tmp__101; + lb211 = __tmp__111; + lb212 = __tmp__121; + lb220 = __tmp__201; + lb221 = __tmp__211; + lb222 = __tmp__221; + let m = this.localBasis1; + m.e00 = lb100; + m.e01 = lb101; + m.e02 = lb102; + m.e10 = lb110; + m.e11 = lb111; + m.e12 = lb112; + m.e20 = lb120; + m.e21 = lb121; + m.e22 = lb122; + let m1 = this.localBasis2; + m1.e00 = lb200; + m1.e01 = lb201; + m1.e02 = lb202; + m1.e10 = lb210; + m1.e11 = lb211; + m1.e12 = lb212; + m1.e20 = lb220; + m1.e21 = lb221; + m1.e22 = lb222; + return this; + } +} +oimo.dynamics.constraint.joint.JointImpulse = class oimo_dynamics_constraint_joint_JointImpulse { + constructor() { + this.impulse = 0; + this.impulseM = 0; + this.impulseP = 0; + } +} +oimo.dynamics.constraint.joint.JointLink = class oimo_dynamics_constraint_joint_JointLink { + constructor(joint) { + this._joint = joint; + } + getContact() { + return this._joint; + } + getOther() { + return this._other; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.constraint.joint.JointMacro = class oimo_dynamics_constraint_joint_JointMacro { +} +oimo.dynamics.constraint.joint.JointType = class oimo_dynamics_constraint_joint_JointType { +} +oimo.dynamics.constraint.joint.PrismaticJoint = class oimo_dynamics_constraint_joint_PrismaticJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.PRISMATIC); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisX2X = v1.x; + this._localBasisX2Y = v1.y; + this._localBasisX2Z = v1.z; + this.buildLocalBasesFromX(); + this._basis = new oimo.dynamics.constraint.joint.BasisTracker(this); + this.translation = 0; + this.linearErrorY = 0; + this.linearErrorZ = 0; + this.angularErrorX = 0; + this.angularErrorY = 0; + this.angularErrorZ = 0; + this._sd = config.springDamper.clone(); + this._lm = config.limitMotor.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linRhsY = this.linearErrorY * erp; + let linRhsZ = this.linearErrorZ * erp; + let angRhsX = this.angularErrorX * erp; + let angRhsY = this.angularErrorY * erp; + let angRhsZ = this.angularErrorZ * erp; + let j; + if(this._sd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowLinear(row,this.translation,this._lm,1 / (this._b1._invMass + this._b2._invMass),this._sd,timeStep,isPositionPart); + j = row.jacobian; + j.lin1X = this._basis.xX; + j.lin1Y = this._basis.xY; + j.lin1Z = this._basis.xZ; + j.lin2X = this._basis.xX; + j.lin2Y = this._basis.xY; + j.lin2Z = this._basis.xZ; + j.ang1X = this._relativeAnchor1Y * this._basis.xZ - this._relativeAnchor1Z * this._basis.xY; + j.ang1Y = this._relativeAnchor1Z * this._basis.xX - this._relativeAnchor1X * this._basis.xZ; + j.ang1Z = this._relativeAnchor1X * this._basis.xY - this._relativeAnchor1Y * this._basis.xX; + j.ang2X = this._relativeAnchor2Y * this._basis.xZ - this._relativeAnchor2Z * this._basis.xY; + j.ang2Y = this._relativeAnchor2Z * this._basis.xX - this._relativeAnchor2X * this._basis.xZ; + j.ang2Z = this._relativeAnchor2X * this._basis.xY - this._relativeAnchor2Y * this._basis.xX; + } + let impulse = this._impulses[1]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linRhsY; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + j = row.jacobian; + j.lin1X = this._basis.yX; + j.lin1Y = this._basis.yY; + j.lin1Z = this._basis.yZ; + j.lin2X = this._basis.yX; + j.lin2Y = this._basis.yY; + j.lin2Z = this._basis.yZ; + j.ang1X = this._relativeAnchor1Y * this._basis.yZ - this._relativeAnchor1Z * this._basis.yY; + j.ang1Y = this._relativeAnchor1Z * this._basis.yX - this._relativeAnchor1X * this._basis.yZ; + j.ang1Z = this._relativeAnchor1X * this._basis.yY - this._relativeAnchor1Y * this._basis.yX; + j.ang2X = this._relativeAnchor2Y * this._basis.yZ - this._relativeAnchor2Z * this._basis.yY; + j.ang2Y = this._relativeAnchor2Z * this._basis.yX - this._relativeAnchor2X * this._basis.yZ; + j.ang2Z = this._relativeAnchor2X * this._basis.yY - this._relativeAnchor2Y * this._basis.yX; + let impulse1 = this._impulses[2]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linRhsZ; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = this._basis.zX; + j.lin1Y = this._basis.zY; + j.lin1Z = this._basis.zZ; + j.lin2X = this._basis.zX; + j.lin2Y = this._basis.zY; + j.lin2Z = this._basis.zZ; + j.ang1X = this._relativeAnchor1Y * this._basis.zZ - this._relativeAnchor1Z * this._basis.zY; + j.ang1Y = this._relativeAnchor1Z * this._basis.zX - this._relativeAnchor1X * this._basis.zZ; + j.ang1Z = this._relativeAnchor1X * this._basis.zY - this._relativeAnchor1Y * this._basis.zX; + j.ang2X = this._relativeAnchor2Y * this._basis.zZ - this._relativeAnchor2Z * this._basis.zY; + j.ang2Y = this._relativeAnchor2Z * this._basis.zX - this._relativeAnchor2X * this._basis.zZ; + j.ang2Z = this._relativeAnchor2X * this._basis.zY - this._relativeAnchor2Y * this._basis.zX; + let impulse2 = this._impulses[3]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = angRhsX; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.ang1X = 1; + j.ang1Y = 0; + j.ang1Z = 0; + j.ang2X = 1; + j.ang2Y = 0; + j.ang2Z = 0; + let impulse3 = this._impulses[4]; + let row3 = info.rows[info.numRows++]; + let _this3 = row3.jacobian; + _this3.lin1X = 0; + _this3.lin1Y = 0; + _this3.lin1Z = 0; + _this3.lin2X = 0; + _this3.lin2Y = 0; + _this3.lin2Z = 0; + _this3.ang1X = 0; + _this3.ang1Y = 0; + _this3.ang1Z = 0; + _this3.ang2X = 0; + _this3.ang2Y = 0; + _this3.ang2Z = 0; + row3.rhs = 0; + row3.cfm = 0; + row3.minImpulse = 0; + row3.maxImpulse = 0; + row3.motorSpeed = 0; + row3.motorMaxImpulse = 0; + row3.impulse = null; + row3.impulse = impulse3; + row3.rhs = angRhsY; + row3.cfm = 0; + row3.minImpulse = -1e65536; + row3.maxImpulse = 1e65536; + j = row3.jacobian; + j.ang1X = 0; + j.ang1Y = 1; + j.ang1Z = 0; + j.ang2X = 0; + j.ang2Y = 1; + j.ang2Z = 0; + let impulse4 = this._impulses[5]; + let row4 = info.rows[info.numRows++]; + let _this4 = row4.jacobian; + _this4.lin1X = 0; + _this4.lin1Y = 0; + _this4.lin1Z = 0; + _this4.lin2X = 0; + _this4.lin2Y = 0; + _this4.lin2Z = 0; + _this4.ang1X = 0; + _this4.ang1Y = 0; + _this4.ang1Z = 0; + _this4.ang2X = 0; + _this4.ang2Y = 0; + _this4.ang2Z = 0; + row4.rhs = 0; + row4.cfm = 0; + row4.minImpulse = 0; + row4.maxImpulse = 0; + row4.motorSpeed = 0; + row4.motorMaxImpulse = 0; + row4.impulse = null; + row4.impulse = impulse4; + row4.rhs = angRhsZ; + row4.cfm = 0; + row4.minImpulse = -1e65536; + row4.maxImpulse = 1e65536; + j = row4.jacobian; + j.ang1X = 0; + j.ang1Y = 0; + j.ang1Z = 1; + j.ang2X = 0; + j.ang2Y = 0; + j.ang2Z = 1; + } + _syncAnchors() { + super._syncAnchors(); + let _this = this._basis; + let invM1 = _this.joint._b1._invMass; + let invM2 = _this.joint._b2._invMass; + let qX; + let qY; + let qZ; + let qW; + let idQX; + let idQY; + let idQZ; + let idQW; + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let newXX; + let newXY; + let newXZ; + let newYX; + let newYY; + let newYZ; + let newZX; + let newZY; + let newZZ; + let prevXX; + let prevXY; + let prevXZ; + let prevYX; + let prevYY; + let prevYZ; + let d = _this.joint._basisX1X * _this.joint._basisX2X + _this.joint._basisX1Y * _this.joint._basisX2Y + _this.joint._basisX1Z * _this.joint._basisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = _this.joint._basisX1X; + let y1 = _this.joint._basisX1Y; + let z1 = _this.joint._basisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + qX = vX; + qY = vY; + qZ = vZ; + qW = 0; + } else { + let cX; + let cY; + let cZ; + cX = _this.joint._basisX1Y * _this.joint._basisX2Z - _this.joint._basisX1Z * _this.joint._basisX2Y; + cY = _this.joint._basisX1Z * _this.joint._basisX2X - _this.joint._basisX1X * _this.joint._basisX2Z; + cZ = _this.joint._basisX1X * _this.joint._basisX2Y - _this.joint._basisX1Y * _this.joint._basisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + qX = cX; + qY = cY; + qZ = cZ; + qW = w; + } + idQX = 0; + idQY = 0; + idQZ = 0; + idQW = 1; + let q1X; + let q1Y; + let q1Z; + let q1W; + let q2X; + let q2Y; + let q2Z; + let q2W; + q1X = idQX; + q1Y = idQY; + q1Z = idQZ; + q1W = idQW; + q2X = qX; + q2Y = qY; + q2Z = qZ; + q2W = qW; + let d1 = q1X * q2X + q1Y * q2Y + q1Z * q2Z + q1W * q2W; + if(d1 < 0) { + d1 = -d1; + q2X = -q2X; + q2Y = -q2Y; + q2Z = -q2Z; + q2W = -q2W; + } + if(d1 > 0.999999) { + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = q2X - q1X; + dqY = q2Y - q1Y; + dqZ = q2Z - q1Z; + dqW = q2W - q1W; + q2X = q1X + dqX * (invM1 / (invM1 + invM2)); + q2Y = q1Y + dqY * (invM1 / (invM1 + invM2)); + q2Z = q1Z + dqZ * (invM1 / (invM1 + invM2)); + q2W = q1W + dqW * (invM1 / (invM1 + invM2)); + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + slerpQX = q2X * l; + slerpQY = q2Y * l; + slerpQZ = q2Z * l; + slerpQW = q2W * l; + } else { + let theta = invM1 / (invM1 + invM2) * Math.acos(d1); + q2X += q1X * -d1; + q2Y += q1Y * -d1; + q2Z += q1Z * -d1; + q2W += q1W * -d1; + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + q2X *= l; + q2Y *= l; + q2Z *= l; + q2W *= l; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + q1X *= cos; + q1Y *= cos; + q1Z *= cos; + q1W *= cos; + slerpQX = q1X + q2X * sin; + slerpQY = q1Y + q2Y * sin; + slerpQZ = q1Z + q2Z * sin; + slerpQW = q1W + q2W * sin; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * _this.joint._basisX1X + slerpM01 * _this.joint._basisX1Y + slerpM02 * _this.joint._basisX1Z; + __tmp__Y = slerpM10 * _this.joint._basisX1X + slerpM11 * _this.joint._basisX1Y + slerpM12 * _this.joint._basisX1Z; + __tmp__Z = slerpM20 * _this.joint._basisX1X + slerpM21 * _this.joint._basisX1Y + slerpM22 * _this.joint._basisX1Z; + newXX = __tmp__X; + newXY = __tmp__Y; + newXZ = __tmp__Z; + prevXX = _this.xX; + prevXY = _this.xY; + prevXZ = _this.xZ; + prevYX = _this.yX; + prevYY = _this.yY; + prevYZ = _this.yZ; + let d2 = prevXX * newXX + prevXY * newXY + prevXZ * newXZ; + if(d2 < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = prevXX; + let y1 = prevXY; + let z1 = prevXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = prevXY * newXZ - prevXZ * newXY; + cY = prevXZ * newXX - prevXX * newXZ; + cZ = prevXX * newXY - prevXY * newXX; + let w = Math.sqrt((1 + d2) * 0.5); + d2 = 0.5 / w; + cX *= d2; + cY *= d2; + cZ *= d2; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x1 = slerpQX; + let y1 = slerpQY; + let z1 = slerpQZ; + let w1 = slerpQW; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + slerpM00 = 1 - yy1 - zz1; + slerpM01 = xy1 - wz1; + slerpM02 = xz1 + wy1; + slerpM10 = xy1 + wz1; + slerpM11 = 1 - xx1 - zz1; + slerpM12 = yz1 - wx1; + slerpM20 = xz1 - wy1; + slerpM21 = yz1 + wx1; + slerpM22 = 1 - xx1 - yy1; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * prevYX + slerpM01 * prevYY + slerpM02 * prevYZ; + __tmp__Y1 = slerpM10 * prevYX + slerpM11 * prevYY + slerpM12 * prevYZ; + __tmp__Z1 = slerpM20 * prevYX + slerpM21 * prevYY + slerpM22 * prevYZ; + newYX = __tmp__X1; + newYY = __tmp__Y1; + newYZ = __tmp__Z1; + newZX = newXY * newYZ - newXZ * newYY; + newZY = newXZ * newYX - newXX * newYZ; + newZZ = newXX * newYY - newXY * newYX; + if(newZX * newZX + newZY * newZY + newZZ * newZZ > 1e-6) { + let l = newZX * newZX + newZY * newZY + newZZ * newZZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + newZX *= l; + newZY *= l; + newZZ *= l; + } else { + let x1 = newXX; + let y1 = newXY; + let z1 = newXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + newZX = 0; + newZY = z1 * d; + newZZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + newZX = -z1 * d; + newZY = 0; + newZZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } + newYX = newZY * newXZ - newZZ * newXY; + newYY = newZZ * newXX - newZX * newXZ; + newYZ = newZX * newXY - newZY * newXX; + _this.xX = newXX; + _this.xY = newXY; + _this.xZ = newXZ; + _this.yX = newYX; + _this.yY = newYY; + _this.yZ = newYZ; + _this.zX = newZX; + _this.zY = newZY; + _this.zZ = newZZ; + let rot100; + let rot101; + let rot102; + let rot110; + let rot111; + let rot112; + let rot120; + let rot121; + let rot122; + let rot200; + let rot201; + let rot202; + let rot210; + let rot211; + let rot212; + let rot220; + let rot221; + let rot222; + rot100 = this._basisX1X; + rot101 = this._basisY1X; + rot102 = this._basisZ1X; + rot110 = this._basisX1Y; + rot111 = this._basisY1Y; + rot112 = this._basisZ1Y; + rot120 = this._basisX1Z; + rot121 = this._basisY1Z; + rot122 = this._basisZ1Z; + rot200 = this._basisX2X; + rot201 = this._basisY2X; + rot202 = this._basisZ2X; + rot210 = this._basisX2Y; + rot211 = this._basisY2Y; + rot212 = this._basisZ2Y; + rot220 = this._basisX2Z; + rot221 = this._basisY2Z; + rot222 = this._basisZ2Z; + let relRot00; + let relRot01; + let relRot02; + let relRot10; + let relRot11; + let relRot12; + let relRot20; + let relRot21; + let relRot22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot200 * rot100 + rot201 * rot101 + rot202 * rot102; + __tmp__01 = rot200 * rot110 + rot201 * rot111 + rot202 * rot112; + __tmp__02 = rot200 * rot120 + rot201 * rot121 + rot202 * rot122; + __tmp__10 = rot210 * rot100 + rot211 * rot101 + rot212 * rot102; + __tmp__11 = rot210 * rot110 + rot211 * rot111 + rot212 * rot112; + __tmp__12 = rot210 * rot120 + rot211 * rot121 + rot212 * rot122; + __tmp__20 = rot220 * rot100 + rot221 * rot101 + rot222 * rot102; + __tmp__21 = rot220 * rot110 + rot221 * rot111 + rot222 * rot112; + __tmp__22 = rot220 * rot120 + rot221 * rot121 + rot222 * rot122; + relRot00 = __tmp__00; + relRot01 = __tmp__01; + relRot02 = __tmp__02; + relRot10 = __tmp__10; + relRot11 = __tmp__11; + relRot12 = __tmp__12; + relRot20 = __tmp__20; + relRot21 = __tmp__21; + relRot22 = __tmp__22; + let relQX; + let relQY; + let relQZ; + let relQW; + let e00 = relRot00; + let e11 = relRot11; + let e22 = relRot22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + relQW = 0.5 * s; + s = 0.5 / s; + relQX = (relRot21 - relRot12) * s; + relQY = (relRot02 - relRot20) * s; + relQZ = (relRot10 - relRot01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + relQX = 0.5 * s; + s = 0.5 / s; + relQY = (relRot01 + relRot10) * s; + relQZ = (relRot02 + relRot20) * s; + relQW = (relRot21 - relRot12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + relQZ = 0.5 * s; + s = 0.5 / s; + relQX = (relRot02 + relRot20) * s; + relQY = (relRot12 + relRot21) * s; + relQW = (relRot10 - relRot01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + relQY = 0.5 * s; + s = 0.5 / s; + relQX = (relRot01 + relRot10) * s; + relQZ = (relRot12 + relRot21) * s; + relQW = (relRot02 - relRot20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + relQZ = 0.5 * s; + s = 0.5 / s; + relQX = (relRot02 + relRot20) * s; + relQY = (relRot12 + relRot21) * s; + relQW = (relRot10 - relRot01) * s; + } + let cosHalfTheta = relQW; + let theta = (cosHalfTheta <= -1 ? 3.14159265358979 : cosHalfTheta >= 1 ? 0 : Math.acos(cosHalfTheta)) * 2; + this.angularErrorX = relQX; + this.angularErrorY = relQY; + this.angularErrorZ = relQZ; + let l = this.angularErrorX * this.angularErrorX + this.angularErrorY * this.angularErrorY + this.angularErrorZ * this.angularErrorZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this.angularErrorX *= l; + this.angularErrorY *= l; + this.angularErrorZ *= l; + this.angularErrorX *= theta; + this.angularErrorY *= theta; + this.angularErrorZ *= theta; + let anchorDiffX; + let anchorDiffY; + let anchorDiffZ; + anchorDiffX = this._anchor2X - this._anchor1X; + anchorDiffY = this._anchor2Y - this._anchor1Y; + anchorDiffZ = this._anchor2Z - this._anchor1Z; + this.translation = anchorDiffX * this._basis.xX + anchorDiffY * this._basis.xY + anchorDiffZ * this._basis.xZ; + this.linearErrorY = anchorDiffX * this._basis.yX + anchorDiffY * this._basis.yY + anchorDiffZ * this._basis.yZ; + this.linearErrorZ = anchorDiffX * this._basis.zX + anchorDiffY * this._basis.zY + anchorDiffZ * this._basis.zZ; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getSpringDamper() { + return this._sd; + } + getLimitMotor() { + return this._lm; + } + getTranslation() { + return this.translation; + } +} +oimo.dynamics.constraint.joint.PrismaticJointConfig = class oimo_dynamics_constraint_joint_PrismaticJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.limitMotor = new oimo.dynamics.constraint.joint.TranslationalLimitMotor(); + this.springDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis.x; + vY = worldAxis.y; + vZ = worldAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis.x; + vY1 = worldAxis.y; + vZ1 = worldAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +oimo.dynamics.constraint.joint.RagdollJoint = class oimo_dynamics_constraint_joint_RagdollJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.RAGDOLL); + let v = config.localTwistAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localSwingAxis1; + this._localBasisY1X = v1.x; + this._localBasisY1Y = v1.y; + this._localBasisY1Z = v1.z; + let v2 = config.localTwistAxis2; + this._localBasisX2X = v2.x; + this._localBasisX2Y = v2.y; + this._localBasisX2Z = v2.z; + this.buildLocalBasesFromXY1X2(); + this._twistSd = config.twistSpringDamper.clone(); + this._twistLm = config.twistLimitMotor.clone(); + this._swingSd = config.swingSpringDamper.clone(); + this._maxSwingAngle1 = config.maxSwingAngle1; + this._maxSwingAngle2 = config.maxSwingAngle2; + if(this._maxSwingAngle1 < oimo.common.Setting.minRagdollMaxSwingAngle) { + this._maxSwingAngle1 = oimo.common.Setting.minRagdollMaxSwingAngle; + } + if(this._maxSwingAngle2 < oimo.common.Setting.minRagdollMaxSwingAngle) { + this._maxSwingAngle2 = oimo.common.Setting.minRagdollMaxSwingAngle; + } + this.dummySwingLm = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.dummySwingLm.lowerLimit = -1; + this.dummySwingLm.upperLimit = 0; + this._swingAngle = 0; + this._twistAngle = 0; + this.swingError = 0; + this.swingAxisX = 0; + this.swingAxisY = 0; + this.swingAxisZ = 0; + this.twistAxisX = 0; + this.twistAxisY = 0; + this.twistAxisZ = 0; + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = this.linearErrorX * erp; + linearRhsY = this.linearErrorY * erp; + linearRhsZ = this.linearErrorZ * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let swingMass = this.computeEffectiveInertiaMoment(this.swingAxisX,this.swingAxisY,this.swingAxisZ); + let twistMass = this.computeEffectiveInertiaMoment(this._basisX2X,this._basisX2Y,this._basisX2Z); + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + if(this.swingError > 0 && (this._swingSd.frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this.swingError,this.dummySwingLm,swingMass,this._swingSd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this.swingAxisX; + j.ang1Y = this.swingAxisY; + j.ang1Z = this.swingAxisZ; + j.ang2X = this.swingAxisX; + j.ang2Y = this.swingAxisY; + j.ang2Z = this.swingAxisZ; + } + if(this._twistSd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[4]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._twistAngle,this._twistLm,twistMass,this._twistSd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this.twistAxisX; + j.ang1Y = this.twistAxisY; + j.ang1Z = this.twistAxisZ; + j.ang2X = this.twistAxisX; + j.ang2Y = this.twistAxisY; + j.ang2Z = this.twistAxisZ; + } + } + _syncAnchors() { + super._syncAnchors(); + let axis1X; + let axis1Y; + let axis1Z; + let axis2X; + let axis2Y; + let axis2Z; + axis1X = this._basisX1X; + axis1Y = this._basisX1Y; + axis1Z = this._basisX1Z; + axis2X = this._basisX2X; + axis2Y = this._basisX2Y; + axis2Z = this._basisX2Z; + let basis1Mat00; + let basis1Mat01; + let basis1Mat02; + let basis1Mat10; + let basis1Mat11; + let basis1Mat12; + let basis1Mat20; + let basis1Mat21; + let basis1Mat22; + basis1Mat00 = this._basisX1X; + basis1Mat01 = this._basisY1X; + basis1Mat02 = this._basisZ1X; + basis1Mat10 = this._basisX1Y; + basis1Mat11 = this._basisY1Y; + basis1Mat12 = this._basisZ1Y; + basis1Mat20 = this._basisX1Z; + basis1Mat21 = this._basisY1Z; + basis1Mat22 = this._basisZ1Z; + let swingQX; + let swingQY; + let swingQZ; + let swingQW; + let swingM00; + let swingM01; + let swingM02; + let swingM10; + let swingM11; + let swingM12; + let swingM20; + let swingM21; + let swingM22; + let swingVX; + let swingVY; + let swingVZ; + let d = axis1X * axis2X + axis1Y * axis2Y + axis1Z * axis2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = axis1X; + let y1 = axis1Y; + let z1 = axis1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + swingQX = vX; + swingQY = vY; + swingQZ = vZ; + swingQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = axis1Y * axis2Z - axis1Z * axis2Y; + cY = axis1Z * axis2X - axis1X * axis2Z; + cZ = axis1X * axis2Y - axis1Y * axis2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + swingQX = cX; + swingQY = cY; + swingQZ = cZ; + swingQW = w; + } + let x = swingQX; + let y = swingQY; + let z = swingQZ; + let w = swingQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + swingM00 = 1 - yy - zz; + swingM01 = xy - wz; + swingM02 = xz + wy; + swingM10 = xy + wz; + swingM11 = 1 - xx - zz; + swingM12 = yz - wx; + swingM20 = xz - wy; + swingM21 = yz + wx; + swingM22 = 1 - xx - yy; + this._swingAngle = (swingQW <= -1 ? 3.14159265358979 : swingQW >= 1 ? 0 : Math.acos(swingQW)) * 2; + swingVX = swingQX; + swingVY = swingQY; + swingVZ = swingQZ; + let basisY2In1X; + let basisY2In1Y; + let basisY2In1Z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = swingM00 * this._basisY2X + swingM10 * this._basisY2Y + swingM20 * this._basisY2Z; + __tmp__Y = swingM01 * this._basisY2X + swingM11 * this._basisY2Y + swingM21 * this._basisY2Z; + __tmp__Z = swingM02 * this._basisY2X + swingM12 * this._basisY2Y + swingM22 * this._basisY2Z; + basisY2In1X = __tmp__X; + basisY2In1Y = __tmp__Y; + basisY2In1Z = __tmp__Z; + this._twistAngle = Math.atan2(this._basisZ1X * basisY2In1X + this._basisZ1Y * basisY2In1Y + this._basisZ1Z * basisY2In1Z,this._basisY1X * basisY2In1X + this._basisY1Y * basisY2In1Y + this._basisY1Z * basisY2In1Z); + this.twistAxisX = this._basisX1X + this._basisX2X; + this.twistAxisY = this._basisX1Y + this._basisX2Y; + this.twistAxisZ = this._basisX1Z + this._basisX2Z; + let l = this.twistAxisX * this.twistAxisX + this.twistAxisY * this.twistAxisY + this.twistAxisZ * this.twistAxisZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this.twistAxisX *= l; + this.twistAxisY *= l; + this.twistAxisZ *= l; + let invLen = Math.sqrt(swingVX * swingVX + swingVY * swingVY + swingVZ * swingVZ); + if(invLen > 0) { + invLen = 1 / invLen; + } + swingVX *= invLen * this._swingAngle; + swingVY *= invLen * this._swingAngle; + swingVZ *= invLen * this._swingAngle; + + let __tmp__Y1; + let __tmp__Z1; + + __tmp__Y1 = basis1Mat01 * swingVX + basis1Mat11 * swingVY + basis1Mat21 * swingVZ; + __tmp__Z1 = basis1Mat02 * swingVX + basis1Mat12 * swingVY + basis1Mat22 * swingVZ; + + swingVY = __tmp__Y1; + swingVZ = __tmp__Z1; + let x1 = swingVY; + let y1 = swingVZ; + let a = this._maxSwingAngle1; + let b = this._maxSwingAngle2; + let invA2 = 1 / (a * a); + let invB2 = 1 / (b * b); + let w1 = x1 * x1 * invA2 + y1 * y1 * invB2; + if(w1 == 0) { + this.swingAxisX = 0; + this.swingAxisY = 0; + this.swingAxisZ = 0; + this.swingError = 0; + } else { + let t = Math.sqrt(1 / w1); + let x0 = x1 * t; + let y0 = y1 * t; + let nx = x0 * invA2; + let ny = y0 * invB2; + invLen = 1 / Math.sqrt(nx * nx + ny * ny); + nx *= invLen; + ny *= invLen; + let depth = (x1 - x0) * nx + (y1 - y0) * ny; + if(depth > 0) { + this.swingError = depth; + this.swingAxisX = 0; + this.swingAxisY = nx; + this.swingAxisZ = ny; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = basis1Mat00 * this.swingAxisX + basis1Mat01 * this.swingAxisY + basis1Mat02 * this.swingAxisZ; + __tmp__Y = basis1Mat10 * this.swingAxisX + basis1Mat11 * this.swingAxisY + basis1Mat12 * this.swingAxisZ; + __tmp__Z = basis1Mat20 * this.swingAxisX + basis1Mat21 * this.swingAxisY + basis1Mat22 * this.swingAxisZ; + this.swingAxisX = __tmp__X; + this.swingAxisY = __tmp__Y; + this.swingAxisZ = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = swingM00 * this.swingAxisX + swingM01 * this.swingAxisY + swingM02 * this.swingAxisZ; + __tmp__Y1 = swingM10 * this.swingAxisX + swingM11 * this.swingAxisY + swingM12 * this.swingAxisZ; + __tmp__Z1 = swingM20 * this.swingAxisX + swingM21 * this.swingAxisY + swingM22 * this.swingAxisZ; + this.swingAxisX = __tmp__X1; + this.swingAxisY = __tmp__Y1; + this.swingAxisZ = __tmp__Z1; + } else { + this.swingError = 0; + } + } + this.linearErrorX = this._anchor2X - this._anchor1X; + this.linearErrorY = this._anchor2Y - this._anchor1Y; + this.linearErrorZ = this._anchor2Z - this._anchor1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getTwistSpringDamper() { + return this._twistSd; + } + getTwistLimitMotor() { + return this._twistLm; + } + getSwingSpringDamper() { + return this._swingSd; + } + getSwingAxis() { + let v = new oimo.common.Vec3(); + v.x = this.swingAxisX; + v.y = this.swingAxisY; + v.z = this.swingAxisZ; + return v; + } + getSwingAxisTo(axis) { + axis.x = this.swingAxisX; + axis.y = this.swingAxisY; + axis.z = this.swingAxisZ; + } + getSwingAngle() { + return this._swingAngle; + } + getTwistAngle() { + return this._twistAngle; + } +} +oimo.dynamics.constraint.joint.RagdollJointConfig = class oimo_dynamics_constraint_joint_RagdollJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localTwistAxis1 = new oimo.common.Vec3(1,0,0); + this.localTwistAxis2 = new oimo.common.Vec3(1,0,0); + this.localSwingAxis1 = new oimo.common.Vec3(0,1,0); + this.twistSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.swingSpringDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.twistLimitMotor = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.maxSwingAngle1 = 3.14159265358979; + this.maxSwingAngle2 = 3.14159265358979; + } + init(rigidBody1,rigidBody2,worldAnchor,worldTwistAxis,worldSwingAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localTwistAxis1; + let vX; + let vY; + let vZ; + vX = worldTwistAxis.x; + vY = worldTwistAxis.y; + vZ = worldTwistAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localTwistAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldTwistAxis.x; + vY1 = worldTwistAxis.y; + vZ1 = worldTwistAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + let localVector2 = this.localSwingAxis1; + let vX2; + let vY2; + let vZ2; + vX2 = worldSwingAxis.x; + vY2 = worldSwingAxis.y; + vZ2 = worldSwingAxis.z; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = rigidBody1._transform._rotation00 * vX2 + rigidBody1._transform._rotation10 * vY2 + rigidBody1._transform._rotation20 * vZ2; + __tmp__Y2 = rigidBody1._transform._rotation01 * vX2 + rigidBody1._transform._rotation11 * vY2 + rigidBody1._transform._rotation21 * vZ2; + __tmp__Z2 = rigidBody1._transform._rotation02 * vX2 + rigidBody1._transform._rotation12 * vY2 + rigidBody1._transform._rotation22 * vZ2; + vX2 = __tmp__X2; + vY2 = __tmp__Y2; + vZ2 = __tmp__Z2; + localVector2.x = vX2; + localVector2.y = vY2; + localVector2.z = vZ2; + return this; + } +} +oimo.dynamics.constraint.joint.RevoluteJoint = class oimo_dynamics_constraint_joint_RevoluteJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,1); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisX2X = v1.x; + this._localBasisX2Y = v1.y; + this._localBasisX2Z = v1.z; + this.buildLocalBasesFromX(); + this.angle = 0; + this.angularErrorY = 0; + this.angularErrorZ = 0; + this._basis = new oimo.dynamics.constraint.joint.BasisTracker(this); + this._sd = config.springDamper.clone(); + this._lm = config.limitMotor.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = this.linearErrorX * erp; + linearRhsY = this.linearErrorY * erp; + linearRhsZ = this.linearErrorZ * erp; + let angRhsY = this.angularErrorY * erp; + let angRhsZ = this.angularErrorZ * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let motorMass = this.computeEffectiveInertiaMoment(this._basis.xX,this._basis.xY,this._basis.xZ); + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + if(this._sd.frequency <= 0 || !isPositionPart) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this.angle,this._lm,motorMass,this._sd,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._basis.xX; + j.ang1Y = this._basis.xY; + j.ang1Z = this._basis.xZ; + j.ang2X = this._basis.xX; + j.ang2Y = this._basis.xY; + j.ang2Z = this._basis.xZ; + } + let impulse3 = this._impulses[4]; + let row3 = info.rows[info.numRows++]; + let _this3 = row3.jacobian; + _this3.lin1X = 0; + _this3.lin1Y = 0; + _this3.lin1Z = 0; + _this3.lin2X = 0; + _this3.lin2Y = 0; + _this3.lin2Z = 0; + _this3.ang1X = 0; + _this3.ang1Y = 0; + _this3.ang1Z = 0; + _this3.ang2X = 0; + _this3.ang2Y = 0; + _this3.ang2Z = 0; + row3.rhs = 0; + row3.cfm = 0; + row3.minImpulse = 0; + row3.maxImpulse = 0; + row3.motorSpeed = 0; + row3.motorMaxImpulse = 0; + row3.impulse = null; + row3.impulse = impulse3; + row3.rhs = angRhsY; + row3.cfm = 0; + row3.minImpulse = -1e65536; + row3.maxImpulse = 1e65536; + j = row3.jacobian; + j.ang1X = this._basis.yX; + j.ang1Y = this._basis.yY; + j.ang1Z = this._basis.yZ; + j.ang2X = this._basis.yX; + j.ang2Y = this._basis.yY; + j.ang2Z = this._basis.yZ; + let impulse4 = this._impulses[5]; + let row4 = info.rows[info.numRows++]; + let _this4 = row4.jacobian; + _this4.lin1X = 0; + _this4.lin1Y = 0; + _this4.lin1Z = 0; + _this4.lin2X = 0; + _this4.lin2Y = 0; + _this4.lin2Z = 0; + _this4.ang1X = 0; + _this4.ang1Y = 0; + _this4.ang1Z = 0; + _this4.ang2X = 0; + _this4.ang2Y = 0; + _this4.ang2Z = 0; + row4.rhs = 0; + row4.cfm = 0; + row4.minImpulse = 0; + row4.maxImpulse = 0; + row4.motorSpeed = 0; + row4.motorMaxImpulse = 0; + row4.impulse = null; + row4.impulse = impulse4; + row4.rhs = angRhsZ; + row4.cfm = 0; + row4.minImpulse = -1e65536; + row4.maxImpulse = 1e65536; + j = row4.jacobian; + j.ang1X = this._basis.zX; + j.ang1Y = this._basis.zY; + j.ang1Z = this._basis.zZ; + j.ang2X = this._basis.zX; + j.ang2Y = this._basis.zY; + j.ang2Z = this._basis.zZ; + } + _syncAnchors() { + super._syncAnchors(); + let _this = this._basis; + let invM1 = _this.joint._b1._invMass; + let invM2 = _this.joint._b2._invMass; + let qX; + let qY; + let qZ; + let qW; + let idQX; + let idQY; + let idQZ; + let idQW; + let slerpQX; + let slerpQY; + let slerpQZ; + let slerpQW; + let slerpM00; + let slerpM01; + let slerpM02; + let slerpM10; + let slerpM11; + let slerpM12; + let slerpM20; + let slerpM21; + let slerpM22; + let newXX; + let newXY; + let newXZ; + let newYX; + let newYY; + let newYZ; + let newZX; + let newZY; + let newZZ; + let prevXX; + let prevXY; + let prevXZ; + let prevYX; + let prevYY; + let prevYZ; + let d = _this.joint._basisX1X * _this.joint._basisX2X + _this.joint._basisX1Y * _this.joint._basisX2Y + _this.joint._basisX1Z * _this.joint._basisX2Z; + if(d < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = _this.joint._basisX1X; + let y1 = _this.joint._basisX1Y; + let z1 = _this.joint._basisX1Z; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + qX = vX; + qY = vY; + qZ = vZ; + qW = 0; + } else { + let cX; + let cY; + let cZ; + cX = _this.joint._basisX1Y * _this.joint._basisX2Z - _this.joint._basisX1Z * _this.joint._basisX2Y; + cY = _this.joint._basisX1Z * _this.joint._basisX2X - _this.joint._basisX1X * _this.joint._basisX2Z; + cZ = _this.joint._basisX1X * _this.joint._basisX2Y - _this.joint._basisX1Y * _this.joint._basisX2X; + let w = Math.sqrt((1 + d) * 0.5); + d = 0.5 / w; + cX *= d; + cY *= d; + cZ *= d; + qX = cX; + qY = cY; + qZ = cZ; + qW = w; + } + idQX = 0; + idQY = 0; + idQZ = 0; + idQW = 1; + let q1X; + let q1Y; + let q1Z; + let q1W; + let q2X; + let q2Y; + let q2Z; + let q2W; + q1X = idQX; + q1Y = idQY; + q1Z = idQZ; + q1W = idQW; + q2X = qX; + q2Y = qY; + q2Z = qZ; + q2W = qW; + let d1 = q1X * q2X + q1Y * q2Y + q1Z * q2Z + q1W * q2W; + if(d1 < 0) { + d1 = -d1; + q2X = -q2X; + q2Y = -q2Y; + q2Z = -q2Z; + q2W = -q2W; + } + if(d1 > 0.999999) { + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = q2X - q1X; + dqY = q2Y - q1Y; + dqZ = q2Z - q1Z; + dqW = q2W - q1W; + q2X = q1X + dqX * (invM1 / (invM1 + invM2)); + q2Y = q1Y + dqY * (invM1 / (invM1 + invM2)); + q2Z = q1Z + dqZ * (invM1 / (invM1 + invM2)); + q2W = q1W + dqW * (invM1 / (invM1 + invM2)); + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + slerpQX = q2X * l; + slerpQY = q2Y * l; + slerpQZ = q2Z * l; + slerpQW = q2W * l; + } else { + let theta = invM1 / (invM1 + invM2) * Math.acos(d1); + q2X += q1X * -d1; + q2Y += q1Y * -d1; + q2Z += q1Z * -d1; + q2W += q1W * -d1; + let l = q2X * q2X + q2Y * q2Y + q2Z * q2Z + q2W * q2W; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + q2X *= l; + q2Y *= l; + q2Z *= l; + q2W *= l; + let sin = Math.sin(theta); + let cos = Math.cos(theta); + q1X *= cos; + q1Y *= cos; + q1Z *= cos; + q1W *= cos; + slerpQX = q1X + q2X * sin; + slerpQY = q1Y + q2Y * sin; + slerpQZ = q1Z + q2Z * sin; + slerpQW = q1W + q2W * sin; + } + let x = slerpQX; + let y = slerpQY; + let z = slerpQZ; + let w = slerpQW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + slerpM00 = 1 - yy - zz; + slerpM01 = xy - wz; + slerpM02 = xz + wy; + slerpM10 = xy + wz; + slerpM11 = 1 - xx - zz; + slerpM12 = yz - wx; + slerpM20 = xz - wy; + slerpM21 = yz + wx; + slerpM22 = 1 - xx - yy; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = slerpM00 * _this.joint._basisX1X + slerpM01 * _this.joint._basisX1Y + slerpM02 * _this.joint._basisX1Z; + __tmp__Y = slerpM10 * _this.joint._basisX1X + slerpM11 * _this.joint._basisX1Y + slerpM12 * _this.joint._basisX1Z; + __tmp__Z = slerpM20 * _this.joint._basisX1X + slerpM21 * _this.joint._basisX1Y + slerpM22 * _this.joint._basisX1Z; + newXX = __tmp__X; + newXY = __tmp__Y; + newXZ = __tmp__Z; + prevXX = _this.xX; + prevXY = _this.xY; + prevXZ = _this.xZ; + prevYX = _this.yX; + prevYY = _this.yY; + prevYZ = _this.yZ; + let d2 = prevXX * newXX + prevXY * newXY + prevXZ * newXZ; + if(d2 < -0.999999999) { + let vX; + let vY; + let vZ; + let x1 = prevXX; + let y1 = prevXY; + let z1 = prevXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + vX = 0; + vY = z1 * d; + vZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + vX = -z1 * d; + vY = 0; + vZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + vX = y1 * d; + vY = -x1 * d; + vZ = 0; + } + slerpQX = vX; + slerpQY = vY; + slerpQZ = vZ; + slerpQW = 0; + } else { + let cX; + let cY; + let cZ; + cX = prevXY * newXZ - prevXZ * newXY; + cY = prevXZ * newXX - prevXX * newXZ; + cZ = prevXX * newXY - prevXY * newXX; + let w = Math.sqrt((1 + d2) * 0.5); + d2 = 0.5 / w; + cX *= d2; + cY *= d2; + cZ *= d2; + slerpQX = cX; + slerpQY = cY; + slerpQZ = cZ; + slerpQW = w; + } + let x1 = slerpQX; + let y1 = slerpQY; + let z1 = slerpQZ; + let w1 = slerpQW; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + slerpM00 = 1 - yy1 - zz1; + slerpM01 = xy1 - wz1; + slerpM02 = xz1 + wy1; + slerpM10 = xy1 + wz1; + slerpM11 = 1 - xx1 - zz1; + slerpM12 = yz1 - wx1; + slerpM20 = xz1 - wy1; + slerpM21 = yz1 + wx1; + slerpM22 = 1 - xx1 - yy1; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = slerpM00 * prevYX + slerpM01 * prevYY + slerpM02 * prevYZ; + __tmp__Y1 = slerpM10 * prevYX + slerpM11 * prevYY + slerpM12 * prevYZ; + __tmp__Z1 = slerpM20 * prevYX + slerpM21 * prevYY + slerpM22 * prevYZ; + newYX = __tmp__X1; + newYY = __tmp__Y1; + newYZ = __tmp__Z1; + newZX = newXY * newYZ - newXZ * newYY; + newZY = newXZ * newYX - newXX * newYZ; + newZZ = newXX * newYY - newXY * newYX; + if(newZX * newZX + newZY * newZY + newZZ * newZZ > 1e-6) { + let l = newZX * newZX + newZY * newZY + newZZ * newZZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + newZX *= l; + newZY *= l; + newZZ *= l; + } else { + let x1 = newXX; + let y1 = newXY; + let z1 = newXZ; + let x2 = x1 * x1; + let y2 = y1 * y1; + let z2 = z1 * z1; + let d; + if(x2 < y2) { + if(x2 < z2) { + d = 1 / Math.sqrt(y2 + z2); + newZX = 0; + newZY = z1 * d; + newZZ = -y1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } else if(y2 < z2) { + d = 1 / Math.sqrt(z2 + x2); + newZX = -z1 * d; + newZY = 0; + newZZ = x1 * d; + } else { + d = 1 / Math.sqrt(x2 + y2); + newZX = y1 * d; + newZY = -x1 * d; + newZZ = 0; + } + } + newYX = newZY * newXZ - newZZ * newXY; + newYY = newZZ * newXX - newZX * newXZ; + newYZ = newZX * newXY - newZY * newXX; + _this.xX = newXX; + _this.xY = newXY; + _this.xZ = newXZ; + _this.yX = newYX; + _this.yY = newYY; + _this.yZ = newYZ; + _this.zX = newZX; + _this.zY = newZY; + _this.zZ = newZZ; + let angErrorX; + let angErrorY; + let angErrorZ; + angErrorX = this._basisX1Y * this._basisX2Z - this._basisX1Z * this._basisX2Y; + angErrorY = this._basisX1Z * this._basisX2X - this._basisX1X * this._basisX2Z; + angErrorZ = this._basisX1X * this._basisX2Y - this._basisX1Y * this._basisX2X; + let cos = this._basisX1X * this._basisX2X + this._basisX1Y * this._basisX2Y + this._basisX1Z * this._basisX2Z; + let theta = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + let l = angErrorX * angErrorX + angErrorY * angErrorY + angErrorZ * angErrorZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + angErrorX *= l; + angErrorY *= l; + angErrorZ *= l; + angErrorX *= theta; + angErrorY *= theta; + angErrorZ *= theta; + this.angularErrorY = angErrorX * this._basis.yX + angErrorY * this._basis.yY + angErrorZ * this._basis.yZ; + this.angularErrorZ = angErrorX * this._basis.zX + angErrorY * this._basis.zY + angErrorZ * this._basis.zZ; + let perpCrossX; + let perpCrossY; + let perpCrossZ; + perpCrossX = this._basisY1Y * this._basisY2Z - this._basisY1Z * this._basisY2Y; + perpCrossY = this._basisY1Z * this._basisY2X - this._basisY1X * this._basisY2Z; + perpCrossZ = this._basisY1X * this._basisY2Y - this._basisY1Y * this._basisY2X; + cos = this._basisY1X * this._basisY2X + this._basisY1Y * this._basisY2Y + this._basisY1Z * this._basisY2Z; + this.angle = cos <= -1 ? 3.14159265358979 : cos >= 1 ? 0 : Math.acos(cos); + if(perpCrossX * this._basis.xX + perpCrossY * this._basis.xY + perpCrossZ * this._basis.xZ < 0) { + this.angle = -this.angle; + } + this.linearErrorX = this._anchor2X - this._anchor1X; + this.linearErrorY = this._anchor2Y - this._anchor1Y; + this.linearErrorZ = this._anchor2Z - this._anchor1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisX2X; + v.y = this._basisX2Y; + v.z = this._basisX2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisX2X; + axis.y = this._basisX2Y; + axis.z = this._basisX2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX2X; + v.y = this._localBasisX2Y; + v.z = this._localBasisX2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisX2X; + axis.y = this._localBasisX2Y; + axis.z = this._localBasisX2Z; + } + getSpringDamper() { + return this._sd; + } + getLimitMotor() { + return this._lm; + } + getAngle() { + return this.angle; + } +} +oimo.dynamics.constraint.joint.RevoluteJointConfig = class oimo_dynamics_constraint_joint_RevoluteJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.springDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + this.limitMotor = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis.x; + vY = worldAxis.y; + vZ = worldAxis.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis.x; + vY1 = worldAxis.y; + vZ1 = worldAxis.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +oimo.dynamics.constraint.joint.RotationalLimitMotor = class oimo_dynamics_constraint_joint_RotationalLimitMotor { + constructor() { + this.lowerLimit = 1; + this.upperLimit = 0; + this.motorTorque = 0; + } + setLimits(lower,upper) { + this.lowerLimit = lower; + this.upperLimit = upper; + return this; + } + setMotor(speed,torque) { + this.motorSpeed = speed; + this.motorTorque = torque; + return this; + } + clone() { + let lm = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + lm.lowerLimit = this.lowerLimit; + lm.upperLimit = this.upperLimit; + lm.motorSpeed = this.motorSpeed; + lm.motorTorque = this.motorTorque; + return lm; + } +} +oimo.dynamics.constraint.joint.SphericalJoint = class oimo_dynamics_constraint_joint_SphericalJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,0); + this._sd = config.springDamper.clone(); + } + getInfo(info,timeStep,isPositionPart) { + if(this._sd.frequency > 0 && isPositionPart) { + return; + } + let errorX; + let errorY; + let errorZ; + errorX = this._anchor2X - this._anchor1X; + errorY = this._anchor2Y - this._anchor1Y; + errorZ = this._anchor2Z - this._anchor1Z; + let cfm; + let erp; + if(this._sd.frequency > 0) { + let omega = 6.28318530717958 * this._sd.frequency; + let zeta = this._sd.dampingRatio; + if(zeta < oimo.common.Setting.minSpringDamperDampingRatio) { + zeta = oimo.common.Setting.minSpringDamperDampingRatio; + } + let h = timeStep.dt; + let c = 2 * zeta * omega; + let k = omega * omega; + if(this._sd.useSymplecticEuler) { + cfm = 1 / (h * c); + erp = k / c; + } else { + cfm = 1 / (h * (h * k + c)); + erp = k / (h * k + c); + } + cfm *= this._b1._invMass + this._b2._invMass; + } else { + cfm = 0; + erp = this.getErp(timeStep,isPositionPart); + } + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = errorX * erp; + linearRhsY = errorY * erp; + linearRhsZ = errorZ * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = cfm; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = cfm; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = cfm; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getSpringDamper() { + return this._sd; + } +} +oimo.dynamics.constraint.joint.SphericalJointConfig = class oimo_dynamics_constraint_joint_SphericalJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.springDamper = new oimo.dynamics.constraint.joint.SpringDamper(); + } + init(rigidBody1,rigidBody2,worldAnchor) { + this._init(rigidBody1,rigidBody2,worldAnchor); + return this; + } +} +oimo.dynamics.constraint.joint.SpringDamper = class oimo_dynamics_constraint_joint_SpringDamper { + constructor() { + this.frequency = 0; + this.dampingRatio = 0; + this.useSymplecticEuler = false; + } + setSpring(frequency,dampingRatio) { + this.frequency = frequency; + this.dampingRatio = dampingRatio; + return this; + } + setSymplecticEuler(useSymplecticEuler) { + this.useSymplecticEuler = useSymplecticEuler; + return this; + } + clone() { + let sd = new oimo.dynamics.constraint.joint.SpringDamper(); + sd.frequency = this.frequency; + sd.dampingRatio = this.dampingRatio; + sd.useSymplecticEuler = this.useSymplecticEuler; + return sd; + } +} +oimo.dynamics.constraint.joint.TranslationalLimitMotor = class oimo_dynamics_constraint_joint_TranslationalLimitMotor { + constructor() { + this.lowerLimit = 1; + this.upperLimit = 0; + this.motorForce = 0; + } + setLimits(lower,upper) { + this.lowerLimit = lower; + this.upperLimit = upper; + return this; + } + setMotor(speed,force) { + this.motorSpeed = speed; + this.motorForce = force; + return this; + } + clone() { + let lm = new oimo.dynamics.constraint.joint.TranslationalLimitMotor(); + lm.lowerLimit = this.lowerLimit; + lm.upperLimit = this.upperLimit; + lm.motorSpeed = this.motorSpeed; + lm.motorForce = this.motorForce; + return lm; + } +} +oimo.dynamics.constraint.joint.UniversalJoint = class oimo_dynamics_constraint_joint_UniversalJoint extends oimo.dynamics.constraint.joint.Joint { + constructor(config) { + super(config,oimo.dynamics.constraint.joint.JointType.UNIVERSAL); + let v = config.localAxis1; + this._localBasisX1X = v.x; + this._localBasisX1Y = v.y; + this._localBasisX1Z = v.z; + let v1 = config.localAxis2; + this._localBasisZ2X = v1.x; + this._localBasisZ2Y = v1.y; + this._localBasisZ2Z = v1.z; + this.buildLocalBasesFromX1Z2(); + this._angleX = 0; + this._angleY = 0; + this._angleZ = 0; + this.xSingular = false; + this.ySingular = false; + this.zSingular = false; + this._sd1 = config.springDamper1.clone(); + this._sd2 = config.springDamper2.clone(); + this._lm1 = config.limitMotor1.clone(); + this._lm2 = config.limitMotor2.clone(); + } + getInfo(info,timeStep,isPositionPart) { + let erp = this.getErp(timeStep,isPositionPart); + let linearRhsX; + let linearRhsY; + let linearRhsZ; + linearRhsX = this.linearErrorX * erp; + linearRhsY = this.linearErrorY * erp; + linearRhsZ = this.linearErrorZ * erp; + let angRhsY = this._angleY * erp; + let crossR100; + let crossR101; + let crossR102; + let crossR110; + let crossR111; + let crossR112; + let crossR120; + let crossR121; + let crossR122; + let crossR200; + let crossR201; + let crossR202; + let crossR210; + let crossR211; + let crossR212; + let crossR220; + let crossR221; + let crossR222; + crossR100 = 0; + crossR101 = -this._relativeAnchor1Z; + crossR102 = this._relativeAnchor1Y; + crossR110 = this._relativeAnchor1Z; + crossR111 = 0; + crossR112 = -this._relativeAnchor1X; + crossR120 = -this._relativeAnchor1Y; + crossR121 = this._relativeAnchor1X; + crossR122 = 0; + crossR200 = 0; + crossR201 = -this._relativeAnchor2Z; + crossR202 = this._relativeAnchor2Y; + crossR210 = this._relativeAnchor2Z; + crossR211 = 0; + crossR212 = -this._relativeAnchor2X; + crossR220 = -this._relativeAnchor2Y; + crossR221 = this._relativeAnchor2X; + crossR222 = 0; + crossR100 = -crossR100; + crossR101 = -crossR101; + crossR102 = -crossR102; + crossR110 = -crossR110; + crossR111 = -crossR111; + crossR112 = -crossR112; + crossR120 = -crossR120; + crossR121 = -crossR121; + crossR122 = -crossR122; + crossR200 = -crossR200; + crossR201 = -crossR201; + crossR202 = -crossR202; + crossR210 = -crossR210; + crossR211 = -crossR211; + crossR212 = -crossR212; + crossR220 = -crossR220; + crossR221 = -crossR221; + crossR222 = -crossR222; + let motorMassX = this.computeEffectiveInertiaMoment(this._axisXX,this._axisXY,this._axisXZ); + let motorMassZ = this.computeEffectiveInertiaMoment(this._axisZX,this._axisZY,this._axisZZ); + let impulse = this._impulses[0]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = linearRhsX; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + let j = row.jacobian; + j.lin1X = 1; + j.lin1Y = 0; + j.lin1Z = 0; + j.lin2X = 1; + j.lin2Y = 0; + j.lin2Z = 0; + j.ang1X = crossR100; + j.ang1Y = crossR101; + j.ang1Z = crossR102; + j.ang2X = crossR200; + j.ang2Y = crossR201; + j.ang2Z = crossR202; + let impulse1 = this._impulses[1]; + let row1 = info.rows[info.numRows++]; + let _this1 = row1.jacobian; + _this1.lin1X = 0; + _this1.lin1Y = 0; + _this1.lin1Z = 0; + _this1.lin2X = 0; + _this1.lin2Y = 0; + _this1.lin2Z = 0; + _this1.ang1X = 0; + _this1.ang1Y = 0; + _this1.ang1Z = 0; + _this1.ang2X = 0; + _this1.ang2Y = 0; + _this1.ang2Z = 0; + row1.rhs = 0; + row1.cfm = 0; + row1.minImpulse = 0; + row1.maxImpulse = 0; + row1.motorSpeed = 0; + row1.motorMaxImpulse = 0; + row1.impulse = null; + row1.impulse = impulse1; + row1.rhs = linearRhsY; + row1.cfm = 0; + row1.minImpulse = -1e65536; + row1.maxImpulse = 1e65536; + j = row1.jacobian; + j.lin1X = 0; + j.lin1Y = 1; + j.lin1Z = 0; + j.lin2X = 0; + j.lin2Y = 1; + j.lin2Z = 0; + j.ang1X = crossR110; + j.ang1Y = crossR111; + j.ang1Z = crossR112; + j.ang2X = crossR210; + j.ang2Y = crossR211; + j.ang2Z = crossR212; + let impulse2 = this._impulses[2]; + let row2 = info.rows[info.numRows++]; + let _this2 = row2.jacobian; + _this2.lin1X = 0; + _this2.lin1Y = 0; + _this2.lin1Z = 0; + _this2.lin2X = 0; + _this2.lin2Y = 0; + _this2.lin2Z = 0; + _this2.ang1X = 0; + _this2.ang1Y = 0; + _this2.ang1Z = 0; + _this2.ang2X = 0; + _this2.ang2Y = 0; + _this2.ang2Z = 0; + row2.rhs = 0; + row2.cfm = 0; + row2.minImpulse = 0; + row2.maxImpulse = 0; + row2.motorSpeed = 0; + row2.motorMaxImpulse = 0; + row2.impulse = null; + row2.impulse = impulse2; + row2.rhs = linearRhsZ; + row2.cfm = 0; + row2.minImpulse = -1e65536; + row2.maxImpulse = 1e65536; + j = row2.jacobian; + j.lin1X = 0; + j.lin1Y = 0; + j.lin1Z = 1; + j.lin2X = 0; + j.lin2Y = 0; + j.lin2Z = 1; + j.ang1X = crossR120; + j.ang1Y = crossR121; + j.ang1Z = crossR122; + j.ang2X = crossR220; + j.ang2Y = crossR221; + j.ang2Z = crossR222; + if(!this.xSingular && (this._sd1.frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[3]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleX,this._lm1,motorMassX,this._sd1,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisXX; + j.ang1Y = this._axisXY; + j.ang1Z = this._axisXZ; + j.ang2X = this._axisXX; + j.ang2Y = this._axisXY; + j.ang2Z = this._axisXZ; + } + if(!this.ySingular) { + let impulse = this._impulses[4]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + row.rhs = angRhsY; + row.cfm = 0; + row.minImpulse = -1e65536; + row.maxImpulse = 1e65536; + j = row.jacobian; + j.ang1X = this._axisYX; + j.ang1Y = this._axisYY; + j.ang1Z = this._axisYZ; + j.ang2X = this._axisYX; + j.ang2Y = this._axisYY; + j.ang2Z = this._axisYZ; + } + if(!this.zSingular && (this._sd2.frequency <= 0 || !isPositionPart)) { + let impulse = this._impulses[5]; + let row = info.rows[info.numRows++]; + let _this = row.jacobian; + _this.lin1X = 0; + _this.lin1Y = 0; + _this.lin1Z = 0; + _this.lin2X = 0; + _this.lin2Y = 0; + _this.lin2Z = 0; + _this.ang1X = 0; + _this.ang1Y = 0; + _this.ang1Z = 0; + _this.ang2X = 0; + _this.ang2Y = 0; + _this.ang2Z = 0; + row.rhs = 0; + row.cfm = 0; + row.minImpulse = 0; + row.maxImpulse = 0; + row.motorSpeed = 0; + row.motorMaxImpulse = 0; + row.impulse = null; + row.impulse = impulse; + this.setSolverInfoRowAngular(row,this._angleZ,this._lm2,motorMassZ,this._sd2,timeStep,isPositionPart); + j = row.jacobian; + j.ang1X = this._axisZX; + j.ang1Y = this._axisZY; + j.ang1Z = this._axisZZ; + j.ang2X = this._axisZX; + j.ang2Y = this._axisZY; + j.ang2Z = this._axisZZ; + } + } + _syncAnchors() { + super._syncAnchors(); + let angleAxisXX; + let angleAxisXY; + let angleAxisXZ; + let angleAxisYX; + let angleAxisYY; + let angleAxisYZ; + let angleAxisZX; + let angleAxisZY; + let angleAxisZZ; + angleAxisXX = this._basisX1X; + angleAxisXY = this._basisX1Y; + angleAxisXZ = this._basisX1Z; + angleAxisZX = this._basisZ2X; + angleAxisZY = this._basisZ2Y; + angleAxisZZ = this._basisZ2Z; + angleAxisYX = angleAxisZY * angleAxisXZ - angleAxisZZ * angleAxisXY; + angleAxisYY = angleAxisZZ * angleAxisXX - angleAxisZX * angleAxisXZ; + angleAxisYZ = angleAxisZX * angleAxisXY - angleAxisZY * angleAxisXX; + this._axisXX = angleAxisYY * angleAxisZZ - angleAxisYZ * angleAxisZY; + this._axisXY = angleAxisYZ * angleAxisZX - angleAxisYX * angleAxisZZ; + this._axisXZ = angleAxisYX * angleAxisZY - angleAxisYY * angleAxisZX; + this._axisYX = angleAxisYX; + this._axisYY = angleAxisYY; + this._axisYZ = angleAxisYZ; + this._axisZX = angleAxisXY * angleAxisYZ - angleAxisXZ * angleAxisYY; + this._axisZY = angleAxisXZ * angleAxisYX - angleAxisXX * angleAxisYZ; + this._axisZZ = angleAxisXX * angleAxisYY - angleAxisXY * angleAxisYX; + let l = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ; + if(l > 0) { + l = 1 / Math.sqrt(l); + } + this._axisXX *= l; + this._axisXY *= l; + this._axisXZ *= l; + let l1 = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ; + if(l1 > 0) { + l1 = 1 / Math.sqrt(l1); + } + this._axisYX *= l1; + this._axisYY *= l1; + this._axisYZ *= l1; + let l2 = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ; + if(l2 > 0) { + l2 = 1 / Math.sqrt(l2); + } + this._axisZX *= l2; + this._axisZY *= l2; + this._axisZZ *= l2; + this.xSingular = this._axisXX * this._axisXX + this._axisXY * this._axisXY + this._axisXZ * this._axisXZ == 0; + this.ySingular = this._axisYX * this._axisYX + this._axisYY * this._axisYY + this._axisYZ * this._axisYZ == 0; + this.zSingular = this._axisZX * this._axisZX + this._axisZY * this._axisZY + this._axisZZ * this._axisZZ == 0; + let rot100; + let rot101; + let rot102; + let rot110; + let rot111; + let rot112; + let rot120; + let rot121; + let rot122; + let rot200; + let rot201; + let rot202; + let rot210; + let rot211; + let rot212; + let rot220; + let rot221; + let rot222; + rot100 = this._basisX1X; + rot101 = this._basisY1X; + rot102 = this._basisZ1X; + rot110 = this._basisX1Y; + rot111 = this._basisY1Y; + rot112 = this._basisZ1Y; + rot120 = this._basisX1Z; + rot121 = this._basisY1Z; + rot122 = this._basisZ1Z; + rot200 = this._basisX2X; + rot201 = this._basisY2X; + rot202 = this._basisZ2X; + rot210 = this._basisX2Y; + rot211 = this._basisY2Y; + rot212 = this._basisZ2Y; + rot220 = this._basisX2Z; + rot221 = this._basisY2Z; + rot222 = this._basisZ2Z; + let relRot00; + let relRot01; + let relRot02; + let relRot11; + let relRot12; + let relRot21; + let relRot22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__11; + let __tmp__12; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot100 * rot200 + rot110 * rot210 + rot120 * rot220; + __tmp__01 = rot100 * rot201 + rot110 * rot211 + rot120 * rot221; + __tmp__02 = rot100 * rot202 + rot110 * rot212 + rot120 * rot222; + __tmp__11 = rot101 * rot201 + rot111 * rot211 + rot121 * rot221; + __tmp__12 = rot101 * rot202 + rot111 * rot212 + rot121 * rot222; + __tmp__21 = rot102 * rot201 + rot112 * rot211 + rot122 * rot221; + __tmp__22 = rot102 * rot202 + rot112 * rot212 + rot122 * rot222; + relRot00 = __tmp__00; + relRot01 = __tmp__01; + relRot02 = __tmp__02; + relRot11 = __tmp__11; + relRot12 = __tmp__12; + relRot21 = __tmp__21; + relRot22 = __tmp__22; + let anglesX; + let anglesY; + let anglesZ; + let sy = relRot02; + if(sy <= -1) { + let xSubZ = Math.atan2(relRot21,relRot11); + anglesX = xSubZ * 0.5; + anglesY = -1.570796326794895; + anglesZ = -xSubZ * 0.5; + } else if(sy >= 1) { + let xAddZ = Math.atan2(relRot21,relRot11); + anglesX = xAddZ * 0.5; + anglesY = 1.570796326794895; + anglesZ = xAddZ * 0.5; + } else { + anglesX = Math.atan2(-relRot12,relRot22); + anglesY = Math.asin(sy); + anglesZ = Math.atan2(-relRot01,relRot00); + } + this._angleX = anglesX; + this._angleY = anglesY; + this._angleZ = anglesZ; + this.linearErrorX = this._anchor2X - this._anchor1X; + this.linearErrorY = this._anchor2Y - this._anchor1Y; + this.linearErrorZ = this._anchor2Z - this._anchor1Z; + } + _getVelocitySolverInfo(timeStep,info) { + super._getVelocitySolverInfo(timeStep,info); + this.getInfo(info,timeStep,false); + } + _getPositionSolverInfo(info) { + super._getPositionSolverInfo(info); + this.getInfo(info,null,true); + } + getAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._basisX1X; + v.y = this._basisX1Y; + v.z = this._basisX1Z; + return v; + } + getAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._basisZ2X; + v.y = this._basisZ2Y; + v.z = this._basisZ2Z; + return v; + } + getAxis1To(axis) { + axis.x = this._basisX1X; + axis.y = this._basisX1Y; + axis.z = this._basisX1Z; + } + getAxis2To(axis) { + axis.x = this._basisZ2X; + axis.y = this._basisZ2Y; + axis.z = this._basisZ2Z; + } + getLocalAxis1() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisX1X; + v.y = this._localBasisX1Y; + v.z = this._localBasisX1Z; + return v; + } + getLocalAxis2() { + let v = new oimo.common.Vec3(); + v.x = this._localBasisZ2X; + v.y = this._localBasisZ2Y; + v.z = this._localBasisZ2Z; + return v; + } + getLocalAxis1To(axis) { + axis.x = this._localBasisX1X; + axis.y = this._localBasisX1Y; + axis.z = this._localBasisX1Z; + } + getLocalAxis2To(axis) { + axis.x = this._localBasisZ2X; + axis.y = this._localBasisZ2Y; + axis.z = this._localBasisZ2Z; + } + getSpringDamper1() { + return this._sd1; + } + getSpringDamper2() { + return this._sd2; + } + getLimitMotor1() { + return this._lm1; + } + getLimitMotor2() { + return this._lm2; + } + getAngle1() { + return this._angleX; + } + getAngle2() { + return this._angleZ; + } +} +oimo.dynamics.constraint.joint.UniversalJointConfig = class oimo_dynamics_constraint_joint_UniversalJointConfig extends oimo.dynamics.constraint.joint.JointConfig { + constructor() { + super(); + this.localAxis1 = new oimo.common.Vec3(1,0,0); + this.localAxis2 = new oimo.common.Vec3(1,0,0); + this.springDamper1 = new oimo.dynamics.constraint.joint.SpringDamper(); + this.springDamper2 = new oimo.dynamics.constraint.joint.SpringDamper(); + this.limitMotor1 = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + this.limitMotor2 = new oimo.dynamics.constraint.joint.RotationalLimitMotor(); + } + init(rigidBody1,rigidBody2,worldAnchor,worldAxis1,worldAxis2) { + this._init(rigidBody1,rigidBody2,worldAnchor); + let localVector = this.localAxis1; + let vX; + let vY; + let vZ; + vX = worldAxis1.x; + vY = worldAxis1.y; + vZ = worldAxis1.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = rigidBody1._transform._rotation00 * vX + rigidBody1._transform._rotation10 * vY + rigidBody1._transform._rotation20 * vZ; + __tmp__Y = rigidBody1._transform._rotation01 * vX + rigidBody1._transform._rotation11 * vY + rigidBody1._transform._rotation21 * vZ; + __tmp__Z = rigidBody1._transform._rotation02 * vX + rigidBody1._transform._rotation12 * vY + rigidBody1._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + let localVector1 = this.localAxis2; + let vX1; + let vY1; + let vZ1; + vX1 = worldAxis2.x; + vY1 = worldAxis2.y; + vZ1 = worldAxis2.z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = rigidBody2._transform._rotation00 * vX1 + rigidBody2._transform._rotation10 * vY1 + rigidBody2._transform._rotation20 * vZ1; + __tmp__Y1 = rigidBody2._transform._rotation01 * vX1 + rigidBody2._transform._rotation11 * vY1 + rigidBody2._transform._rotation21 * vZ1; + __tmp__Z1 = rigidBody2._transform._rotation02 * vX1 + rigidBody2._transform._rotation12 * vY1 + rigidBody2._transform._rotation22 * vZ1; + vX1 = __tmp__X1; + vY1 = __tmp__Y1; + vZ1 = __tmp__Z1; + localVector1.x = vX1; + localVector1.y = vY1; + localVector1.z = vZ1; + return this; + } +} +if(!oimo.dynamics.constraint.solver) oimo.dynamics.constraint.solver = {}; +oimo.dynamics.constraint.solver.ConstraintSolverType = class oimo_dynamics_constraint_solver_ConstraintSolverType { +} +if(!oimo.dynamics.constraint.solver.common) oimo.dynamics.constraint.solver.common = {}; +oimo.dynamics.constraint.solver.common.ContactSolverMassDataRow = class oimo_dynamics_constraint_solver_common_ContactSolverMassDataRow { + constructor() { + this.invMLinN1X = 0; + this.invMLinN1Y = 0; + this.invMLinN1Z = 0; + this.invMLinN2X = 0; + this.invMLinN2Y = 0; + this.invMLinN2Z = 0; + this.invMAngN1X = 0; + this.invMAngN1Y = 0; + this.invMAngN1Z = 0; + this.invMAngN2X = 0; + this.invMAngN2Y = 0; + this.invMAngN2Z = 0; + this.invMLinT1X = 0; + this.invMLinT1Y = 0; + this.invMLinT1Z = 0; + this.invMLinT2X = 0; + this.invMLinT2Y = 0; + this.invMLinT2Z = 0; + this.invMAngT1X = 0; + this.invMAngT1Y = 0; + this.invMAngT1Z = 0; + this.invMAngT2X = 0; + this.invMAngT2Y = 0; + this.invMAngT2Z = 0; + this.invMLinB1X = 0; + this.invMLinB1Y = 0; + this.invMLinB1Z = 0; + this.invMLinB2X = 0; + this.invMLinB2Y = 0; + this.invMLinB2Z = 0; + this.invMAngB1X = 0; + this.invMAngB1Y = 0; + this.invMAngB1Z = 0; + this.invMAngB2X = 0; + this.invMAngB2Y = 0; + this.invMAngB2Z = 0; + this.massN = 0; + this.massTB00 = 0; + this.massTB01 = 0; + this.massTB10 = 0; + this.massTB11 = 0; + } +} +oimo.dynamics.constraint.solver.common.JointSolverMassDataRow = class oimo_dynamics_constraint_solver_common_JointSolverMassDataRow { + constructor() { + this.invMLin1X = 0; + this.invMLin1Y = 0; + this.invMLin1Z = 0; + this.invMLin2X = 0; + this.invMLin2Y = 0; + this.invMLin2Z = 0; + this.invMAng1X = 0; + this.invMAng1Y = 0; + this.invMAng1Z = 0; + this.invMAng2X = 0; + this.invMAng2Y = 0; + this.invMAng2Z = 0; + this.mass = 0; + this.massWithoutCfm = 0; + } +} +if(!oimo.dynamics.constraint.solver.direct) oimo.dynamics.constraint.solver.direct = {}; +oimo.dynamics.constraint.solver.direct.Boundary = class oimo_dynamics_constraint_solver_direct_Boundary { + constructor(maxRows) { + this.iBounded = new Array(maxRows); + this.iUnbounded = new Array(maxRows); + this.signs = new Array(maxRows); + this.b = new Array(maxRows); + this.numBounded = 0; + this.numUnbounded = 0; + this.matrixId = 0; + } + init(buildInfo) { + this.numBounded = buildInfo.numBounded; + let _g = 0; + let _g1 = this.numBounded; + while(_g < _g1) { + let i = _g++; + this.iBounded[i] = buildInfo.iBounded[i]; + this.signs[i] = buildInfo.signs[i]; + } + this.numUnbounded = buildInfo.numUnbounded; + this.matrixId = 0; + let _g2 = 0; + let _g3 = this.numUnbounded; + while(_g2 < _g3) { + let i = _g2++; + let idx = buildInfo.iUnbounded[i]; + this.iUnbounded[i] = idx; + this.matrixId |= 1 << idx; + } + } + computeImpulses(info,mass,relVels,impulses,dImpulses,impulseFactor,noCheck) { + let _g = 0; + let _g1 = this.numUnbounded; + while(_g < _g1) { + let idx = this.iUnbounded[_g++]; + let row = info.rows[idx]; + this.b[idx] = row.rhs * impulseFactor - relVels[idx] - row.cfm * impulses[idx]; + } + let invMassWithoutCfm = mass._invMassWithoutCfm; + let _g2 = 0; + let _g3 = this.numBounded; + while(_g2 < _g3) { + let i = _g2++; + let idx = this.iBounded[i]; + let sign = this.signs[i]; + let row = info.rows[idx]; + let dImpulse = (sign < 0 ? row.minImpulse : sign > 0 ? row.maxImpulse : 0) - impulses[idx]; + dImpulses[idx] = dImpulse; + if(dImpulse != 0) { + let _g = 0; + let _g1 = this.numUnbounded; + while(_g < _g1) { + let idx2 = this.iUnbounded[_g++]; + this.b[idx2] -= invMassWithoutCfm[idx][idx2] * dImpulse; + } + } + } + let indices = this.iUnbounded; + let n = this.numUnbounded; + let id = 0; + let _g4 = 0; + while(_g4 < n) id |= 1 << indices[_g4++]; + let massMatrix; + if(mass._cacheComputed[id]) { + massMatrix = mass._cachedSubmatrices[id]; + } else { + mass.computeSubmatrix(id,indices,n); + mass._cacheComputed[id] = true; + massMatrix = mass._cachedSubmatrices[id]; + } + let ok = true; + let _g5 = 0; + let _g6 = this.numUnbounded; + while(_g5 < _g6) { + let i = _g5++; + let idx = this.iUnbounded[i]; + let row = info.rows[idx]; + let oldImpulse = impulses[idx]; + let impulse = oldImpulse; + let _g = 0; + let _g1 = this.numUnbounded; + while(_g < _g1) { + let j = _g++; + impulse += this.b[this.iUnbounded[j]] * massMatrix[i][j]; + } + if(impulse < row.minImpulse - oimo.common.Setting.directMlcpSolverEps || impulse > row.maxImpulse + oimo.common.Setting.directMlcpSolverEps) { + ok = false; + break; + } + dImpulses[idx] = impulse - oldImpulse; + } + if(noCheck) { + return true; + } + if(!ok) { + return false; + } + let _g7 = 0; + let _g8 = this.numBounded; + while(_g7 < _g8) { + let i = _g7++; + let idx = this.iBounded[i]; + let row = info.rows[idx]; + let sign = this.signs[i]; + let error = 0; + let newImpulse = impulses[idx] + dImpulses[idx]; + let relVel = relVels[idx]; + let _g = 0; + let _g1 = info.numRows; + while(_g < _g1) { + let j = _g++; + relVel += invMassWithoutCfm[idx][j] * dImpulses[j]; + } + error = row.rhs * impulseFactor - relVel - row.cfm * newImpulse; + if(sign < 0 && error > oimo.common.Setting.directMlcpSolverEps || sign > 0 && error < -oimo.common.Setting.directMlcpSolverEps) { + ok = false; + break; + } + } + return ok; + } +} +oimo.dynamics.constraint.solver.direct.BoundaryBuildInfo = class oimo_dynamics_constraint_solver_direct_BoundaryBuildInfo { + constructor(size) { + this.size = size; + this.numBounded = 0; + this.iBounded = new Array(size); + this.signs = new Array(size); + this.numUnbounded = 0; + this.iUnbounded = new Array(size); + } +} +oimo.dynamics.constraint.solver.direct.BoundaryBuilder = class oimo_dynamics_constraint_solver_direct_BoundaryBuilder { + constructor(maxRows) { + this.maxRows = maxRows; + this.numBoundaries = 0; + this.boundaries = new Array(1 << maxRows); + this.bbInfo = new oimo.dynamics.constraint.solver.direct.BoundaryBuildInfo(maxRows); + } + buildBoundariesRecursive(info,i) { + if(i == info.numRows) { + if(this.boundaries[this.numBoundaries] == null) { + this.boundaries[this.numBoundaries] = new oimo.dynamics.constraint.solver.direct.Boundary(this.maxRows); + } + this.boundaries[this.numBoundaries++].init(this.bbInfo); + return; + } + let row = info.rows[i]; + let lowerLimitEnabled = row.minImpulse > -1e65536; + let upperLimitEnabled = row.maxImpulse < 1e65536; + if(row.minImpulse == 0 && row.maxImpulse == 0) { + let _this = this.bbInfo; + _this.iBounded[_this.numBounded] = i; + _this.signs[_this.numBounded] = 0; + _this.numBounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numBounded--; + return; + } + let _this = this.bbInfo; + _this.iUnbounded[_this.numUnbounded] = i; + _this.numUnbounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numUnbounded--; + if(lowerLimitEnabled) { + let _this = this.bbInfo; + _this.iBounded[_this.numBounded] = i; + _this.signs[_this.numBounded] = -1; + _this.numBounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numBounded--; + } + if(upperLimitEnabled) { + let _this = this.bbInfo; + _this.iBounded[_this.numBounded] = i; + _this.signs[_this.numBounded] = 1; + _this.numBounded++; + this.buildBoundariesRecursive(info,i + 1); + this.bbInfo.numBounded--; + } + } + buildBoundaries(info) { + this.numBoundaries = 0; + let _this = this.bbInfo; + _this.numBounded = 0; + _this.numUnbounded = 0; + this.buildBoundariesRecursive(info,0); + } +} +oimo.dynamics.constraint.solver.direct.BoundarySelector = class oimo_dynamics_constraint_solver_direct_BoundarySelector { + constructor(n) { + this.n = n; + this.indices = new Array(n); + this.tmpIndices = new Array(n); + let _g = 0; + while(_g < n) { + let i = _g++; + this.indices[i] = i; + } + } + getIndex(i) { + return this.indices[i]; + } + select(index) { + let i = 0; + while(this.indices[i] != index) ++i; + while(i > 0) { + let tmp = this.indices[i]; + this.indices[i] = this.indices[i - 1]; + this.indices[i - 1] = tmp; + --i; + } + } + setSize(size) { + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = this.n; + while(_g < _g1) { + let idx = this.indices[_g++]; + if(idx < size) { + this.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + this.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = this.indices; + this.indices = this.tmpIndices; + this.tmpIndices = tmp; + } +} +oimo.dynamics.constraint.solver.direct.DirectJointConstraintSolver = class oimo_dynamics_constraint_solver_direct_DirectJointConstraintSolver extends oimo.dynamics.constraint.ConstraintSolver { + constructor(joint) { + super(); + this.joint = joint; + this.info = new oimo.dynamics.constraint.info.joint.JointSolverInfo(); + let maxRows = oimo.common.Setting.maxJacobianRows; + this.massMatrix = new oimo.dynamics.constraint.solver.direct.MassMatrix(maxRows); + this.boundaryBuilder = new oimo.dynamics.constraint.solver.direct.BoundaryBuilder(maxRows); + this.massData = new Array(maxRows); + let _g = 0; + let _g1 = this.massData.length; + while(_g < _g1) this.massData[_g++] = new oimo.dynamics.constraint.solver.common.JointSolverMassDataRow(); + let numMaxBoundaries = this.boundaryBuilder.boundaries.length; + this.velBoundarySelector = new oimo.dynamics.constraint.solver.direct.BoundarySelector(numMaxBoundaries); + this.posBoundarySelector = new oimo.dynamics.constraint.solver.direct.BoundarySelector(numMaxBoundaries); + this.relVels = new Array(maxRows); + this.impulses = new Array(maxRows); + this.dImpulses = new Array(maxRows); + this.dTotalImpulses = new Array(maxRows); + let _g2 = 0; + while(_g2 < maxRows) { + let i = _g2++; + this.relVels[i] = 0; + this.impulses[i] = 0; + this.dImpulses[i] = 0; + this.dTotalImpulses[i] = 0; + } + } + preSolveVelocity(timeStep) { + this.joint._syncAnchors(); + this.joint._getVelocitySolverInfo(timeStep,this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + this.massMatrix.computeInvMass(this.info,this.massData); + let _this = this.boundaryBuilder; + _this.numBoundaries = 0; + let _this1 = _this.bbInfo; + _this1.numBounded = 0; + _this1.numUnbounded = 0; + _this.buildBoundariesRecursive(this.info,0); + let _this2 = this.velBoundarySelector; + let size = this.boundaryBuilder.numBoundaries; + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = _this2.n; + while(_g < _g1) { + let idx = _this2.indices[_g++]; + if(idx < size) { + _this2.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + _this2.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = _this2.indices; + _this2.indices = _this2.tmpIndices; + _this2.tmpIndices = tmp; + } + warmStart(timeStep) { + let factor = this.joint._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE ? oimo.common.Setting.jointWarmStartingFactorForBaungarte : oimo.common.Setting.jointWarmStartingFactor; + factor *= timeStep.dtRatio; + if(factor <= 0) { + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let _this = this.info.rows[_g++].impulse; + _this.impulse = 0; + _this.impulseM = 0; + _this.impulseP = 0; + } + return; + } + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let imp = row.impulse; + let impulse = imp.impulse * factor; + if(impulse < row.minImpulse) { + impulse = row.minImpulse; + } else if(impulse > row.maxImpulse) { + impulse = row.maxImpulse; + } + imp.impulse = impulse; + if(row.motorMaxImpulse > 0) { + let impulseM = imp.impulseM * factor; + let max = row.motorMaxImpulse; + if(impulseM < -max) { + impulseM = -max; + } else if(impulseM > max) { + impulseM = max; + } + imp.impulseM = impulseM; + } else { + imp.impulseM = 0; + } + this.dImpulses[i] = imp.impulse + imp.impulseM; + } + let impulses = this.dImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + } + if(angularSet) { + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + } + solveVelocity() { + let numRows = this.info.numRows; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + while(_g < numRows) { + let i = _g++; + let row = this.info.rows[i]; + let j = row.jacobian; + let relVel = 0; + relVel += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + relVel -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + relVel += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + relVel -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + this.relVels[i] = relVel; + this.impulses[i] = row.impulse.impulse; + this.dTotalImpulses[i] = 0; + } + let invMass = this.massMatrix._invMassWithoutCfm; + let _g1 = 0; + while(_g1 < numRows) { + let i = _g1++; + let row = this.info.rows[i]; + let imp = row.impulse; + if(row.motorMaxImpulse > 0) { + let oldImpulseM = imp.impulseM; + let impulseM = oldImpulseM + this.massData[i].massWithoutCfm * (-row.motorSpeed - this.relVels[i]); + let maxImpulseM = row.motorMaxImpulse; + if(impulseM < -maxImpulseM) { + impulseM = -maxImpulseM; + } else if(impulseM > maxImpulseM) { + impulseM = maxImpulseM; + } + imp.impulseM = impulseM; + let dImpulseM = impulseM - oldImpulseM; + this.dTotalImpulses[i] = dImpulseM; + let _g = 0; + while(_g < numRows) { + let j = _g++; + this.relVels[j] += dImpulseM * invMass[i][j]; + } + } + } + let solved = false; + let _g2 = 0; + let _g3 = this.boundaryBuilder.numBoundaries; + while(_g2 < _g3) { + let idx = this.velBoundarySelector.indices[_g2++]; + if(this.boundaryBuilder.boundaries[idx].computeImpulses(this.info,this.massMatrix,this.relVels,this.impulses,this.dImpulses,1,false)) { + let _g = 0; + while(_g < numRows) { + let j = _g++; + let dimp = this.dImpulses[j]; + this.info.rows[j].impulse.impulse += dimp; + this.dTotalImpulses[j] += dimp; + } + let impulses = this.dTotalImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g1 = 0; + let _g2 = this.info.numRows; + while(_g1 < _g2) { + let i = _g1++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + } + if(angularSet) { + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + let _this = this.velBoundarySelector; + let i = 0; + while(_this.indices[i] != idx) ++i; + while(i > 0) { + let tmp = _this.indices[i]; + _this.indices[i] = _this.indices[i - 1]; + _this.indices[i - 1] = tmp; + --i; + } + solved = true; + break; + } + } + if(!solved) { + console.log("src/oimo/dynamics/constraint/solver/direct/DirectJointConstraintSolver.hx:335:","could not find solution. (velocity)"); + return; + } + } + postSolveVelocity(timeStep) { + let linX; + let linY; + let linZ; + let angX; + let angY; + let angZ; + linX = 0; + linY = 0; + linZ = 0; + angX = 0; + angY = 0; + angZ = 0; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let row = this.info.rows[_g++]; + let imp = row.impulse; + let j = row.jacobian; + if((j.flag & 1) != 0) { + linX += j.lin1X * imp.impulse; + linY += j.lin1Y * imp.impulse; + linZ += j.lin1Z * imp.impulse; + } else if((j.flag & 2) != 0) { + angX += j.ang1X * imp.impulse; + angY += j.ang1Y * imp.impulse; + angZ += j.ang1Z * imp.impulse; + } + } + this.joint._appliedForceX = linX * timeStep.invDt; + this.joint._appliedForceY = linY * timeStep.invDt; + this.joint._appliedForceZ = linZ * timeStep.invDt; + this.joint._appliedTorqueX = angX * timeStep.invDt; + this.joint._appliedTorqueY = angY * timeStep.invDt; + this.joint._appliedTorqueZ = angZ * timeStep.invDt; + } + preSolvePosition(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + this.massMatrix.computeInvMass(this.info,this.massData); + let _this = this.boundaryBuilder; + _this.numBoundaries = 0; + let _this1 = _this.bbInfo; + _this1.numBounded = 0; + _this1.numUnbounded = 0; + _this.buildBoundariesRecursive(this.info,0); + let _this2 = this.posBoundarySelector; + let size = this.boundaryBuilder.numBoundaries; + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = _this2.n; + while(_g < _g1) { + let idx = _this2.indices[_g++]; + if(idx < size) { + _this2.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + _this2.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = _this2.indices; + _this2.indices = _this2.tmpIndices; + _this2.tmpIndices = tmp; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) this.info.rows[_g2++].impulse.impulseP = 0; + } + solvePositionSplitImpulse() { + let numRows = this.info.numRows; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g = 0; + while(_g < numRows) { + let i = _g++; + let row = this.info.rows[i]; + let j = row.jacobian; + let relVel = 0; + relVel += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + relVel -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + relVel += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + relVel -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + this.relVels[i] = relVel; + this.impulses[i] = row.impulse.impulseP; + } + let solved = false; + let _g1 = 0; + let _g2 = this.boundaryBuilder.numBoundaries; + while(_g1 < _g2) { + let idx = this.posBoundarySelector.indices[_g1++]; + if(this.boundaryBuilder.boundaries[idx].computeImpulses(this.info,this.massMatrix,this.relVels,this.impulses,this.dImpulses,oimo.common.Setting.positionSplitImpulseBaumgarte,false)) { + let _g = 0; + while(_g < numRows) { + let j = _g++; + this.info.rows[j].impulse.impulseP += this.dImpulses[j]; + } + let impulses = this.dImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g1 = 0; + let _g2 = this.info.numRows; + while(_g1 < _g2) { + let i = _g1++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + this._b1._pseudoVelX = lv1X; + this._b1._pseudoVelY = lv1Y; + this._b1._pseudoVelZ = lv1Z; + this._b2._pseudoVelX = lv2X; + this._b2._pseudoVelY = lv2Y; + this._b2._pseudoVelZ = lv2Z; + } + if(angularSet) { + this._b1._angPseudoVelX = av1X; + this._b1._angPseudoVelY = av1Y; + this._b1._angPseudoVelZ = av1Z; + this._b2._angPseudoVelX = av2X; + this._b2._angPseudoVelY = av2Y; + this._b2._angPseudoVelZ = av2Z; + } + let _this = this.posBoundarySelector; + let i = 0; + while(_this.indices[i] != idx) ++i; + while(i > 0) { + let tmp = _this.indices[i]; + _this.indices[i] = _this.indices[i - 1]; + _this.indices[i - 1] = tmp; + --i; + } + solved = true; + break; + } + } + if(!solved) { + console.log("src/oimo/dynamics/constraint/solver/direct/DirectJointConstraintSolver.hx:450:","could not find solution. (split impulse)"); + return; + } + } + solvePositionNgs(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + this.massMatrix.computeInvMass(this.info,this.massData); + let _this = this.boundaryBuilder; + _this.numBoundaries = 0; + let _this1 = _this.bbInfo; + _this1.numBounded = 0; + _this1.numUnbounded = 0; + _this.buildBoundariesRecursive(this.info,0); + let _this2 = this.posBoundarySelector; + let size = this.boundaryBuilder.numBoundaries; + let numSmaller = 0; + let numGreater = 0; + let _g = 0; + let _g1 = _this2.n; + while(_g < _g1) { + let idx = _this2.indices[_g++]; + if(idx < size) { + _this2.tmpIndices[numSmaller] = idx; + ++numSmaller; + } else { + _this2.tmpIndices[size + numGreater] = idx; + ++numGreater; + } + } + let tmp = _this2.indices; + _this2.indices = _this2.tmpIndices; + _this2.tmpIndices = tmp; + let numRows = this.info.numRows; + let _g2 = 0; + while(_g2 < numRows) { + let i = _g2++; + let imp = this.info.rows[i].impulse; + this.relVels[i] = 0; + this.impulses[i] = imp.impulseP; + } + let solved = false; + let _g3 = 0; + let _g4 = this.boundaryBuilder.numBoundaries; + while(_g3 < _g4) { + let idx = this.posBoundarySelector.indices[_g3++]; + if(this.boundaryBuilder.boundaries[idx].computeImpulses(this.info,this.massMatrix,this.relVels,this.impulses,this.dImpulses,oimo.common.Setting.positionNgsBaumgarte,false)) { + let _g = 0; + while(_g < numRows) { + let j = _g++; + this.info.rows[j].impulse.impulseP += this.dImpulses[j]; + } + let impulses = this.dImpulses; + let linearSet = false; + let angularSet = false; + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = 0; + lv1Y = 0; + lv1Z = 0; + lv2X = 0; + lv2Y = 0; + lv2Z = 0; + av1X = 0; + av1Y = 0; + av1Z = 0; + av2X = 0; + av2Y = 0; + av2Z = 0; + let _g1 = 0; + let _g2 = this.info.numRows; + while(_g1 < _g2) { + let i = _g1++; + let j = this.info.rows[i].jacobian; + let md = this.massData[i]; + let imp = impulses[i]; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * imp; + lv1Y += md.invMLin1Y * imp; + lv1Z += md.invMLin1Z * imp; + lv2X += md.invMLin2X * -imp; + lv2Y += md.invMLin2Y * -imp; + lv2Z += md.invMLin2Z * -imp; + linearSet = true; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * imp; + av1Y += md.invMAng1Y * imp; + av1Z += md.invMAng1Z * imp; + av2X += md.invMAng2X * -imp; + av2Y += md.invMAng2Y * -imp; + av2Z += md.invMAng2Z * -imp; + angularSet = true; + } + } + if(linearSet) { + let _this = this._b1; + _this._transform._positionX += lv1X; + _this._transform._positionY += lv1Y; + _this._transform._positionZ += lv1Z; + let _this1 = this._b2; + _this1._transform._positionX += lv2X; + _this1._transform._positionY += lv2Y; + _this1._transform._positionZ += lv2Z; + } + if(angularSet) { + let _this = this._b1; + let theta = Math.sqrt(av1X * av1X + av1Y * av1Y + av1Z * av1Z); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = av1X * rotationToSinAxisFactor; + sinAxisY = av1Y * rotationToSinAxisFactor; + sinAxisZ = av1Z * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = _this._transform._rotation00; + let e11 = _this._transform._rotation11; + let e22 = _this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation21 - _this._transform._rotation12) * s; + qY = (_this._transform._rotation02 - _this._transform._rotation20) * s; + qZ = (_this._transform._rotation10 - _this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (_this._transform._rotation01 + _this._transform._rotation10) * s; + qZ = (_this._transform._rotation02 + _this._transform._rotation20) * s; + qW = (_this._transform._rotation21 - _this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation02 + _this._transform._rotation20) * s; + qY = (_this._transform._rotation12 + _this._transform._rotation21) * s; + qW = (_this._transform._rotation10 - _this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation01 + _this._transform._rotation10) * s; + qZ = (_this._transform._rotation12 + _this._transform._rotation21) * s; + qW = (_this._transform._rotation02 - _this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this._transform._rotation02 + _this._transform._rotation20) * s; + qY = (_this._transform._rotation12 + _this._transform._rotation21) * s; + qW = (_this._transform._rotation10 - _this._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this._transform._rotation00 = 1 - yy - zz; + _this._transform._rotation01 = xy - wz; + _this._transform._rotation02 = xz + wy; + _this._transform._rotation10 = xy + wz; + _this._transform._rotation11 = 1 - xx - zz; + _this._transform._rotation12 = yz - wx; + _this._transform._rotation20 = xz - wy; + _this._transform._rotation21 = yz + wx; + _this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = _this._transform._rotation00 * _this._invLocalInertia00 + _this._transform._rotation01 * _this._invLocalInertia10 + _this._transform._rotation02 * _this._invLocalInertia20; + __tmp__01 = _this._transform._rotation00 * _this._invLocalInertia01 + _this._transform._rotation01 * _this._invLocalInertia11 + _this._transform._rotation02 * _this._invLocalInertia21; + __tmp__02 = _this._transform._rotation00 * _this._invLocalInertia02 + _this._transform._rotation01 * _this._invLocalInertia12 + _this._transform._rotation02 * _this._invLocalInertia22; + __tmp__10 = _this._transform._rotation10 * _this._invLocalInertia00 + _this._transform._rotation11 * _this._invLocalInertia10 + _this._transform._rotation12 * _this._invLocalInertia20; + __tmp__11 = _this._transform._rotation10 * _this._invLocalInertia01 + _this._transform._rotation11 * _this._invLocalInertia11 + _this._transform._rotation12 * _this._invLocalInertia21; + __tmp__12 = _this._transform._rotation10 * _this._invLocalInertia02 + _this._transform._rotation11 * _this._invLocalInertia12 + _this._transform._rotation12 * _this._invLocalInertia22; + __tmp__20 = _this._transform._rotation20 * _this._invLocalInertia00 + _this._transform._rotation21 * _this._invLocalInertia10 + _this._transform._rotation22 * _this._invLocalInertia20; + __tmp__21 = _this._transform._rotation20 * _this._invLocalInertia01 + _this._transform._rotation21 * _this._invLocalInertia11 + _this._transform._rotation22 * _this._invLocalInertia21; + __tmp__22 = _this._transform._rotation20 * _this._invLocalInertia02 + _this._transform._rotation21 * _this._invLocalInertia12 + _this._transform._rotation22 * _this._invLocalInertia22; + _this._invInertia00 = __tmp__00; + _this._invInertia01 = __tmp__01; + _this._invInertia02 = __tmp__02; + _this._invInertia10 = __tmp__10; + _this._invInertia11 = __tmp__11; + _this._invInertia12 = __tmp__12; + _this._invInertia20 = __tmp__20; + _this._invInertia21 = __tmp__21; + _this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = _this._invInertia00 * _this._transform._rotation00 + _this._invInertia01 * _this._transform._rotation01 + _this._invInertia02 * _this._transform._rotation02; + __tmp__011 = _this._invInertia00 * _this._transform._rotation10 + _this._invInertia01 * _this._transform._rotation11 + _this._invInertia02 * _this._transform._rotation12; + __tmp__021 = _this._invInertia00 * _this._transform._rotation20 + _this._invInertia01 * _this._transform._rotation21 + _this._invInertia02 * _this._transform._rotation22; + __tmp__101 = _this._invInertia10 * _this._transform._rotation00 + _this._invInertia11 * _this._transform._rotation01 + _this._invInertia12 * _this._transform._rotation02; + __tmp__111 = _this._invInertia10 * _this._transform._rotation10 + _this._invInertia11 * _this._transform._rotation11 + _this._invInertia12 * _this._transform._rotation12; + __tmp__121 = _this._invInertia10 * _this._transform._rotation20 + _this._invInertia11 * _this._transform._rotation21 + _this._invInertia12 * _this._transform._rotation22; + __tmp__201 = _this._invInertia20 * _this._transform._rotation00 + _this._invInertia21 * _this._transform._rotation01 + _this._invInertia22 * _this._transform._rotation02; + __tmp__211 = _this._invInertia20 * _this._transform._rotation10 + _this._invInertia21 * _this._transform._rotation11 + _this._invInertia22 * _this._transform._rotation12; + __tmp__221 = _this._invInertia20 * _this._transform._rotation20 + _this._invInertia21 * _this._transform._rotation21 + _this._invInertia22 * _this._transform._rotation22; + _this._invInertia00 = __tmp__001; + _this._invInertia01 = __tmp__011; + _this._invInertia02 = __tmp__021; + _this._invInertia10 = __tmp__101; + _this._invInertia11 = __tmp__111; + _this._invInertia12 = __tmp__121; + _this._invInertia20 = __tmp__201; + _this._invInertia21 = __tmp__211; + _this._invInertia22 = __tmp__221; + _this._invInertia00 *= _this._rotFactor.x; + _this._invInertia01 *= _this._rotFactor.x; + _this._invInertia02 *= _this._rotFactor.x; + _this._invInertia10 *= _this._rotFactor.y; + _this._invInertia11 *= _this._rotFactor.y; + _this._invInertia12 *= _this._rotFactor.y; + _this._invInertia20 *= _this._rotFactor.z; + _this._invInertia21 *= _this._rotFactor.z; + _this._invInertia22 *= _this._rotFactor.z; + let _this1 = this._b2; + let theta1 = Math.sqrt(av2X * av2X + av2Y * av2Y + av2Z * av2Z); + let halfTheta1 = theta1 * 0.5; + let rotationToSinAxisFactor1; + let cosHalfTheta1; + if(halfTheta1 < 0.5) { + let ht2 = halfTheta1 * halfTheta1; + rotationToSinAxisFactor1 = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta1 = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor1 = Math.sin(halfTheta1) / theta1; + cosHalfTheta1 = Math.cos(halfTheta1); + } + let sinAxisX1; + let sinAxisY1; + let sinAxisZ1; + sinAxisX1 = av2X * rotationToSinAxisFactor1; + sinAxisY1 = av2Y * rotationToSinAxisFactor1; + sinAxisZ1 = av2Z * rotationToSinAxisFactor1; + let dqX1; + let dqY1; + let dqZ1; + let dqW1; + dqX1 = sinAxisX1; + dqY1 = sinAxisY1; + dqZ1 = sinAxisZ1; + dqW1 = cosHalfTheta1; + let qX1; + let qY1; + let qZ1; + let qW1; + let e001 = _this1._transform._rotation00; + let e111 = _this1._transform._rotation11; + let e221 = _this1._transform._rotation22; + let t1 = e001 + e111 + e221; + let s1; + if(t1 > 0) { + s1 = Math.sqrt(t1 + 1); + qW1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation21 - _this1._transform._rotation12) * s1; + qY1 = (_this1._transform._rotation02 - _this1._transform._rotation20) * s1; + qZ1 = (_this1._transform._rotation10 - _this1._transform._rotation01) * s1; + } else if(e001 > e111) { + if(e001 > e221) { + s1 = Math.sqrt(e001 - e111 - e221 + 1); + qX1 = 0.5 * s1; + s1 = 0.5 / s1; + qY1 = (_this1._transform._rotation01 + _this1._transform._rotation10) * s1; + qZ1 = (_this1._transform._rotation02 + _this1._transform._rotation20) * s1; + qW1 = (_this1._transform._rotation21 - _this1._transform._rotation12) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation02 + _this1._transform._rotation20) * s1; + qY1 = (_this1._transform._rotation12 + _this1._transform._rotation21) * s1; + qW1 = (_this1._transform._rotation10 - _this1._transform._rotation01) * s1; + } + } else if(e111 > e221) { + s1 = Math.sqrt(e111 - e221 - e001 + 1); + qY1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation01 + _this1._transform._rotation10) * s1; + qZ1 = (_this1._transform._rotation12 + _this1._transform._rotation21) * s1; + qW1 = (_this1._transform._rotation02 - _this1._transform._rotation20) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this1._transform._rotation02 + _this1._transform._rotation20) * s1; + qY1 = (_this1._transform._rotation12 + _this1._transform._rotation21) * s1; + qW1 = (_this1._transform._rotation10 - _this1._transform._rotation01) * s1; + } + qX1 = dqW1 * qX1 + dqX1 * qW1 + dqY1 * qZ1 - dqZ1 * qY1; + qY1 = dqW1 * qY1 - dqX1 * qZ1 + dqY1 * qW1 + dqZ1 * qX1; + qZ1 = dqW1 * qZ1 + dqX1 * qY1 - dqY1 * qX1 + dqZ1 * qW1; + qW1 = dqW1 * qW1 - dqX1 * qX1 - dqY1 * qY1 - dqZ1 * qZ1; + let l1 = qX1 * qX1 + qY1 * qY1 + qZ1 * qZ1 + qW1 * qW1; + if(l1 > 1e-32) { + l1 = 1 / Math.sqrt(l1); + } + qX1 *= l1; + qY1 *= l1; + qZ1 *= l1; + qW1 *= l1; + let x1 = qX1; + let y1 = qY1; + let z1 = qZ1; + let w1 = qW1; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + _this1._transform._rotation00 = 1 - yy1 - zz1; + _this1._transform._rotation01 = xy1 - wz1; + _this1._transform._rotation02 = xz1 + wy1; + _this1._transform._rotation10 = xy1 + wz1; + _this1._transform._rotation11 = 1 - xx1 - zz1; + _this1._transform._rotation12 = yz1 - wx1; + _this1._transform._rotation20 = xz1 - wy1; + _this1._transform._rotation21 = yz1 + wx1; + _this1._transform._rotation22 = 1 - xx1 - yy1; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = _this1._transform._rotation00 * _this1._invLocalInertia00 + _this1._transform._rotation01 * _this1._invLocalInertia10 + _this1._transform._rotation02 * _this1._invLocalInertia20; + __tmp__012 = _this1._transform._rotation00 * _this1._invLocalInertia01 + _this1._transform._rotation01 * _this1._invLocalInertia11 + _this1._transform._rotation02 * _this1._invLocalInertia21; + __tmp__022 = _this1._transform._rotation00 * _this1._invLocalInertia02 + _this1._transform._rotation01 * _this1._invLocalInertia12 + _this1._transform._rotation02 * _this1._invLocalInertia22; + __tmp__102 = _this1._transform._rotation10 * _this1._invLocalInertia00 + _this1._transform._rotation11 * _this1._invLocalInertia10 + _this1._transform._rotation12 * _this1._invLocalInertia20; + __tmp__112 = _this1._transform._rotation10 * _this1._invLocalInertia01 + _this1._transform._rotation11 * _this1._invLocalInertia11 + _this1._transform._rotation12 * _this1._invLocalInertia21; + __tmp__122 = _this1._transform._rotation10 * _this1._invLocalInertia02 + _this1._transform._rotation11 * _this1._invLocalInertia12 + _this1._transform._rotation12 * _this1._invLocalInertia22; + __tmp__202 = _this1._transform._rotation20 * _this1._invLocalInertia00 + _this1._transform._rotation21 * _this1._invLocalInertia10 + _this1._transform._rotation22 * _this1._invLocalInertia20; + __tmp__212 = _this1._transform._rotation20 * _this1._invLocalInertia01 + _this1._transform._rotation21 * _this1._invLocalInertia11 + _this1._transform._rotation22 * _this1._invLocalInertia21; + __tmp__222 = _this1._transform._rotation20 * _this1._invLocalInertia02 + _this1._transform._rotation21 * _this1._invLocalInertia12 + _this1._transform._rotation22 * _this1._invLocalInertia22; + _this1._invInertia00 = __tmp__002; + _this1._invInertia01 = __tmp__012; + _this1._invInertia02 = __tmp__022; + _this1._invInertia10 = __tmp__102; + _this1._invInertia11 = __tmp__112; + _this1._invInertia12 = __tmp__122; + _this1._invInertia20 = __tmp__202; + _this1._invInertia21 = __tmp__212; + _this1._invInertia22 = __tmp__222; + let __tmp__003; + let __tmp__013; + let __tmp__023; + let __tmp__103; + let __tmp__113; + let __tmp__123; + let __tmp__203; + let __tmp__213; + let __tmp__223; + __tmp__003 = _this1._invInertia00 * _this1._transform._rotation00 + _this1._invInertia01 * _this1._transform._rotation01 + _this1._invInertia02 * _this1._transform._rotation02; + __tmp__013 = _this1._invInertia00 * _this1._transform._rotation10 + _this1._invInertia01 * _this1._transform._rotation11 + _this1._invInertia02 * _this1._transform._rotation12; + __tmp__023 = _this1._invInertia00 * _this1._transform._rotation20 + _this1._invInertia01 * _this1._transform._rotation21 + _this1._invInertia02 * _this1._transform._rotation22; + __tmp__103 = _this1._invInertia10 * _this1._transform._rotation00 + _this1._invInertia11 * _this1._transform._rotation01 + _this1._invInertia12 * _this1._transform._rotation02; + __tmp__113 = _this1._invInertia10 * _this1._transform._rotation10 + _this1._invInertia11 * _this1._transform._rotation11 + _this1._invInertia12 * _this1._transform._rotation12; + __tmp__123 = _this1._invInertia10 * _this1._transform._rotation20 + _this1._invInertia11 * _this1._transform._rotation21 + _this1._invInertia12 * _this1._transform._rotation22; + __tmp__203 = _this1._invInertia20 * _this1._transform._rotation00 + _this1._invInertia21 * _this1._transform._rotation01 + _this1._invInertia22 * _this1._transform._rotation02; + __tmp__213 = _this1._invInertia20 * _this1._transform._rotation10 + _this1._invInertia21 * _this1._transform._rotation11 + _this1._invInertia22 * _this1._transform._rotation12; + __tmp__223 = _this1._invInertia20 * _this1._transform._rotation20 + _this1._invInertia21 * _this1._transform._rotation21 + _this1._invInertia22 * _this1._transform._rotation22; + _this1._invInertia00 = __tmp__003; + _this1._invInertia01 = __tmp__013; + _this1._invInertia02 = __tmp__023; + _this1._invInertia10 = __tmp__103; + _this1._invInertia11 = __tmp__113; + _this1._invInertia12 = __tmp__123; + _this1._invInertia20 = __tmp__203; + _this1._invInertia21 = __tmp__213; + _this1._invInertia22 = __tmp__223; + _this1._invInertia00 *= _this1._rotFactor.x; + _this1._invInertia01 *= _this1._rotFactor.x; + _this1._invInertia02 *= _this1._rotFactor.x; + _this1._invInertia10 *= _this1._rotFactor.y; + _this1._invInertia11 *= _this1._rotFactor.y; + _this1._invInertia12 *= _this1._rotFactor.y; + _this1._invInertia20 *= _this1._rotFactor.z; + _this1._invInertia21 *= _this1._rotFactor.z; + _this1._invInertia22 *= _this1._rotFactor.z; + } + let _this = this.posBoundarySelector; + let i = 0; + while(_this.indices[i] != idx) ++i; + while(i > 0) { + let tmp = _this.indices[i]; + _this.indices[i] = _this.indices[i - 1]; + _this.indices[i - 1] = tmp; + --i; + } + solved = true; + break; + } + } + if(!solved) { + console.log("src/oimo/dynamics/constraint/solver/direct/DirectJointConstraintSolver.hx:502:","could not find solution. (NGS)"); + return; + } + } + postSolve() { + this.joint._syncAnchors(); + this.joint._checkDestruction(); + } +} +oimo.dynamics.constraint.solver.direct.MassMatrix = class oimo_dynamics_constraint_solver_direct_MassMatrix { + constructor(size) { + this._size = size; + this.tmpMatrix = new Array(this._size); + this._invMass = new Array(this._size); + this._invMassWithoutCfm = new Array(this._size); + let _g = 0; + let _g1 = this._size; + while(_g < _g1) { + let i = _g++; + this.tmpMatrix[i] = new Array(this._size); + this._invMass[i] = new Array(this._size); + this._invMassWithoutCfm[i] = new Array(this._size); + let _g1 = 0; + let _g2 = this._size; + while(_g1 < _g2) { + let j = _g1++; + this.tmpMatrix[i][j] = 0; + this._invMass[i][j] = 0; + this._invMassWithoutCfm[i][j] = 0; + } + } + this._maxSubmatrixId = 1 << this._size; + this._cacheComputed = new Array(this._maxSubmatrixId); + this._cachedSubmatrices = new Array(this._maxSubmatrixId); + let _g2 = 0; + let _g3 = this._maxSubmatrixId; + while(_g2 < _g3) { + let i = _g2++; + let t; + t = (i & 85) + (i >> 1 & 85); + t = (t & 51) + (t >> 2 & 51); + t = (t & 15) + (t >> 4 & 15); + let matrixSize = t; + let subMatrix = new Array(matrixSize); + let _g = 0; + while(_g < matrixSize) { + let j = _g++; + subMatrix[j] = new Array(matrixSize); + let _g1 = 0; + while(_g1 < matrixSize) subMatrix[j][_g1++] = 0; + } + this._cacheComputed[i] = false; + this._cachedSubmatrices[i] = subMatrix; + } + } + computeSubmatrix(id,indices,size) { + let _g = 0; + while(_g < size) { + let i = _g++; + let ii = indices[i]; + let _g1 = 0; + while(_g1 < size) { + let j = _g1++; + this.tmpMatrix[i][j] = this._invMass[ii][indices[j]]; + } + } + let src = this.tmpMatrix; + let dst = this._cachedSubmatrices[id]; + let srci; + let dsti; + let srcj; + let dstj; + let diag; + switch(size) { + case 4: + srci = src[0]; + dsti = dst[0]; + diag = 1 / srci[0]; + dsti[0] = diag; + srci[1] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srcj = src[1]; + dstj = dst[1]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srci = src[1]; + dsti = dst[1]; + diag = 1 / srci[1]; + dsti[1] = diag; + dsti[0] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srci = src[2]; + dsti = dst[2]; + diag = 1 / srci[2]; + dsti[2] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + srci[3] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srci = src[3]; + dsti = dst[3]; + diag = 1 / srci[3]; + dsti[3] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[3]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dsti = dst[1]; + dst[0][1] = dsti[0]; + dsti = dst[2]; + dst[0][2] = dsti[0]; + dst[1][2] = dsti[1]; + dsti = dst[3]; + dst[0][3] = dsti[0]; + dst[1][3] = dsti[1]; + dst[2][3] = dsti[2]; + break; + case 5: + srci = src[0]; + dsti = dst[0]; + diag = 1 / srci[0]; + dsti[0] = diag; + srci[1] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srcj = src[1]; + dstj = dst[1]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srci = src[1]; + dsti = dst[1]; + diag = 1 / srci[1]; + dsti[1] = diag; + dsti[0] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srci = src[2]; + dsti = dst[2]; + diag = 1 / srci[2]; + dsti[2] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srci = src[3]; + dsti = dst[3]; + diag = 1 / srci[3]; + dsti[3] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + srci[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dstj[3] = -diag * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srci = src[4]; + dsti = dst[4]; + diag = 1 / srci[4]; + dsti[4] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + dsti[3] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[4]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + dstj[3] -= dsti[3] * srcj[4]; + dsti = dst[1]; + dst[0][1] = dsti[0]; + dsti = dst[2]; + dst[0][2] = dsti[0]; + dst[1][2] = dsti[1]; + dsti = dst[3]; + dst[0][3] = dsti[0]; + dst[1][3] = dsti[1]; + dst[2][3] = dsti[2]; + dsti = dst[4]; + dst[0][4] = dsti[0]; + dst[1][4] = dsti[1]; + dst[2][4] = dsti[2]; + dst[3][4] = dsti[3]; + break; + case 6: + srci = src[0]; + dsti = dst[0]; + diag = 1 / srci[0]; + dsti[0] = diag; + srci[1] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[1]; + dstj = dst[1]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] = -diag * srcj[0]; + srcj[1] -= srci[1] * srcj[0]; + srcj[2] -= srci[2] * srcj[0]; + srcj[3] -= srci[3] * srcj[0]; + srcj[4] -= srci[4] * srcj[0]; + srcj[5] -= srci[5] * srcj[0]; + srci = src[1]; + dsti = dst[1]; + diag = 1 / srci[1]; + dsti[1] = diag; + dsti[0] *= diag; + srci[2] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[1]; + dstj[1] = -diag * srcj[1]; + srcj[2] -= srci[2] * srcj[1]; + srcj[3] -= srci[3] * srcj[1]; + srcj[4] -= srci[4] * srcj[1]; + srcj[5] -= srci[5] * srcj[1]; + srci = src[2]; + dsti = dst[2]; + diag = 1 / srci[2]; + dsti[2] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + srci[3] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[2]; + dstj[1] -= dsti[1] * srcj[2]; + dstj[2] = -diag * srcj[2]; + srcj[3] -= srci[3] * srcj[2]; + srcj[4] -= srci[4] * srcj[2]; + srcj[5] -= srci[5] * srcj[2]; + srci = src[3]; + dsti = dst[3]; + diag = 1 / srci[3]; + dsti[3] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + srci[4] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dstj[3] = -diag * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[3]; + dstj[1] -= dsti[1] * srcj[3]; + dstj[2] -= dsti[2] * srcj[3]; + dstj[3] = -diag * srcj[3]; + srcj[4] -= srci[4] * srcj[3]; + srcj[5] -= srci[5] * srcj[3]; + srci = src[4]; + dsti = dst[4]; + diag = 1 / srci[4]; + dsti[4] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + dsti[3] *= diag; + srci[5] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + dstj[3] -= dsti[3] * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srcj = src[5]; + dstj = dst[5]; + dstj[0] -= dsti[0] * srcj[4]; + dstj[1] -= dsti[1] * srcj[4]; + dstj[2] -= dsti[2] * srcj[4]; + dstj[3] -= dsti[3] * srcj[4]; + dstj[4] = -diag * srcj[4]; + srcj[5] -= srci[5] * srcj[4]; + srci = src[5]; + dsti = dst[5]; + diag = 1 / srci[5]; + dsti[5] = diag; + dsti[0] *= diag; + dsti[1] *= diag; + dsti[2] *= diag; + dsti[3] *= diag; + dsti[4] *= diag; + srcj = src[0]; + dstj = dst[0]; + dstj[0] -= dsti[0] * srcj[5]; + srcj = src[1]; + dstj = dst[1]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + srcj = src[2]; + dstj = dst[2]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + dstj[2] -= dsti[2] * srcj[5]; + srcj = src[3]; + dstj = dst[3]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + dstj[2] -= dsti[2] * srcj[5]; + dstj[3] -= dsti[3] * srcj[5]; + srcj = src[4]; + dstj = dst[4]; + dstj[0] -= dsti[0] * srcj[5]; + dstj[1] -= dsti[1] * srcj[5]; + dstj[2] -= dsti[2] * srcj[5]; + dstj[3] -= dsti[3] * srcj[5]; + dstj[4] -= dsti[4] * srcj[5]; + dsti = dst[1]; + dst[0][1] = dsti[0]; + dsti = dst[2]; + dst[0][2] = dsti[0]; + dst[1][2] = dsti[1]; + dsti = dst[3]; + dst[0][3] = dsti[0]; + dst[1][3] = dsti[1]; + dst[2][3] = dsti[2]; + dsti = dst[4]; + dst[0][4] = dsti[0]; + dst[1][4] = dsti[1]; + dst[2][4] = dsti[2]; + dst[3][4] = dsti[3]; + dsti = dst[5]; + dst[0][5] = dsti[0]; + dst[1][5] = dsti[1]; + dst[2][5] = dsti[2]; + dst[3][5] = dsti[3]; + dst[4][5] = dsti[4]; + break; + default: + let _g1 = 0; + while(_g1 < size) { + let i = _g1++; + srci = src[i]; + dsti = dst[i]; + let diag = 1 / srci[i]; + dsti[i] = diag; + let _g = 0; + while(_g < i) dsti[_g++] *= diag; + let _g2 = i + 1; + while(_g2 < size) srci[_g2++] *= diag; + let _g3 = 0; + while(_g3 < i) { + let j = _g3++; + srcj = src[j]; + dstj = dst[j]; + let _g = 0; + let _g1 = j + 1; + while(_g < _g1) { + let k = _g++; + dstj[k] -= dsti[k] * srcj[i]; + } + let _g2 = i + 1; + while(_g2 < size) { + let k = _g2++; + srcj[k] -= srci[k] * srcj[i]; + } + } + let _g4 = i + 1; + while(_g4 < size) { + let j = _g4++; + srcj = src[j]; + dstj = dst[j]; + let _g = 0; + while(_g < i) { + let k = _g++; + dstj[k] -= dsti[k] * srcj[i]; + } + dstj[i] = -diag * srcj[i]; + let _g1 = i + 1; + while(_g1 < size) { + let k = _g1++; + srcj[k] -= srci[k] * srcj[i]; + } + } + } + let _g2 = 1; + while(_g2 < size) { + let i = _g2++; + dsti = dst[i]; + let _g = 0; + while(_g < i) { + let j = _g++; + dst[j][i] = dsti[j]; + } + } + } + } + computeInvMass(info,massData) { + let invMass = this._invMass; + let invMassWithoutCfm = this._invMassWithoutCfm; + let numRows = info.numRows; + let b1 = info.b1; + let b2 = info.b2; + let invM1 = b1._invMass; + let invM2 = b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = b1._invInertia00; + invI101 = b1._invInertia01; + invI102 = b1._invInertia02; + invI110 = b1._invInertia10; + invI111 = b1._invInertia11; + invI112 = b1._invInertia12; + invI120 = b1._invInertia20; + invI121 = b1._invInertia21; + invI122 = b1._invInertia22; + invI200 = b2._invInertia00; + invI201 = b2._invInertia01; + invI202 = b2._invInertia02; + invI210 = b2._invInertia10; + invI211 = b2._invInertia11; + invI212 = b2._invInertia12; + invI220 = b2._invInertia20; + invI221 = b2._invInertia21; + invI222 = b2._invInertia22; + let _g = 0; + while(_g < numRows) { + let i = _g++; + let j = info.rows[i].jacobian; + let md = massData[i]; + j.updateSparsity(); + if((j.flag & 1) != 0) { + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + } else { + md.invMLin1X = 0; + md.invMLin1Y = 0; + md.invMLin1Z = 0; + md.invMLin2X = 0; + md.invMLin2Y = 0; + md.invMLin2Z = 0; + } + if((j.flag & 2) != 0) { + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + } else { + md.invMAng1X = 0; + md.invMAng1Y = 0; + md.invMAng1Z = 0; + md.invMAng2X = 0; + md.invMAng2Y = 0; + md.invMAng2Z = 0; + } + } + let _g1 = 0; + while(_g1 < numRows) { + let i = _g1++; + let j1 = info.rows[i].jacobian; + let _g = i; + while(_g < numRows) { + let j = _g++; + let md2 = massData[j]; + let val = j1.lin1X * md2.invMLin1X + j1.lin1Y * md2.invMLin1Y + j1.lin1Z * md2.invMLin1Z + (j1.ang1X * md2.invMAng1X + j1.ang1Y * md2.invMAng1Y + j1.ang1Z * md2.invMAng1Z) + (j1.lin2X * md2.invMLin2X + j1.lin2Y * md2.invMLin2Y + j1.lin2Z * md2.invMLin2Z) + (j1.ang2X * md2.invMAng2X + j1.ang2Y * md2.invMAng2Y + j1.ang2Z * md2.invMAng2Z); + if(i == j) { + invMass[i][j] = val + info.rows[i].cfm; + invMassWithoutCfm[i][j] = val; + md2.mass = val + info.rows[i].cfm; + md2.massWithoutCfm = val; + if(md2.mass != 0) { + md2.mass = 1 / md2.mass; + } + if(md2.massWithoutCfm != 0) { + md2.massWithoutCfm = 1 / md2.massWithoutCfm; + } + } else { + invMass[i][j] = val; + invMass[j][i] = val; + invMassWithoutCfm[i][j] = val; + invMassWithoutCfm[j][i] = val; + } + } + } + let _g2 = 0; + let _g3 = this._maxSubmatrixId; + while(_g2 < _g3) this._cacheComputed[_g2++] = false; + } +} +if(!oimo.dynamics.constraint.solver.pgs) oimo.dynamics.constraint.solver.pgs = {}; +oimo.dynamics.constraint.solver.pgs.PgsContactConstraintSolver = class oimo_dynamics_constraint_solver_pgs_PgsContactConstraintSolver extends oimo.dynamics.constraint.ConstraintSolver { + constructor(constraint) { + super(); + this.constraint = constraint; + this.info = new oimo.dynamics.constraint.info.contact.ContactSolverInfo(); + this.massData = new Array(oimo.common.Setting.maxManifoldPoints); + let _g = 0; + let _g1 = this.massData.length; + while(_g < _g1) this.massData[_g++] = new oimo.dynamics.constraint.solver.common.ContactSolverMassDataRow(); + } + preSolveVelocity(timeStep) { + this.constraint._getVelocitySolverInfo(timeStep,this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let j = row.jacobianN; + md.invMLinN1X = j.lin1X * invM1; + md.invMLinN1Y = j.lin1Y * invM1; + md.invMLinN1Z = j.lin1Z * invM1; + md.invMLinN2X = j.lin2X * invM2; + md.invMLinN2Y = j.lin2Y * invM2; + md.invMLinN2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAngN1X = __tmp__X; + md.invMAngN1Y = __tmp__Y; + md.invMAngN1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAngN2X = __tmp__X1; + md.invMAngN2Y = __tmp__Y1; + md.invMAngN2Z = __tmp__Z1; + md.massN = invM1 + invM2 + (md.invMAngN1X * j.ang1X + md.invMAngN1Y * j.ang1Y + md.invMAngN1Z * j.ang1Z) + (md.invMAngN2X * j.ang2X + md.invMAngN2Y * j.ang2Y + md.invMAngN2Z * j.ang2Z); + if(md.massN != 0) { + md.massN = 1 / md.massN; + } + let jt = row.jacobianT; + let jb = row.jacobianB; + md.invMLinT1X = jt.lin1X * invM1; + md.invMLinT1Y = jt.lin1Y * invM1; + md.invMLinT1Z = jt.lin1Z * invM1; + md.invMLinT2X = jt.lin2X * invM2; + md.invMLinT2Y = jt.lin2Y * invM2; + md.invMLinT2Z = jt.lin2Z * invM2; + md.invMLinB1X = jb.lin1X * invM1; + md.invMLinB1Y = jb.lin1Y * invM1; + md.invMLinB1Z = jb.lin1Z * invM1; + md.invMLinB2X = jb.lin2X * invM2; + md.invMLinB2Y = jb.lin2Y * invM2; + md.invMLinB2Z = jb.lin2Z * invM2; + let __tmp__X2; + let __tmp__Y2; + let __tmp__Z2; + __tmp__X2 = invI100 * jt.ang1X + invI101 * jt.ang1Y + invI102 * jt.ang1Z; + __tmp__Y2 = invI110 * jt.ang1X + invI111 * jt.ang1Y + invI112 * jt.ang1Z; + __tmp__Z2 = invI120 * jt.ang1X + invI121 * jt.ang1Y + invI122 * jt.ang1Z; + md.invMAngT1X = __tmp__X2; + md.invMAngT1Y = __tmp__Y2; + md.invMAngT1Z = __tmp__Z2; + let __tmp__X3; + let __tmp__Y3; + let __tmp__Z3; + __tmp__X3 = invI200 * jt.ang2X + invI201 * jt.ang2Y + invI202 * jt.ang2Z; + __tmp__Y3 = invI210 * jt.ang2X + invI211 * jt.ang2Y + invI212 * jt.ang2Z; + __tmp__Z3 = invI220 * jt.ang2X + invI221 * jt.ang2Y + invI222 * jt.ang2Z; + md.invMAngT2X = __tmp__X3; + md.invMAngT2Y = __tmp__Y3; + md.invMAngT2Z = __tmp__Z3; + let __tmp__X4; + let __tmp__Y4; + let __tmp__Z4; + __tmp__X4 = invI100 * jb.ang1X + invI101 * jb.ang1Y + invI102 * jb.ang1Z; + __tmp__Y4 = invI110 * jb.ang1X + invI111 * jb.ang1Y + invI112 * jb.ang1Z; + __tmp__Z4 = invI120 * jb.ang1X + invI121 * jb.ang1Y + invI122 * jb.ang1Z; + md.invMAngB1X = __tmp__X4; + md.invMAngB1Y = __tmp__Y4; + md.invMAngB1Z = __tmp__Z4; + let __tmp__X5; + let __tmp__Y5; + let __tmp__Z5; + __tmp__X5 = invI200 * jb.ang2X + invI201 * jb.ang2Y + invI202 * jb.ang2Z; + __tmp__Y5 = invI210 * jb.ang2X + invI211 * jb.ang2Y + invI212 * jb.ang2Z; + __tmp__Z5 = invI220 * jb.ang2X + invI221 * jb.ang2Y + invI222 * jb.ang2Z; + md.invMAngB2X = __tmp__X5; + md.invMAngB2Y = __tmp__Y5; + md.invMAngB2Z = __tmp__Z5; + let invMassTB00 = invM1 + invM2 + (md.invMAngT1X * jt.ang1X + md.invMAngT1Y * jt.ang1Y + md.invMAngT1Z * jt.ang1Z) + (md.invMAngT2X * jt.ang2X + md.invMAngT2Y * jt.ang2Y + md.invMAngT2Z * jt.ang2Z); + let invMassTB01 = md.invMAngT1X * jb.ang1X + md.invMAngT1Y * jb.ang1Y + md.invMAngT1Z * jb.ang1Z + (md.invMAngT2X * jb.ang2X + md.invMAngT2Y * jb.ang2Y + md.invMAngT2Z * jb.ang2Z); + let invMassTB11 = invM1 + invM2 + (md.invMAngB1X * jb.ang1X + md.invMAngB1Y * jb.ang1Y + md.invMAngB1Z * jb.ang1Z) + (md.invMAngB2X * jb.ang2X + md.invMAngB2Y * jb.ang2Y + md.invMAngB2Z * jb.ang2Z); + let invDet = invMassTB00 * invMassTB11 - invMassTB01 * invMassTB01; + if(invDet != 0) { + invDet = 1 / invDet; + } + md.massTB00 = invMassTB11 * invDet; + md.massTB01 = -invMassTB01 * invDet; + md.massTB10 = -invMassTB01 * invDet; + md.massTB11 = invMassTB00 * invDet; + } + } + warmStart(timeStep) { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let imp = row.impulse; + let md = this.massData[i]; + let jt = row.jacobianT; + let jb = row.jacobianB; + let impulseN = imp.impulseN; + let impulseT = imp.impulseLX * jt.lin1X + imp.impulseLY * jt.lin1Y + imp.impulseLZ * jt.lin1Z; + let impulseB = imp.impulseLX * jb.lin1X + imp.impulseLY * jb.lin1Y + imp.impulseLZ * jb.lin1Z; + imp.impulseT = impulseT; + imp.impulseB = impulseB; + imp.impulseN *= timeStep.dtRatio; + imp.impulseT *= timeStep.dtRatio; + imp.impulseB *= timeStep.dtRatio; + lv1X += md.invMLinN1X * impulseN; + lv1Y += md.invMLinN1Y * impulseN; + lv1Z += md.invMLinN1Z * impulseN; + lv1X += md.invMLinT1X * impulseT; + lv1Y += md.invMLinT1Y * impulseT; + lv1Z += md.invMLinT1Z * impulseT; + lv1X += md.invMLinB1X * impulseB; + lv1Y += md.invMLinB1Y * impulseB; + lv1Z += md.invMLinB1Z * impulseB; + lv2X += md.invMLinN2X * -impulseN; + lv2Y += md.invMLinN2Y * -impulseN; + lv2Z += md.invMLinN2Z * -impulseN; + lv2X += md.invMLinT2X * -impulseT; + lv2Y += md.invMLinT2Y * -impulseT; + lv2Z += md.invMLinT2Z * -impulseT; + lv2X += md.invMLinB2X * -impulseB; + lv2Y += md.invMLinB2Y * -impulseB; + lv2Z += md.invMLinB2Z * -impulseB; + av1X += md.invMAngN1X * impulseN; + av1Y += md.invMAngN1Y * impulseN; + av1Z += md.invMAngN1Z * impulseN; + av1X += md.invMAngT1X * impulseT; + av1Y += md.invMAngT1Y * impulseT; + av1Z += md.invMAngT1Z * impulseT; + av1X += md.invMAngB1X * impulseB; + av1Y += md.invMAngB1Y * impulseB; + av1Z += md.invMAngB1Z * impulseB; + av2X += md.invMAngN2X * -impulseN; + av2Y += md.invMAngN2Y * -impulseN; + av2Z += md.invMAngN2Z * -impulseN; + av2X += md.invMAngT2X * -impulseT; + av2Y += md.invMAngT2Y * -impulseT; + av2Z += md.invMAngT2Z * -impulseT; + av2X += md.invMAngB2X * -impulseB; + av2Y += md.invMAngB2Y * -impulseB; + av2Z += md.invMAngB2Z * -impulseB; + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + solveVelocity() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let rvt = 0; + let j = row.jacobianT; + rvt += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvt -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvt += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvt -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let rvb = 0; + j = row.jacobianB; + rvb += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvb -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvb += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvb -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseT = -(rvt * md.massTB00 + rvb * md.massTB01); + let impulseB = -(rvt * md.massTB10 + rvb * md.massTB11); + let oldImpulseT = imp.impulseT; + let oldImpulseB = imp.impulseB; + imp.impulseT += impulseT; + imp.impulseB += impulseB; + let maxImpulse = row.friction * imp.impulseN; + if(maxImpulse == 0) { + imp.impulseT = 0; + imp.impulseB = 0; + } else { + let impulseLengthSq = imp.impulseT * imp.impulseT + imp.impulseB * imp.impulseB; + if(impulseLengthSq > maxImpulse * maxImpulse) { + let invL = maxImpulse / Math.sqrt(impulseLengthSq); + imp.impulseT *= invL; + imp.impulseB *= invL; + } + } + impulseT = imp.impulseT - oldImpulseT; + impulseB = imp.impulseB - oldImpulseB; + lv1X += md.invMLinT1X * impulseT; + lv1Y += md.invMLinT1Y * impulseT; + lv1Z += md.invMLinT1Z * impulseT; + lv1X += md.invMLinB1X * impulseB; + lv1Y += md.invMLinB1Y * impulseB; + lv1Z += md.invMLinB1Z * impulseB; + lv2X += md.invMLinT2X * -impulseT; + lv2Y += md.invMLinT2Y * -impulseT; + lv2Z += md.invMLinT2Z * -impulseT; + lv2X += md.invMLinB2X * -impulseB; + lv2Y += md.invMLinB2Y * -impulseB; + lv2Z += md.invMLinB2Z * -impulseB; + av1X += md.invMAngT1X * impulseT; + av1Y += md.invMAngT1Y * impulseT; + av1Z += md.invMAngT1Z * impulseT; + av1X += md.invMAngB1X * impulseB; + av1Y += md.invMAngB1Y * impulseB; + av1Z += md.invMAngB1Z * impulseB; + av2X += md.invMAngT2X * -impulseT; + av2Y += md.invMAngT2Y * -impulseT; + av2Z += md.invMAngT2Z * -impulseT; + av2X += md.invMAngB2X * -impulseB; + av2Y += md.invMAngB2Y * -impulseB; + av2Z += md.invMAngB2Z * -impulseB; + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let rvn = 0; + let j = row.jacobianN; + rvn += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvn -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvn += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvn -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseN = (row.rhs - rvn) * md.massN; + let oldImpulseN = imp.impulseN; + imp.impulseN += impulseN; + if(imp.impulseN < 0) { + imp.impulseN = 0; + } + impulseN = imp.impulseN - oldImpulseN; + lv1X += md.invMLinN1X * impulseN; + lv1Y += md.invMLinN1Y * impulseN; + lv1Z += md.invMLinN1Z * impulseN; + lv2X += md.invMLinN2X * -impulseN; + lv2Y += md.invMLinN2Y * -impulseN; + lv2Z += md.invMLinN2Z * -impulseN; + av1X += md.invMAngN1X * impulseN; + av1Y += md.invMAngN1Y * impulseN; + av1Z += md.invMAngN1Z * impulseN; + av2X += md.invMAngN2X * -impulseN; + av2Y += md.invMAngN2Y * -impulseN; + av2Z += md.invMAngN2Z * -impulseN; + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + preSolvePosition(timeStep) { + this.constraint._syncManifold(); + this.constraint._getPositionSolverInfo(this.info); + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobianN; + md.invMLinN1X = j.lin1X * invM1; + md.invMLinN1Y = j.lin1Y * invM1; + md.invMLinN1Z = j.lin1Z * invM1; + md.invMLinN2X = j.lin2X * invM2; + md.invMLinN2Y = j.lin2Y * invM2; + md.invMLinN2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAngN1X = __tmp__X; + md.invMAngN1Y = __tmp__Y; + md.invMAngN1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAngN2X = __tmp__X1; + md.invMAngN2Y = __tmp__Y1; + md.invMAngN2Z = __tmp__Z1; + md.massN = invM1 + invM2 + (md.invMAngN1X * j.ang1X + md.invMAngN1Y * j.ang1Y + md.invMAngN1Z * j.ang1Z) + (md.invMAngN2X * j.ang2X + md.invMAngN2Y * j.ang2Y + md.invMAngN2Z * j.ang2Z); + if(md.massN != 0) { + md.massN = 1 / md.massN; + } + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) this.info.rows[_g2++].impulse.impulseP = 0; + } + solvePositionSplitImpulse() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobianN; + let rvn = 0; + rvn += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvn -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvn += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvn -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs - rvn) * md.massN * oimo.common.Setting.positionSplitImpulseBaumgarte; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < 0) { + imp.impulseP = 0; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLinN1X * impulseP; + lv1Y += md.invMLinN1Y * impulseP; + lv1Z += md.invMLinN1Z * impulseP; + lv2X += md.invMLinN2X * -impulseP; + lv2Y += md.invMLinN2Y * -impulseP; + lv2Z += md.invMLinN2Z * -impulseP; + av1X += md.invMAngN1X * impulseP; + av1Y += md.invMAngN1Y * impulseP; + av1Z += md.invMAngN1Z * impulseP; + av2X += md.invMAngN2X * -impulseP; + av2Y += md.invMAngN2Y * -impulseP; + av2Z += md.invMAngN2Z * -impulseP; + } + this._b1._pseudoVelX = lv1X; + this._b1._pseudoVelY = lv1Y; + this._b1._pseudoVelZ = lv1Z; + this._b2._pseudoVelX = lv2X; + this._b2._pseudoVelY = lv2Y; + this._b2._pseudoVelZ = lv2Z; + this._b1._angPseudoVelX = av1X; + this._b1._angPseudoVelY = av1Y; + this._b1._angPseudoVelZ = av1Z; + this._b2._angPseudoVelX = av2X; + this._b2._angPseudoVelY = av2Y; + this._b2._angPseudoVelZ = av2Z; + } + solvePositionNgs(timeStep) { + this.constraint._syncManifold(); + this.constraint._getPositionSolverInfo(this.info); + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobianN; + md.invMLinN1X = j.lin1X * invM1; + md.invMLinN1Y = j.lin1Y * invM1; + md.invMLinN1Z = j.lin1Z * invM1; + md.invMLinN2X = j.lin2X * invM2; + md.invMLinN2Y = j.lin2Y * invM2; + md.invMLinN2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAngN1X = __tmp__X; + md.invMAngN1Y = __tmp__Y; + md.invMAngN1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAngN2X = __tmp__X1; + md.invMAngN2Y = __tmp__Y1; + md.invMAngN2Z = __tmp__Z1; + md.massN = invM1 + invM2 + (md.invMAngN1X * j.ang1X + md.invMAngN1Y * j.ang1Y + md.invMAngN1Z * j.ang1Z) + (md.invMAngN2X * j.ang2X + md.invMAngN2Y * j.ang2Y + md.invMAngN2Z * j.ang2Z); + if(md.massN != 0) { + md.massN = 1 / md.massN; + } + } + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = 0; + lv1Y = 0; + lv1Z = 0; + lv2X = 0; + lv2Y = 0; + lv2Z = 0; + av1X = 0; + av1Y = 0; + av1Z = 0; + av2X = 0; + av2Y = 0; + av2Z = 0; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobianN; + let rvn = 0; + rvn += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rvn -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rvn += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rvn -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs - rvn) * md.massN * oimo.common.Setting.positionNgsBaumgarte; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < 0) { + imp.impulseP = 0; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLinN1X * impulseP; + lv1Y += md.invMLinN1Y * impulseP; + lv1Z += md.invMLinN1Z * impulseP; + lv2X += md.invMLinN2X * -impulseP; + lv2Y += md.invMLinN2Y * -impulseP; + lv2Z += md.invMLinN2Z * -impulseP; + av1X += md.invMAngN1X * impulseP; + av1Y += md.invMAngN1Y * impulseP; + av1Z += md.invMAngN1Z * impulseP; + av2X += md.invMAngN2X * -impulseP; + av2Y += md.invMAngN2Y * -impulseP; + av2Z += md.invMAngN2Z * -impulseP; + } + let _this = this._b1; + _this._transform._positionX += lv1X; + _this._transform._positionY += lv1Y; + _this._transform._positionZ += lv1Z; + let _this1 = this._b2; + _this1._transform._positionX += lv2X; + _this1._transform._positionY += lv2Y; + _this1._transform._positionZ += lv2Z; + let _this2 = this._b1; + let theta = Math.sqrt(av1X * av1X + av1Y * av1Y + av1Z * av1Z); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = av1X * rotationToSinAxisFactor; + sinAxisY = av1Y * rotationToSinAxisFactor; + sinAxisZ = av1Z * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = _this2._transform._rotation00; + let e11 = _this2._transform._rotation11; + let e22 = _this2._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + qY = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + qZ = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qW = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this2._transform._rotation00 = 1 - yy - zz; + _this2._transform._rotation01 = xy - wz; + _this2._transform._rotation02 = xz + wy; + _this2._transform._rotation10 = xy + wz; + _this2._transform._rotation11 = 1 - xx - zz; + _this2._transform._rotation12 = yz - wx; + _this2._transform._rotation20 = xz - wy; + _this2._transform._rotation21 = yz + wx; + _this2._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = _this2._transform._rotation00 * _this2._invLocalInertia00 + _this2._transform._rotation01 * _this2._invLocalInertia10 + _this2._transform._rotation02 * _this2._invLocalInertia20; + __tmp__01 = _this2._transform._rotation00 * _this2._invLocalInertia01 + _this2._transform._rotation01 * _this2._invLocalInertia11 + _this2._transform._rotation02 * _this2._invLocalInertia21; + __tmp__02 = _this2._transform._rotation00 * _this2._invLocalInertia02 + _this2._transform._rotation01 * _this2._invLocalInertia12 + _this2._transform._rotation02 * _this2._invLocalInertia22; + __tmp__10 = _this2._transform._rotation10 * _this2._invLocalInertia00 + _this2._transform._rotation11 * _this2._invLocalInertia10 + _this2._transform._rotation12 * _this2._invLocalInertia20; + __tmp__11 = _this2._transform._rotation10 * _this2._invLocalInertia01 + _this2._transform._rotation11 * _this2._invLocalInertia11 + _this2._transform._rotation12 * _this2._invLocalInertia21; + __tmp__12 = _this2._transform._rotation10 * _this2._invLocalInertia02 + _this2._transform._rotation11 * _this2._invLocalInertia12 + _this2._transform._rotation12 * _this2._invLocalInertia22; + __tmp__20 = _this2._transform._rotation20 * _this2._invLocalInertia00 + _this2._transform._rotation21 * _this2._invLocalInertia10 + _this2._transform._rotation22 * _this2._invLocalInertia20; + __tmp__21 = _this2._transform._rotation20 * _this2._invLocalInertia01 + _this2._transform._rotation21 * _this2._invLocalInertia11 + _this2._transform._rotation22 * _this2._invLocalInertia21; + __tmp__22 = _this2._transform._rotation20 * _this2._invLocalInertia02 + _this2._transform._rotation21 * _this2._invLocalInertia12 + _this2._transform._rotation22 * _this2._invLocalInertia22; + _this2._invInertia00 = __tmp__00; + _this2._invInertia01 = __tmp__01; + _this2._invInertia02 = __tmp__02; + _this2._invInertia10 = __tmp__10; + _this2._invInertia11 = __tmp__11; + _this2._invInertia12 = __tmp__12; + _this2._invInertia20 = __tmp__20; + _this2._invInertia21 = __tmp__21; + _this2._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = _this2._invInertia00 * _this2._transform._rotation00 + _this2._invInertia01 * _this2._transform._rotation01 + _this2._invInertia02 * _this2._transform._rotation02; + __tmp__011 = _this2._invInertia00 * _this2._transform._rotation10 + _this2._invInertia01 * _this2._transform._rotation11 + _this2._invInertia02 * _this2._transform._rotation12; + __tmp__021 = _this2._invInertia00 * _this2._transform._rotation20 + _this2._invInertia01 * _this2._transform._rotation21 + _this2._invInertia02 * _this2._transform._rotation22; + __tmp__101 = _this2._invInertia10 * _this2._transform._rotation00 + _this2._invInertia11 * _this2._transform._rotation01 + _this2._invInertia12 * _this2._transform._rotation02; + __tmp__111 = _this2._invInertia10 * _this2._transform._rotation10 + _this2._invInertia11 * _this2._transform._rotation11 + _this2._invInertia12 * _this2._transform._rotation12; + __tmp__121 = _this2._invInertia10 * _this2._transform._rotation20 + _this2._invInertia11 * _this2._transform._rotation21 + _this2._invInertia12 * _this2._transform._rotation22; + __tmp__201 = _this2._invInertia20 * _this2._transform._rotation00 + _this2._invInertia21 * _this2._transform._rotation01 + _this2._invInertia22 * _this2._transform._rotation02; + __tmp__211 = _this2._invInertia20 * _this2._transform._rotation10 + _this2._invInertia21 * _this2._transform._rotation11 + _this2._invInertia22 * _this2._transform._rotation12; + __tmp__221 = _this2._invInertia20 * _this2._transform._rotation20 + _this2._invInertia21 * _this2._transform._rotation21 + _this2._invInertia22 * _this2._transform._rotation22; + _this2._invInertia00 = __tmp__001; + _this2._invInertia01 = __tmp__011; + _this2._invInertia02 = __tmp__021; + _this2._invInertia10 = __tmp__101; + _this2._invInertia11 = __tmp__111; + _this2._invInertia12 = __tmp__121; + _this2._invInertia20 = __tmp__201; + _this2._invInertia21 = __tmp__211; + _this2._invInertia22 = __tmp__221; + _this2._invInertia00 *= _this2._rotFactor.x; + _this2._invInertia01 *= _this2._rotFactor.x; + _this2._invInertia02 *= _this2._rotFactor.x; + _this2._invInertia10 *= _this2._rotFactor.y; + _this2._invInertia11 *= _this2._rotFactor.y; + _this2._invInertia12 *= _this2._rotFactor.y; + _this2._invInertia20 *= _this2._rotFactor.z; + _this2._invInertia21 *= _this2._rotFactor.z; + _this2._invInertia22 *= _this2._rotFactor.z; + let _this3 = this._b2; + let theta1 = Math.sqrt(av2X * av2X + av2Y * av2Y + av2Z * av2Z); + let halfTheta1 = theta1 * 0.5; + let rotationToSinAxisFactor1; + let cosHalfTheta1; + if(halfTheta1 < 0.5) { + let ht2 = halfTheta1 * halfTheta1; + rotationToSinAxisFactor1 = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta1 = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor1 = Math.sin(halfTheta1) / theta1; + cosHalfTheta1 = Math.cos(halfTheta1); + } + let sinAxisX1; + let sinAxisY1; + let sinAxisZ1; + sinAxisX1 = av2X * rotationToSinAxisFactor1; + sinAxisY1 = av2Y * rotationToSinAxisFactor1; + sinAxisZ1 = av2Z * rotationToSinAxisFactor1; + let dqX1; + let dqY1; + let dqZ1; + let dqW1; + dqX1 = sinAxisX1; + dqY1 = sinAxisY1; + dqZ1 = sinAxisZ1; + dqW1 = cosHalfTheta1; + let qX1; + let qY1; + let qZ1; + let qW1; + let e001 = _this3._transform._rotation00; + let e111 = _this3._transform._rotation11; + let e221 = _this3._transform._rotation22; + let t1 = e001 + e111 + e221; + let s1; + if(t1 > 0) { + s1 = Math.sqrt(t1 + 1); + qW1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + qY1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + qZ1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } else if(e001 > e111) { + if(e001 > e221) { + s1 = Math.sqrt(e001 - e111 - e221 + 1); + qX1 = 0.5 * s1; + s1 = 0.5 / s1; + qY1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qW1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + } else if(e111 > e221) { + s1 = Math.sqrt(e111 - e221 - e001 + 1); + qY1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + qX1 = dqW1 * qX1 + dqX1 * qW1 + dqY1 * qZ1 - dqZ1 * qY1; + qY1 = dqW1 * qY1 - dqX1 * qZ1 + dqY1 * qW1 + dqZ1 * qX1; + qZ1 = dqW1 * qZ1 + dqX1 * qY1 - dqY1 * qX1 + dqZ1 * qW1; + qW1 = dqW1 * qW1 - dqX1 * qX1 - dqY1 * qY1 - dqZ1 * qZ1; + let l1 = qX1 * qX1 + qY1 * qY1 + qZ1 * qZ1 + qW1 * qW1; + if(l1 > 1e-32) { + l1 = 1 / Math.sqrt(l1); + } + qX1 *= l1; + qY1 *= l1; + qZ1 *= l1; + qW1 *= l1; + let x1 = qX1; + let y1 = qY1; + let z1 = qZ1; + let w1 = qW1; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + _this3._transform._rotation00 = 1 - yy1 - zz1; + _this3._transform._rotation01 = xy1 - wz1; + _this3._transform._rotation02 = xz1 + wy1; + _this3._transform._rotation10 = xy1 + wz1; + _this3._transform._rotation11 = 1 - xx1 - zz1; + _this3._transform._rotation12 = yz1 - wx1; + _this3._transform._rotation20 = xz1 - wy1; + _this3._transform._rotation21 = yz1 + wx1; + _this3._transform._rotation22 = 1 - xx1 - yy1; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = _this3._transform._rotation00 * _this3._invLocalInertia00 + _this3._transform._rotation01 * _this3._invLocalInertia10 + _this3._transform._rotation02 * _this3._invLocalInertia20; + __tmp__012 = _this3._transform._rotation00 * _this3._invLocalInertia01 + _this3._transform._rotation01 * _this3._invLocalInertia11 + _this3._transform._rotation02 * _this3._invLocalInertia21; + __tmp__022 = _this3._transform._rotation00 * _this3._invLocalInertia02 + _this3._transform._rotation01 * _this3._invLocalInertia12 + _this3._transform._rotation02 * _this3._invLocalInertia22; + __tmp__102 = _this3._transform._rotation10 * _this3._invLocalInertia00 + _this3._transform._rotation11 * _this3._invLocalInertia10 + _this3._transform._rotation12 * _this3._invLocalInertia20; + __tmp__112 = _this3._transform._rotation10 * _this3._invLocalInertia01 + _this3._transform._rotation11 * _this3._invLocalInertia11 + _this3._transform._rotation12 * _this3._invLocalInertia21; + __tmp__122 = _this3._transform._rotation10 * _this3._invLocalInertia02 + _this3._transform._rotation11 * _this3._invLocalInertia12 + _this3._transform._rotation12 * _this3._invLocalInertia22; + __tmp__202 = _this3._transform._rotation20 * _this3._invLocalInertia00 + _this3._transform._rotation21 * _this3._invLocalInertia10 + _this3._transform._rotation22 * _this3._invLocalInertia20; + __tmp__212 = _this3._transform._rotation20 * _this3._invLocalInertia01 + _this3._transform._rotation21 * _this3._invLocalInertia11 + _this3._transform._rotation22 * _this3._invLocalInertia21; + __tmp__222 = _this3._transform._rotation20 * _this3._invLocalInertia02 + _this3._transform._rotation21 * _this3._invLocalInertia12 + _this3._transform._rotation22 * _this3._invLocalInertia22; + _this3._invInertia00 = __tmp__002; + _this3._invInertia01 = __tmp__012; + _this3._invInertia02 = __tmp__022; + _this3._invInertia10 = __tmp__102; + _this3._invInertia11 = __tmp__112; + _this3._invInertia12 = __tmp__122; + _this3._invInertia20 = __tmp__202; + _this3._invInertia21 = __tmp__212; + _this3._invInertia22 = __tmp__222; + let __tmp__003; + let __tmp__013; + let __tmp__023; + let __tmp__103; + let __tmp__113; + let __tmp__123; + let __tmp__203; + let __tmp__213; + let __tmp__223; + __tmp__003 = _this3._invInertia00 * _this3._transform._rotation00 + _this3._invInertia01 * _this3._transform._rotation01 + _this3._invInertia02 * _this3._transform._rotation02; + __tmp__013 = _this3._invInertia00 * _this3._transform._rotation10 + _this3._invInertia01 * _this3._transform._rotation11 + _this3._invInertia02 * _this3._transform._rotation12; + __tmp__023 = _this3._invInertia00 * _this3._transform._rotation20 + _this3._invInertia01 * _this3._transform._rotation21 + _this3._invInertia02 * _this3._transform._rotation22; + __tmp__103 = _this3._invInertia10 * _this3._transform._rotation00 + _this3._invInertia11 * _this3._transform._rotation01 + _this3._invInertia12 * _this3._transform._rotation02; + __tmp__113 = _this3._invInertia10 * _this3._transform._rotation10 + _this3._invInertia11 * _this3._transform._rotation11 + _this3._invInertia12 * _this3._transform._rotation12; + __tmp__123 = _this3._invInertia10 * _this3._transform._rotation20 + _this3._invInertia11 * _this3._transform._rotation21 + _this3._invInertia12 * _this3._transform._rotation22; + __tmp__203 = _this3._invInertia20 * _this3._transform._rotation00 + _this3._invInertia21 * _this3._transform._rotation01 + _this3._invInertia22 * _this3._transform._rotation02; + __tmp__213 = _this3._invInertia20 * _this3._transform._rotation10 + _this3._invInertia21 * _this3._transform._rotation11 + _this3._invInertia22 * _this3._transform._rotation12; + __tmp__223 = _this3._invInertia20 * _this3._transform._rotation20 + _this3._invInertia21 * _this3._transform._rotation21 + _this3._invInertia22 * _this3._transform._rotation22; + _this3._invInertia00 = __tmp__003; + _this3._invInertia01 = __tmp__013; + _this3._invInertia02 = __tmp__023; + _this3._invInertia10 = __tmp__103; + _this3._invInertia11 = __tmp__113; + _this3._invInertia12 = __tmp__123; + _this3._invInertia20 = __tmp__203; + _this3._invInertia21 = __tmp__213; + _this3._invInertia22 = __tmp__223; + _this3._invInertia00 *= _this3._rotFactor.x; + _this3._invInertia01 *= _this3._rotFactor.x; + _this3._invInertia02 *= _this3._rotFactor.x; + _this3._invInertia10 *= _this3._rotFactor.y; + _this3._invInertia11 *= _this3._rotFactor.y; + _this3._invInertia12 *= _this3._rotFactor.y; + _this3._invInertia20 *= _this3._rotFactor.z; + _this3._invInertia21 *= _this3._rotFactor.z; + _this3._invInertia22 *= _this3._rotFactor.z; + } + postSolve() { + let lin1X; + let lin1Y; + let lin1Z; + let ang1X; + let ang1Y; + let ang1Z; + let ang2X; + let ang2Y; + let ang2Z; + lin1X = 0; + lin1Y = 0; + lin1Z = 0; + ang1X = 0; + ang1Y = 0; + ang1Z = 0; + ang2X = 0; + ang2Y = 0; + ang2Z = 0; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let row = this.info.rows[_g++]; + let imp = row.impulse; + let jn = row.jacobianN; + let jt = row.jacobianT; + let jb = row.jacobianB; + let impN = imp.impulseN; + let impT = imp.impulseT; + let impB = imp.impulseB; + let impulseLX; + let impulseLY; + let impulseLZ; + impulseLX = 0; + impulseLY = 0; + impulseLZ = 0; + impulseLX += jt.lin1X * impT; + impulseLY += jt.lin1Y * impT; + impulseLZ += jt.lin1Z * impT; + impulseLX += jb.lin1X * impB; + impulseLY += jb.lin1Y * impB; + impulseLZ += jb.lin1Z * impB; + imp.impulseLX = impulseLX; + imp.impulseLY = impulseLY; + imp.impulseLZ = impulseLZ; + lin1X += jn.lin1X * impN; + lin1Y += jn.lin1Y * impN; + lin1Z += jn.lin1Z * impN; + ang1X += jn.ang1X * impN; + ang1Y += jn.ang1Y * impN; + ang1Z += jn.ang1Z * impN; + ang2X += jn.ang2X * impN; + ang2Y += jn.ang2Y * impN; + ang2Z += jn.ang2Z * impN; + lin1X += jt.lin1X * impT; + lin1Y += jt.lin1Y * impT; + lin1Z += jt.lin1Z * impT; + ang1X += jt.ang1X * impT; + ang1Y += jt.ang1Y * impT; + ang1Z += jt.ang1Z * impT; + ang2X += jt.ang2X * impT; + ang2Y += jt.ang2Y * impT; + ang2Z += jt.ang2Z * impT; + lin1X += jb.lin1X * impB; + lin1Y += jb.lin1Y * impB; + lin1Z += jb.lin1Z * impB; + ang1X += jb.ang1X * impB; + ang1Y += jb.ang1Y * impB; + ang1Z += jb.ang1Z * impB; + ang2X += jb.ang2X * impB; + ang2Y += jb.ang2Y * impB; + ang2Z += jb.ang2Z * impB; + } + this._b1._linearContactImpulseX += lin1X; + this._b1._linearContactImpulseY += lin1Y; + this._b1._linearContactImpulseZ += lin1Z; + this._b1._angularContactImpulseX += ang1X; + this._b1._angularContactImpulseY += ang1Y; + this._b1._angularContactImpulseZ += ang1Z; + this._b2._linearContactImpulseX -= lin1X; + this._b2._linearContactImpulseY -= lin1Y; + this._b2._linearContactImpulseZ -= lin1Z; + this._b2._angularContactImpulseX -= ang2X; + this._b2._angularContactImpulseY -= ang2Y; + this._b2._angularContactImpulseZ -= ang2Z; + this.constraint._syncManifold(); + } +} +oimo.dynamics.constraint.solver.pgs.PgsJointConstraintSolver = class oimo_dynamics_constraint_solver_pgs_PgsJointConstraintSolver extends oimo.dynamics.constraint.ConstraintSolver { + constructor(joint) { + super(); + this.joint = joint; + this.info = new oimo.dynamics.constraint.info.joint.JointSolverInfo(); + this.massData = new Array(oimo.common.Setting.maxJacobianRows); + let _g = 0; + let _g1 = this.massData.length; + while(_g < _g1) this.massData[_g++] = new oimo.dynamics.constraint.solver.common.JointSolverMassDataRow(); + } + preSolveVelocity(timeStep) { + this.joint._syncAnchors(); + this.joint._getVelocitySolverInfo(timeStep,this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let j = row.jacobian; + j.updateSparsity(); + if((j.flag & 1) != 0) { + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + } else { + md.invMLin1X = 0; + md.invMLin1Y = 0; + md.invMLin1Z = 0; + md.invMLin2X = 0; + md.invMLin2Y = 0; + md.invMLin2Z = 0; + } + if((j.flag & 2) != 0) { + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + } else { + md.invMAng1X = 0; + md.invMAng1Y = 0; + md.invMAng1Z = 0; + md.invMAng2X = 0; + md.invMAng2Y = 0; + md.invMAng2Z = 0; + } + md.massWithoutCfm = md.invMLin1X * j.lin1X + md.invMLin1Y * j.lin1Y + md.invMLin1Z * j.lin1Z + (md.invMLin2X * j.lin2X + md.invMLin2Y * j.lin2Y + md.invMLin2Z * j.lin2Z) + (md.invMAng1X * j.ang1X + md.invMAng1Y * j.ang1Y + md.invMAng1Z * j.ang1Z) + (md.invMAng2X * j.ang2X + md.invMAng2Y * j.ang2Y + md.invMAng2Z * j.ang2Z); + md.mass = md.massWithoutCfm + row.cfm; + if(md.massWithoutCfm != 0) { + md.massWithoutCfm = 1 / md.massWithoutCfm; + } + if(md.mass != 0) { + md.mass = 1 / md.mass; + } + } + } + warmStart(timeStep) { + let factor = this.joint._positionCorrectionAlgorithm == oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE ? oimo.common.Setting.jointWarmStartingFactorForBaungarte : oimo.common.Setting.jointWarmStartingFactor; + factor *= timeStep.dtRatio; + if(factor <= 0) { + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let _this = this.info.rows[_g++].impulse; + _this.impulse = 0; + _this.impulseM = 0; + _this.impulseP = 0; + } + return; + } + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let imp = this.info.rows[i].impulse; + imp.impulse *= factor; + imp.impulseM *= factor; + let impulse = imp.impulse + imp.impulseM; + lv1X += md.invMLin1X * impulse; + lv1Y += md.invMLin1Y * impulse; + lv1Z += md.invMLin1Z * impulse; + lv2X += md.invMLin2X * -impulse; + lv2Y += md.invMLin2Y * -impulse; + lv2Z += md.invMLin2Z * -impulse; + av1X += md.invMAng1X * impulse; + av1Y += md.invMAng1Y * impulse; + av1Z += md.invMAng1Z * impulse; + av2X += md.invMAng2X * -impulse; + av2Y += md.invMAng2Y * -impulse; + av2Z += md.invMAng2Z * -impulse; + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + solveVelocity() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._velX; + lv1Y = this._b1._velY; + lv1Z = this._b1._velZ; + lv2X = this._b2._velX; + lv2Y = this._b2._velY; + lv2Z = this._b2._velZ; + av1X = this._b1._angVelX; + av1Y = this._b1._angVelY; + av1Z = this._b1._angVelZ; + av2X = this._b2._angVelX; + av2Y = this._b2._angVelY; + av2Z = this._b2._angVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + if(row.motorMaxImpulse == 0) { + continue; + } + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseM = (-row.motorSpeed - rv) * md.massWithoutCfm; + let oldImpulseM = imp.impulseM; + imp.impulseM += impulseM; + if(imp.impulseM < -row.motorMaxImpulse) { + imp.impulseM = -row.motorMaxImpulse; + } else if(imp.impulseM > row.motorMaxImpulse) { + imp.impulseM = row.motorMaxImpulse; + } + impulseM = imp.impulseM - oldImpulseM; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * impulseM; + lv1Y += md.invMLin1Y * impulseM; + lv1Z += md.invMLin1Z * impulseM; + lv2X += md.invMLin2X * -impulseM; + lv2Y += md.invMLin2Y * -impulseM; + lv2Z += md.invMLin2Z * -impulseM; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * impulseM; + av1Y += md.invMAng1Y * impulseM; + av1Z += md.invMAng1Z * impulseM; + av2X += md.invMAng2X * -impulseM; + av2Y += md.invMAng2Y * -impulseM; + av2Z += md.invMAng2Z * -impulseM; + } + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulse = (row.rhs - rv - imp.impulse * row.cfm) * md.mass; + let oldImpulse = imp.impulse; + imp.impulse += impulse; + if(imp.impulse < row.minImpulse) { + imp.impulse = row.minImpulse; + } else if(imp.impulse > row.maxImpulse) { + imp.impulse = row.maxImpulse; + } + impulse = imp.impulse - oldImpulse; + if((j.flag & 1) != 0) { + lv1X += md.invMLin1X * impulse; + lv1Y += md.invMLin1Y * impulse; + lv1Z += md.invMLin1Z * impulse; + lv2X += md.invMLin2X * -impulse; + lv2Y += md.invMLin2Y * -impulse; + lv2Z += md.invMLin2Z * -impulse; + } + if((j.flag & 2) != 0) { + av1X += md.invMAng1X * impulse; + av1Y += md.invMAng1Y * impulse; + av1Z += md.invMAng1Z * impulse; + av2X += md.invMAng2X * -impulse; + av2Y += md.invMAng2Y * -impulse; + av2Z += md.invMAng2Z * -impulse; + } + } + this._b1._velX = lv1X; + this._b1._velY = lv1Y; + this._b1._velZ = lv1Z; + this._b2._velX = lv2X; + this._b2._velY = lv2Y; + this._b2._velZ = lv2Z; + this._b1._angVelX = av1X; + this._b1._angVelY = av1Y; + this._b1._angVelZ = av1Z; + this._b2._angVelX = av2X; + this._b2._angVelY = av2Y; + this._b2._angVelZ = av2Z; + } + postSolveVelocity(timeStep) { + let linX; + let linY; + let linZ; + let angX; + let angY; + let angZ; + linX = 0; + linY = 0; + linZ = 0; + angX = 0; + angY = 0; + angZ = 0; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let row = this.info.rows[_g++]; + let imp = row.impulse; + let j = row.jacobian; + if((j.flag & 1) != 0) { + linX += j.lin1X * imp.impulse; + linY += j.lin1Y * imp.impulse; + linZ += j.lin1Z * imp.impulse; + } else if((j.flag & 2) != 0) { + angX += j.ang1X * imp.impulse; + angY += j.ang1Y * imp.impulse; + angZ += j.ang1Z * imp.impulse; + } + } + this.joint._appliedForceX = linX * timeStep.invDt; + this.joint._appliedForceY = linY * timeStep.invDt; + this.joint._appliedForceZ = linZ * timeStep.invDt; + this.joint._appliedTorqueX = angX * timeStep.invDt; + this.joint._appliedTorqueY = angY * timeStep.invDt; + this.joint._appliedTorqueZ = angZ * timeStep.invDt; + } + preSolvePosition(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobian; + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + md.mass = md.invMLin1X * j.lin1X + md.invMLin1Y * j.lin1Y + md.invMLin1Z * j.lin1Z + (md.invMLin2X * j.lin2X + md.invMLin2Y * j.lin2Y + md.invMLin2Z * j.lin2Z) + (md.invMAng1X * j.ang1X + md.invMAng1Y * j.ang1Y + md.invMAng1Z * j.ang1Z) + (md.invMAng2X * j.ang2X + md.invMAng2Y * j.ang2Y + md.invMAng2Z * j.ang2Z); + if(md.mass != 0) { + md.mass = 1 / md.mass; + } + } + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) this.info.rows[_g2++].impulse.impulseP = 0; + } + solvePositionSplitImpulse() { + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = this._b1._pseudoVelX; + lv1Y = this._b1._pseudoVelY; + lv1Z = this._b1._pseudoVelZ; + lv2X = this._b2._pseudoVelX; + lv2Y = this._b2._pseudoVelY; + lv2Z = this._b2._pseudoVelZ; + av1X = this._b1._angPseudoVelX; + av1Y = this._b1._angPseudoVelY; + av1Z = this._b1._angPseudoVelZ; + av2X = this._b2._angPseudoVelX; + av2Y = this._b2._angPseudoVelY; + av2Z = this._b2._angPseudoVelZ; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs * oimo.common.Setting.positionSplitImpulseBaumgarte - rv) * md.mass; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < row.minImpulse) { + imp.impulseP = row.minImpulse; + } else if(imp.impulseP > row.maxImpulse) { + imp.impulseP = row.maxImpulse; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLin1X * impulseP; + lv1Y += md.invMLin1Y * impulseP; + lv1Z += md.invMLin1Z * impulseP; + lv2X += md.invMLin2X * -impulseP; + lv2Y += md.invMLin2Y * -impulseP; + lv2Z += md.invMLin2Z * -impulseP; + av1X += md.invMAng1X * impulseP; + av1Y += md.invMAng1Y * impulseP; + av1Z += md.invMAng1Z * impulseP; + av2X += md.invMAng2X * -impulseP; + av2Y += md.invMAng2Y * -impulseP; + av2Z += md.invMAng2Z * -impulseP; + } + this._b1._pseudoVelX = lv1X; + this._b1._pseudoVelY = lv1Y; + this._b1._pseudoVelZ = lv1Z; + this._b2._pseudoVelX = lv2X; + this._b2._pseudoVelY = lv2Y; + this._b2._pseudoVelZ = lv2Z; + this._b1._angPseudoVelX = av1X; + this._b1._angPseudoVelY = av1Y; + this._b1._angPseudoVelZ = av1Z; + this._b2._angPseudoVelX = av2X; + this._b2._angPseudoVelY = av2Y; + this._b2._angPseudoVelZ = av2Z; + } + solvePositionNgs(timeStep) { + this.joint._syncAnchors(); + this.joint._getPositionSolverInfo(this.info); + this._b1 = this.info.b1; + this._b2 = this.info.b2; + let invM1 = this._b1._invMass; + let invM2 = this._b2._invMass; + let invI100; + let invI101; + let invI102; + let invI110; + let invI111; + let invI112; + let invI120; + let invI121; + let invI122; + let invI200; + let invI201; + let invI202; + let invI210; + let invI211; + let invI212; + let invI220; + let invI221; + let invI222; + invI100 = this._b1._invInertia00; + invI101 = this._b1._invInertia01; + invI102 = this._b1._invInertia02; + invI110 = this._b1._invInertia10; + invI111 = this._b1._invInertia11; + invI112 = this._b1._invInertia12; + invI120 = this._b1._invInertia20; + invI121 = this._b1._invInertia21; + invI122 = this._b1._invInertia22; + invI200 = this._b2._invInertia00; + invI201 = this._b2._invInertia01; + invI202 = this._b2._invInertia02; + invI210 = this._b2._invInertia10; + invI211 = this._b2._invInertia11; + invI212 = this._b2._invInertia12; + invI220 = this._b2._invInertia20; + invI221 = this._b2._invInertia21; + invI222 = this._b2._invInertia22; + let _g = 0; + let _g1 = this.info.numRows; + while(_g < _g1) { + let i = _g++; + let md = this.massData[i]; + let j = this.info.rows[i].jacobian; + md.invMLin1X = j.lin1X * invM1; + md.invMLin1Y = j.lin1Y * invM1; + md.invMLin1Z = j.lin1Z * invM1; + md.invMLin2X = j.lin2X * invM2; + md.invMLin2Y = j.lin2Y * invM2; + md.invMLin2Z = j.lin2Z * invM2; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = invI100 * j.ang1X + invI101 * j.ang1Y + invI102 * j.ang1Z; + __tmp__Y = invI110 * j.ang1X + invI111 * j.ang1Y + invI112 * j.ang1Z; + __tmp__Z = invI120 * j.ang1X + invI121 * j.ang1Y + invI122 * j.ang1Z; + md.invMAng1X = __tmp__X; + md.invMAng1Y = __tmp__Y; + md.invMAng1Z = __tmp__Z; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = invI200 * j.ang2X + invI201 * j.ang2Y + invI202 * j.ang2Z; + __tmp__Y1 = invI210 * j.ang2X + invI211 * j.ang2Y + invI212 * j.ang2Z; + __tmp__Z1 = invI220 * j.ang2X + invI221 * j.ang2Y + invI222 * j.ang2Z; + md.invMAng2X = __tmp__X1; + md.invMAng2Y = __tmp__Y1; + md.invMAng2Z = __tmp__Z1; + md.mass = md.invMLin1X * j.lin1X + md.invMLin1Y * j.lin1Y + md.invMLin1Z * j.lin1Z + (md.invMLin2X * j.lin2X + md.invMLin2Y * j.lin2Y + md.invMLin2Z * j.lin2Z) + (md.invMAng1X * j.ang1X + md.invMAng1Y * j.ang1Y + md.invMAng1Z * j.ang1Z) + (md.invMAng2X * j.ang2X + md.invMAng2Y * j.ang2Y + md.invMAng2Z * j.ang2Z); + if(md.mass != 0) { + md.mass = 1 / md.mass; + } + } + let lv1X; + let lv1Y; + let lv1Z; + let lv2X; + let lv2Y; + let lv2Z; + let av1X; + let av1Y; + let av1Z; + let av2X; + let av2Y; + let av2Z; + lv1X = 0; + lv1Y = 0; + lv1Z = 0; + lv2X = 0; + lv2Y = 0; + lv2Z = 0; + av1X = 0; + av1Y = 0; + av1Z = 0; + av2X = 0; + av2Y = 0; + av2Z = 0; + let _g2 = 0; + let _g3 = this.info.numRows; + while(_g2 < _g3) { + let i = _g2++; + let row = this.info.rows[i]; + let md = this.massData[i]; + let imp = row.impulse; + let j = row.jacobian; + let rv = 0; + rv += lv1X * j.lin1X + lv1Y * j.lin1Y + lv1Z * j.lin1Z; + rv -= lv2X * j.lin2X + lv2Y * j.lin2Y + lv2Z * j.lin2Z; + rv += av1X * j.ang1X + av1Y * j.ang1Y + av1Z * j.ang1Z; + rv -= av2X * j.ang2X + av2Y * j.ang2Y + av2Z * j.ang2Z; + let impulseP = (row.rhs * oimo.common.Setting.positionNgsBaumgarte - rv) * md.mass; + let oldImpulseP = imp.impulseP; + imp.impulseP += impulseP; + if(imp.impulseP < row.minImpulse) { + imp.impulseP = row.minImpulse; + } else if(imp.impulseP > row.maxImpulse) { + imp.impulseP = row.maxImpulse; + } + impulseP = imp.impulseP - oldImpulseP; + lv1X += md.invMLin1X * impulseP; + lv1Y += md.invMLin1Y * impulseP; + lv1Z += md.invMLin1Z * impulseP; + lv2X += md.invMLin2X * -impulseP; + lv2Y += md.invMLin2Y * -impulseP; + lv2Z += md.invMLin2Z * -impulseP; + av1X += md.invMAng1X * impulseP; + av1Y += md.invMAng1Y * impulseP; + av1Z += md.invMAng1Z * impulseP; + av2X += md.invMAng2X * -impulseP; + av2Y += md.invMAng2Y * -impulseP; + av2Z += md.invMAng2Z * -impulseP; + } + let _this = this._b1; + _this._transform._positionX += lv1X; + _this._transform._positionY += lv1Y; + _this._transform._positionZ += lv1Z; + let _this1 = this._b2; + _this1._transform._positionX += lv2X; + _this1._transform._positionY += lv2Y; + _this1._transform._positionZ += lv2Z; + let _this2 = this._b1; + let theta = Math.sqrt(av1X * av1X + av1Y * av1Y + av1Z * av1Z); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = av1X * rotationToSinAxisFactor; + sinAxisY = av1Y * rotationToSinAxisFactor; + sinAxisZ = av1Z * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = _this2._transform._rotation00; + let e11 = _this2._transform._rotation11; + let e22 = _this2._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + qY = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + qZ = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qW = (_this2._transform._rotation21 - _this2._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation01 + _this2._transform._rotation10) * s; + qZ = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation02 - _this2._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (_this2._transform._rotation02 + _this2._transform._rotation20) * s; + qY = (_this2._transform._rotation12 + _this2._transform._rotation21) * s; + qW = (_this2._transform._rotation10 - _this2._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + _this2._transform._rotation00 = 1 - yy - zz; + _this2._transform._rotation01 = xy - wz; + _this2._transform._rotation02 = xz + wy; + _this2._transform._rotation10 = xy + wz; + _this2._transform._rotation11 = 1 - xx - zz; + _this2._transform._rotation12 = yz - wx; + _this2._transform._rotation20 = xz - wy; + _this2._transform._rotation21 = yz + wx; + _this2._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = _this2._transform._rotation00 * _this2._invLocalInertia00 + _this2._transform._rotation01 * _this2._invLocalInertia10 + _this2._transform._rotation02 * _this2._invLocalInertia20; + __tmp__01 = _this2._transform._rotation00 * _this2._invLocalInertia01 + _this2._transform._rotation01 * _this2._invLocalInertia11 + _this2._transform._rotation02 * _this2._invLocalInertia21; + __tmp__02 = _this2._transform._rotation00 * _this2._invLocalInertia02 + _this2._transform._rotation01 * _this2._invLocalInertia12 + _this2._transform._rotation02 * _this2._invLocalInertia22; + __tmp__10 = _this2._transform._rotation10 * _this2._invLocalInertia00 + _this2._transform._rotation11 * _this2._invLocalInertia10 + _this2._transform._rotation12 * _this2._invLocalInertia20; + __tmp__11 = _this2._transform._rotation10 * _this2._invLocalInertia01 + _this2._transform._rotation11 * _this2._invLocalInertia11 + _this2._transform._rotation12 * _this2._invLocalInertia21; + __tmp__12 = _this2._transform._rotation10 * _this2._invLocalInertia02 + _this2._transform._rotation11 * _this2._invLocalInertia12 + _this2._transform._rotation12 * _this2._invLocalInertia22; + __tmp__20 = _this2._transform._rotation20 * _this2._invLocalInertia00 + _this2._transform._rotation21 * _this2._invLocalInertia10 + _this2._transform._rotation22 * _this2._invLocalInertia20; + __tmp__21 = _this2._transform._rotation20 * _this2._invLocalInertia01 + _this2._transform._rotation21 * _this2._invLocalInertia11 + _this2._transform._rotation22 * _this2._invLocalInertia21; + __tmp__22 = _this2._transform._rotation20 * _this2._invLocalInertia02 + _this2._transform._rotation21 * _this2._invLocalInertia12 + _this2._transform._rotation22 * _this2._invLocalInertia22; + _this2._invInertia00 = __tmp__00; + _this2._invInertia01 = __tmp__01; + _this2._invInertia02 = __tmp__02; + _this2._invInertia10 = __tmp__10; + _this2._invInertia11 = __tmp__11; + _this2._invInertia12 = __tmp__12; + _this2._invInertia20 = __tmp__20; + _this2._invInertia21 = __tmp__21; + _this2._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = _this2._invInertia00 * _this2._transform._rotation00 + _this2._invInertia01 * _this2._transform._rotation01 + _this2._invInertia02 * _this2._transform._rotation02; + __tmp__011 = _this2._invInertia00 * _this2._transform._rotation10 + _this2._invInertia01 * _this2._transform._rotation11 + _this2._invInertia02 * _this2._transform._rotation12; + __tmp__021 = _this2._invInertia00 * _this2._transform._rotation20 + _this2._invInertia01 * _this2._transform._rotation21 + _this2._invInertia02 * _this2._transform._rotation22; + __tmp__101 = _this2._invInertia10 * _this2._transform._rotation00 + _this2._invInertia11 * _this2._transform._rotation01 + _this2._invInertia12 * _this2._transform._rotation02; + __tmp__111 = _this2._invInertia10 * _this2._transform._rotation10 + _this2._invInertia11 * _this2._transform._rotation11 + _this2._invInertia12 * _this2._transform._rotation12; + __tmp__121 = _this2._invInertia10 * _this2._transform._rotation20 + _this2._invInertia11 * _this2._transform._rotation21 + _this2._invInertia12 * _this2._transform._rotation22; + __tmp__201 = _this2._invInertia20 * _this2._transform._rotation00 + _this2._invInertia21 * _this2._transform._rotation01 + _this2._invInertia22 * _this2._transform._rotation02; + __tmp__211 = _this2._invInertia20 * _this2._transform._rotation10 + _this2._invInertia21 * _this2._transform._rotation11 + _this2._invInertia22 * _this2._transform._rotation12; + __tmp__221 = _this2._invInertia20 * _this2._transform._rotation20 + _this2._invInertia21 * _this2._transform._rotation21 + _this2._invInertia22 * _this2._transform._rotation22; + _this2._invInertia00 = __tmp__001; + _this2._invInertia01 = __tmp__011; + _this2._invInertia02 = __tmp__021; + _this2._invInertia10 = __tmp__101; + _this2._invInertia11 = __tmp__111; + _this2._invInertia12 = __tmp__121; + _this2._invInertia20 = __tmp__201; + _this2._invInertia21 = __tmp__211; + _this2._invInertia22 = __tmp__221; + _this2._invInertia00 *= _this2._rotFactor.x; + _this2._invInertia01 *= _this2._rotFactor.x; + _this2._invInertia02 *= _this2._rotFactor.x; + _this2._invInertia10 *= _this2._rotFactor.y; + _this2._invInertia11 *= _this2._rotFactor.y; + _this2._invInertia12 *= _this2._rotFactor.y; + _this2._invInertia20 *= _this2._rotFactor.z; + _this2._invInertia21 *= _this2._rotFactor.z; + _this2._invInertia22 *= _this2._rotFactor.z; + let _this3 = this._b2; + let theta1 = Math.sqrt(av2X * av2X + av2Y * av2Y + av2Z * av2Z); + let halfTheta1 = theta1 * 0.5; + let rotationToSinAxisFactor1; + let cosHalfTheta1; + if(halfTheta1 < 0.5) { + let ht2 = halfTheta1 * halfTheta1; + rotationToSinAxisFactor1 = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta1 = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor1 = Math.sin(halfTheta1) / theta1; + cosHalfTheta1 = Math.cos(halfTheta1); + } + let sinAxisX1; + let sinAxisY1; + let sinAxisZ1; + sinAxisX1 = av2X * rotationToSinAxisFactor1; + sinAxisY1 = av2Y * rotationToSinAxisFactor1; + sinAxisZ1 = av2Z * rotationToSinAxisFactor1; + let dqX1; + let dqY1; + let dqZ1; + let dqW1; + dqX1 = sinAxisX1; + dqY1 = sinAxisY1; + dqZ1 = sinAxisZ1; + dqW1 = cosHalfTheta1; + let qX1; + let qY1; + let qZ1; + let qW1; + let e001 = _this3._transform._rotation00; + let e111 = _this3._transform._rotation11; + let e221 = _this3._transform._rotation22; + let t1 = e001 + e111 + e221; + let s1; + if(t1 > 0) { + s1 = Math.sqrt(t1 + 1); + qW1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + qY1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + qZ1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } else if(e001 > e111) { + if(e001 > e221) { + s1 = Math.sqrt(e001 - e111 - e221 + 1); + qX1 = 0.5 * s1; + s1 = 0.5 / s1; + qY1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qW1 = (_this3._transform._rotation21 - _this3._transform._rotation12) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + } else if(e111 > e221) { + s1 = Math.sqrt(e111 - e221 - e001 + 1); + qY1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation01 + _this3._transform._rotation10) * s1; + qZ1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation02 - _this3._transform._rotation20) * s1; + } else { + s1 = Math.sqrt(e221 - e001 - e111 + 1); + qZ1 = 0.5 * s1; + s1 = 0.5 / s1; + qX1 = (_this3._transform._rotation02 + _this3._transform._rotation20) * s1; + qY1 = (_this3._transform._rotation12 + _this3._transform._rotation21) * s1; + qW1 = (_this3._transform._rotation10 - _this3._transform._rotation01) * s1; + } + qX1 = dqW1 * qX1 + dqX1 * qW1 + dqY1 * qZ1 - dqZ1 * qY1; + qY1 = dqW1 * qY1 - dqX1 * qZ1 + dqY1 * qW1 + dqZ1 * qX1; + qZ1 = dqW1 * qZ1 + dqX1 * qY1 - dqY1 * qX1 + dqZ1 * qW1; + qW1 = dqW1 * qW1 - dqX1 * qX1 - dqY1 * qY1 - dqZ1 * qZ1; + let l1 = qX1 * qX1 + qY1 * qY1 + qZ1 * qZ1 + qW1 * qW1; + if(l1 > 1e-32) { + l1 = 1 / Math.sqrt(l1); + } + qX1 *= l1; + qY1 *= l1; + qZ1 *= l1; + qW1 *= l1; + let x1 = qX1; + let y1 = qY1; + let z1 = qZ1; + let w1 = qW1; + let x21 = 2 * x1; + let y21 = 2 * y1; + let z21 = 2 * z1; + let xx1 = x1 * x21; + let yy1 = y1 * y21; + let zz1 = z1 * z21; + let xy1 = x1 * y21; + let yz1 = y1 * z21; + let xz1 = x1 * z21; + let wx1 = w1 * x21; + let wy1 = w1 * y21; + let wz1 = w1 * z21; + _this3._transform._rotation00 = 1 - yy1 - zz1; + _this3._transform._rotation01 = xy1 - wz1; + _this3._transform._rotation02 = xz1 + wy1; + _this3._transform._rotation10 = xy1 + wz1; + _this3._transform._rotation11 = 1 - xx1 - zz1; + _this3._transform._rotation12 = yz1 - wx1; + _this3._transform._rotation20 = xz1 - wy1; + _this3._transform._rotation21 = yz1 + wx1; + _this3._transform._rotation22 = 1 - xx1 - yy1; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = _this3._transform._rotation00 * _this3._invLocalInertia00 + _this3._transform._rotation01 * _this3._invLocalInertia10 + _this3._transform._rotation02 * _this3._invLocalInertia20; + __tmp__012 = _this3._transform._rotation00 * _this3._invLocalInertia01 + _this3._transform._rotation01 * _this3._invLocalInertia11 + _this3._transform._rotation02 * _this3._invLocalInertia21; + __tmp__022 = _this3._transform._rotation00 * _this3._invLocalInertia02 + _this3._transform._rotation01 * _this3._invLocalInertia12 + _this3._transform._rotation02 * _this3._invLocalInertia22; + __tmp__102 = _this3._transform._rotation10 * _this3._invLocalInertia00 + _this3._transform._rotation11 * _this3._invLocalInertia10 + _this3._transform._rotation12 * _this3._invLocalInertia20; + __tmp__112 = _this3._transform._rotation10 * _this3._invLocalInertia01 + _this3._transform._rotation11 * _this3._invLocalInertia11 + _this3._transform._rotation12 * _this3._invLocalInertia21; + __tmp__122 = _this3._transform._rotation10 * _this3._invLocalInertia02 + _this3._transform._rotation11 * _this3._invLocalInertia12 + _this3._transform._rotation12 * _this3._invLocalInertia22; + __tmp__202 = _this3._transform._rotation20 * _this3._invLocalInertia00 + _this3._transform._rotation21 * _this3._invLocalInertia10 + _this3._transform._rotation22 * _this3._invLocalInertia20; + __tmp__212 = _this3._transform._rotation20 * _this3._invLocalInertia01 + _this3._transform._rotation21 * _this3._invLocalInertia11 + _this3._transform._rotation22 * _this3._invLocalInertia21; + __tmp__222 = _this3._transform._rotation20 * _this3._invLocalInertia02 + _this3._transform._rotation21 * _this3._invLocalInertia12 + _this3._transform._rotation22 * _this3._invLocalInertia22; + _this3._invInertia00 = __tmp__002; + _this3._invInertia01 = __tmp__012; + _this3._invInertia02 = __tmp__022; + _this3._invInertia10 = __tmp__102; + _this3._invInertia11 = __tmp__112; + _this3._invInertia12 = __tmp__122; + _this3._invInertia20 = __tmp__202; + _this3._invInertia21 = __tmp__212; + _this3._invInertia22 = __tmp__222; + let __tmp__003; + let __tmp__013; + let __tmp__023; + let __tmp__103; + let __tmp__113; + let __tmp__123; + let __tmp__203; + let __tmp__213; + let __tmp__223; + __tmp__003 = _this3._invInertia00 * _this3._transform._rotation00 + _this3._invInertia01 * _this3._transform._rotation01 + _this3._invInertia02 * _this3._transform._rotation02; + __tmp__013 = _this3._invInertia00 * _this3._transform._rotation10 + _this3._invInertia01 * _this3._transform._rotation11 + _this3._invInertia02 * _this3._transform._rotation12; + __tmp__023 = _this3._invInertia00 * _this3._transform._rotation20 + _this3._invInertia01 * _this3._transform._rotation21 + _this3._invInertia02 * _this3._transform._rotation22; + __tmp__103 = _this3._invInertia10 * _this3._transform._rotation00 + _this3._invInertia11 * _this3._transform._rotation01 + _this3._invInertia12 * _this3._transform._rotation02; + __tmp__113 = _this3._invInertia10 * _this3._transform._rotation10 + _this3._invInertia11 * _this3._transform._rotation11 + _this3._invInertia12 * _this3._transform._rotation12; + __tmp__123 = _this3._invInertia10 * _this3._transform._rotation20 + _this3._invInertia11 * _this3._transform._rotation21 + _this3._invInertia12 * _this3._transform._rotation22; + __tmp__203 = _this3._invInertia20 * _this3._transform._rotation00 + _this3._invInertia21 * _this3._transform._rotation01 + _this3._invInertia22 * _this3._transform._rotation02; + __tmp__213 = _this3._invInertia20 * _this3._transform._rotation10 + _this3._invInertia21 * _this3._transform._rotation11 + _this3._invInertia22 * _this3._transform._rotation12; + __tmp__223 = _this3._invInertia20 * _this3._transform._rotation20 + _this3._invInertia21 * _this3._transform._rotation21 + _this3._invInertia22 * _this3._transform._rotation22; + _this3._invInertia00 = __tmp__003; + _this3._invInertia01 = __tmp__013; + _this3._invInertia02 = __tmp__023; + _this3._invInertia10 = __tmp__103; + _this3._invInertia11 = __tmp__113; + _this3._invInertia12 = __tmp__123; + _this3._invInertia20 = __tmp__203; + _this3._invInertia21 = __tmp__213; + _this3._invInertia22 = __tmp__223; + _this3._invInertia00 *= _this3._rotFactor.x; + _this3._invInertia01 *= _this3._rotFactor.x; + _this3._invInertia02 *= _this3._rotFactor.x; + _this3._invInertia10 *= _this3._rotFactor.y; + _this3._invInertia11 *= _this3._rotFactor.y; + _this3._invInertia12 *= _this3._rotFactor.y; + _this3._invInertia20 *= _this3._rotFactor.z; + _this3._invInertia21 *= _this3._rotFactor.z; + _this3._invInertia22 *= _this3._rotFactor.z; + } + postSolve() { + this.joint._syncAnchors(); + this.joint._checkDestruction(); + } +} +if(!oimo.dynamics.rigidbody) oimo.dynamics.rigidbody = {}; +oimo.dynamics.rigidbody.MassData = class oimo_dynamics_rigidbody_MassData { + constructor() { + this.mass = 0; + this.localInertia = new oimo.common.Mat3(); + } +} +oimo.dynamics.rigidbody.RigidBody = class oimo_dynamics_rigidbody_RigidBody { + constructor(config) { + this._next = null; + this._prev = null; + this._shapeList = null; + this._shapeListLast = null; + this._numShapes = 0; + this._contactLinkList = null; + this._contactLinkListLast = null; + this._numContactLinks = 0; + this._jointLinkList = null; + this._jointLinkListLast = null; + this._numJointLinks = 0; + let v = config.linearVelocity; + this._velX = v.x; + this._velY = v.y; + this._velZ = v.z; + let v1 = config.angularVelocity; + this._angVelX = v1.x; + this._angVelY = v1.y; + this._angVelZ = v1.z; + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + this._ptransform = new oimo.common.Transform(); + this._transform = new oimo.common.Transform(); + let v2 = config.position; + this._ptransform._positionX = v2.x; + this._ptransform._positionY = v2.y; + this._ptransform._positionZ = v2.z; + let m = config.rotation; + this._ptransform._rotation00 = m.e00; + this._ptransform._rotation01 = m.e01; + this._ptransform._rotation02 = m.e02; + this._ptransform._rotation10 = m.e10; + this._ptransform._rotation11 = m.e11; + this._ptransform._rotation12 = m.e12; + this._ptransform._rotation20 = m.e20; + this._ptransform._rotation21 = m.e21; + this._ptransform._rotation22 = m.e22; + let dst = this._transform; + let src = this._ptransform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + this._type = config.type; + this._sleepTime = 0; + this._sleeping = false; + this._autoSleep = config.autoSleep; + this._mass = 0; + this._invMass = 0; + this._localInertia00 = 0; + this._localInertia01 = 0; + this._localInertia02 = 0; + this._localInertia10 = 0; + this._localInertia11 = 0; + this._localInertia12 = 0; + this._localInertia20 = 0; + this._localInertia21 = 0; + this._localInertia22 = 0; + this._invLocalInertia00 = 0; + this._invLocalInertia01 = 0; + this._invLocalInertia02 = 0; + this._invLocalInertia10 = 0; + this._invLocalInertia11 = 0; + this._invLocalInertia12 = 0; + this._invLocalInertia20 = 0; + this._invLocalInertia21 = 0; + this._invLocalInertia22 = 0; + this._invLocalInertiaWithoutRotFactor00 = 0; + this._invLocalInertiaWithoutRotFactor01 = 0; + this._invLocalInertiaWithoutRotFactor02 = 0; + this._invLocalInertiaWithoutRotFactor10 = 0; + this._invLocalInertiaWithoutRotFactor11 = 0; + this._invLocalInertiaWithoutRotFactor12 = 0; + this._invLocalInertiaWithoutRotFactor20 = 0; + this._invLocalInertiaWithoutRotFactor21 = 0; + this._invLocalInertiaWithoutRotFactor22 = 0; + this._invInertia00 = 0; + this._invInertia01 = 0; + this._invInertia02 = 0; + this._invInertia10 = 0; + this._invInertia11 = 0; + this._invInertia12 = 0; + this._invInertia20 = 0; + this._invInertia21 = 0; + this._invInertia22 = 0; + this._linearDamping = config.linearDamping; + this._angularDamping = config.angularDamping; + this._forceX = 0; + this._forceY = 0; + this._forceZ = 0; + this._torqueX = 0; + this._torqueY = 0; + this._torqueZ = 0; + this._linearContactImpulseX = 0; + this._linearContactImpulseY = 0; + this._linearContactImpulseZ = 0; + this._angularContactImpulseX = 0; + this._angularContactImpulseY = 0; + this._angularContactImpulseZ = 0; + this._rotFactor = new oimo.common.Vec3(1,1,1); + this._addedToIsland = false; + this._gravityScale = 1; + this._world = null; + } + _integrate(dt) { + switch(this._type) { + case 1: + this._velX = 0; + this._velY = 0; + this._velZ = 0; + this._angVelX = 0; + this._angVelY = 0; + this._angVelZ = 0; + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + break; + case 0:case 2: + let translationX; + let translationY; + let translationZ; + let rotationX; + let rotationY; + let rotationZ; + translationX = this._velX * dt; + translationY = this._velY * dt; + translationZ = this._velZ * dt; + rotationX = this._angVelX * dt; + rotationY = this._angVelY * dt; + rotationZ = this._angVelZ * dt; + let translationLengthSq = translationX * translationX + translationY * translationY + translationZ * translationZ; + let rotationLengthSq = rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ; + if(translationLengthSq == 0 && rotationLengthSq == 0) { + return; + } + if(translationLengthSq > oimo.common.Setting.maxTranslationPerStep * oimo.common.Setting.maxTranslationPerStep) { + let l = oimo.common.Setting.maxTranslationPerStep / Math.sqrt(translationLengthSq); + this._velX *= l; + this._velY *= l; + this._velZ *= l; + translationX *= l; + translationY *= l; + translationZ *= l; + } + if(rotationLengthSq > oimo.common.Setting.maxRotationPerStep * oimo.common.Setting.maxRotationPerStep) { + let l = oimo.common.Setting.maxRotationPerStep / Math.sqrt(rotationLengthSq); + this._angVelX *= l; + this._angVelY *= l; + this._angVelZ *= l; + rotationX *= l; + rotationY *= l; + rotationZ *= l; + } + this._transform._positionX += translationX; + this._transform._positionY += translationY; + this._transform._positionZ += translationZ; + let theta = Math.sqrt(rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = rotationX * rotationToSinAxisFactor; + sinAxisY = rotationY * rotationToSinAxisFactor; + sinAxisZ = rotationZ * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation21 - this._transform._rotation12) * s; + qY = (this._transform._rotation02 - this._transform._rotation20) * s; + qZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation02 + this._transform._rotation20) * s; + qW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._transform._rotation00 = 1 - yy - zz; + this._transform._rotation01 = xy - wz; + this._transform._rotation02 = xz + wy; + this._transform._rotation10 = xy + wz; + this._transform._rotation11 = 1 - xx - zz; + this._transform._rotation12 = yz - wx; + this._transform._rotation20 = xz - wy; + this._transform._rotation21 = yz + wx; + this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + break; + } + } + _integratePseudoVelocity() { + if(this._pseudoVelX * this._pseudoVelX + this._pseudoVelY * this._pseudoVelY + this._pseudoVelZ * this._pseudoVelZ == 0 && this._angPseudoVelX * this._angPseudoVelX + this._angPseudoVelY * this._angPseudoVelY + this._angPseudoVelZ * this._angPseudoVelZ == 0) { + return; + } + switch(this._type) { + case 1: + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + break; + case 0:case 2: + let translationX; + let translationY; + let translationZ; + let rotationX; + let rotationY; + let rotationZ; + translationX = this._pseudoVelX; + translationY = this._pseudoVelY; + translationZ = this._pseudoVelZ; + rotationX = this._angPseudoVelX; + rotationY = this._angPseudoVelY; + rotationZ = this._angPseudoVelZ; + this._pseudoVelX = 0; + this._pseudoVelY = 0; + this._pseudoVelZ = 0; + this._angPseudoVelX = 0; + this._angPseudoVelY = 0; + this._angPseudoVelZ = 0; + this._transform._positionX += translationX; + this._transform._positionY += translationY; + this._transform._positionZ += translationZ; + let theta = Math.sqrt(rotationX * rotationX + rotationY * rotationY + rotationZ * rotationZ); + let halfTheta = theta * 0.5; + let rotationToSinAxisFactor; + let cosHalfTheta; + if(halfTheta < 0.5) { + let ht2 = halfTheta * halfTheta; + rotationToSinAxisFactor = 0.5 * (1 - ht2 * 0.16666666666666666 + ht2 * ht2 * 0.0083333333333333332); + cosHalfTheta = 1 - ht2 * 0.5 + ht2 * ht2 * 0.041666666666666664; + } else { + rotationToSinAxisFactor = Math.sin(halfTheta) / theta; + cosHalfTheta = Math.cos(halfTheta); + } + let sinAxisX; + let sinAxisY; + let sinAxisZ; + sinAxisX = rotationX * rotationToSinAxisFactor; + sinAxisY = rotationY * rotationToSinAxisFactor; + sinAxisZ = rotationZ * rotationToSinAxisFactor; + let dqX; + let dqY; + let dqZ; + let dqW; + dqX = sinAxisX; + dqY = sinAxisY; + dqZ = sinAxisZ; + dqW = cosHalfTheta; + let qX; + let qY; + let qZ; + let qW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + qW = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation21 - this._transform._rotation12) * s; + qY = (this._transform._rotation02 - this._transform._rotation20) * s; + qZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + qX = 0.5 * s; + s = 0.5 / s; + qY = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation02 + this._transform._rotation20) * s; + qW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + qY = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation01 + this._transform._rotation10) * s; + qZ = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + qZ = 0.5 * s; + s = 0.5 / s; + qX = (this._transform._rotation02 + this._transform._rotation20) * s; + qY = (this._transform._rotation12 + this._transform._rotation21) * s; + qW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + qX = dqW * qX + dqX * qW + dqY * qZ - dqZ * qY; + qY = dqW * qY - dqX * qZ + dqY * qW + dqZ * qX; + qZ = dqW * qZ + dqX * qY - dqY * qX + dqZ * qW; + qW = dqW * qW - dqX * qX - dqY * qY - dqZ * qZ; + let l = qX * qX + qY * qY + qZ * qZ + qW * qW; + if(l > 1e-32) { + l = 1 / Math.sqrt(l); + } + qX *= l; + qY *= l; + qZ *= l; + qW *= l; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._transform._rotation00 = 1 - yy - zz; + this._transform._rotation01 = xy - wz; + this._transform._rotation02 = xz + wy; + this._transform._rotation10 = xy + wz; + this._transform._rotation11 = 1 - xx - zz; + this._transform._rotation12 = yz - wx; + this._transform._rotation20 = xz - wy; + this._transform._rotation21 = yz + wx; + this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + break; + } + } + updateMass() { + let totalInertia00; + let totalInertia01; + let totalInertia02; + let totalInertia10; + let totalInertia11; + let totalInertia12; + let totalInertia20; + let totalInertia21; + let totalInertia22; + totalInertia00 = 0; + totalInertia01 = 0; + totalInertia02 = 0; + totalInertia10 = 0; + totalInertia11 = 0; + totalInertia12 = 0; + totalInertia20 = 0; + totalInertia21 = 0; + totalInertia22 = 0; + let totalMass = 0; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let g = s._geom; + g._updateMass(); + let mass = s._density * g._volume; + let inertia00; + let inertia01; + let inertia02; + let inertia10; + let inertia11; + let inertia12; + let inertia20; + let inertia21; + let inertia22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = s._localTransform._rotation00 * g._inertiaCoeff00 + s._localTransform._rotation01 * g._inertiaCoeff10 + s._localTransform._rotation02 * g._inertiaCoeff20; + __tmp__01 = s._localTransform._rotation00 * g._inertiaCoeff01 + s._localTransform._rotation01 * g._inertiaCoeff11 + s._localTransform._rotation02 * g._inertiaCoeff21; + __tmp__02 = s._localTransform._rotation00 * g._inertiaCoeff02 + s._localTransform._rotation01 * g._inertiaCoeff12 + s._localTransform._rotation02 * g._inertiaCoeff22; + __tmp__10 = s._localTransform._rotation10 * g._inertiaCoeff00 + s._localTransform._rotation11 * g._inertiaCoeff10 + s._localTransform._rotation12 * g._inertiaCoeff20; + __tmp__11 = s._localTransform._rotation10 * g._inertiaCoeff01 + s._localTransform._rotation11 * g._inertiaCoeff11 + s._localTransform._rotation12 * g._inertiaCoeff21; + __tmp__12 = s._localTransform._rotation10 * g._inertiaCoeff02 + s._localTransform._rotation11 * g._inertiaCoeff12 + s._localTransform._rotation12 * g._inertiaCoeff22; + __tmp__20 = s._localTransform._rotation20 * g._inertiaCoeff00 + s._localTransform._rotation21 * g._inertiaCoeff10 + s._localTransform._rotation22 * g._inertiaCoeff20; + __tmp__21 = s._localTransform._rotation20 * g._inertiaCoeff01 + s._localTransform._rotation21 * g._inertiaCoeff11 + s._localTransform._rotation22 * g._inertiaCoeff21; + __tmp__22 = s._localTransform._rotation20 * g._inertiaCoeff02 + s._localTransform._rotation21 * g._inertiaCoeff12 + s._localTransform._rotation22 * g._inertiaCoeff22; + inertia00 = __tmp__00; + inertia01 = __tmp__01; + inertia02 = __tmp__02; + inertia10 = __tmp__10; + inertia11 = __tmp__11; + inertia12 = __tmp__12; + inertia20 = __tmp__20; + inertia21 = __tmp__21; + inertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = inertia00 * s._localTransform._rotation00 + inertia01 * s._localTransform._rotation01 + inertia02 * s._localTransform._rotation02; + __tmp__011 = inertia00 * s._localTransform._rotation10 + inertia01 * s._localTransform._rotation11 + inertia02 * s._localTransform._rotation12; + __tmp__021 = inertia00 * s._localTransform._rotation20 + inertia01 * s._localTransform._rotation21 + inertia02 * s._localTransform._rotation22; + __tmp__101 = inertia10 * s._localTransform._rotation00 + inertia11 * s._localTransform._rotation01 + inertia12 * s._localTransform._rotation02; + __tmp__111 = inertia10 * s._localTransform._rotation10 + inertia11 * s._localTransform._rotation11 + inertia12 * s._localTransform._rotation12; + __tmp__121 = inertia10 * s._localTransform._rotation20 + inertia11 * s._localTransform._rotation21 + inertia12 * s._localTransform._rotation22; + __tmp__201 = inertia20 * s._localTransform._rotation00 + inertia21 * s._localTransform._rotation01 + inertia22 * s._localTransform._rotation02; + __tmp__211 = inertia20 * s._localTransform._rotation10 + inertia21 * s._localTransform._rotation11 + inertia22 * s._localTransform._rotation12; + __tmp__221 = inertia20 * s._localTransform._rotation20 + inertia21 * s._localTransform._rotation21 + inertia22 * s._localTransform._rotation22; + inertia00 = __tmp__001; + inertia01 = __tmp__011; + inertia02 = __tmp__021; + inertia10 = __tmp__101; + inertia11 = __tmp__111; + inertia12 = __tmp__121; + inertia20 = __tmp__201; + inertia21 = __tmp__211; + inertia22 = __tmp__221; + inertia00 *= mass; + inertia01 *= mass; + inertia02 *= mass; + inertia10 *= mass; + inertia11 *= mass; + inertia12 *= mass; + inertia20 *= mass; + inertia21 *= mass; + inertia22 *= mass; + let cogInertia00; + let cogInertia01; + let cogInertia02; + let cogInertia10; + let cogInertia11; + let cogInertia12; + let cogInertia20; + let cogInertia21; + let cogInertia22; + let xx = s._localTransform._positionX * s._localTransform._positionX; + let yy = s._localTransform._positionY * s._localTransform._positionY; + let zz = s._localTransform._positionZ * s._localTransform._positionZ; + let xy = -s._localTransform._positionX * s._localTransform._positionY; + let yz = -s._localTransform._positionY * s._localTransform._positionZ; + let zx = -s._localTransform._positionZ * s._localTransform._positionX; + cogInertia00 = yy + zz; + cogInertia01 = xy; + cogInertia02 = zx; + cogInertia10 = xy; + cogInertia11 = xx + zz; + cogInertia12 = yz; + cogInertia20 = zx; + cogInertia21 = yz; + cogInertia22 = xx + yy; + inertia00 += cogInertia00 * mass; + inertia01 += cogInertia01 * mass; + inertia02 += cogInertia02 * mass; + inertia10 += cogInertia10 * mass; + inertia11 += cogInertia11 * mass; + inertia12 += cogInertia12 * mass; + inertia20 += cogInertia20 * mass; + inertia21 += cogInertia21 * mass; + inertia22 += cogInertia22 * mass; + totalMass += mass; + totalInertia00 += inertia00; + totalInertia01 += inertia01; + totalInertia02 += inertia02; + totalInertia10 += inertia10; + totalInertia11 += inertia11; + totalInertia12 += inertia12; + totalInertia20 += inertia20; + totalInertia21 += inertia21; + totalInertia22 += inertia22; + s = n; + } + this._mass = totalMass; + this._localInertia00 = totalInertia00; + this._localInertia01 = totalInertia01; + this._localInertia02 = totalInertia02; + this._localInertia10 = totalInertia10; + this._localInertia11 = totalInertia11; + this._localInertia12 = totalInertia12; + this._localInertia20 = totalInertia20; + this._localInertia21 = totalInertia21; + this._localInertia22 = totalInertia22; + if(this._mass > 0 && this._localInertia00 * (this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21) - this._localInertia01 * (this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20) + this._localInertia02 * (this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20) > 0 && this._type == 0) { + this._invMass = 1 / this._mass; + let d00 = this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21; + let d01 = this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20; + let d02 = this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20; + let d = this._localInertia00 * d00 - this._localInertia01 * d01 + this._localInertia02 * d02; + if(d < -1e-32 || d > 1e-32) { + d = 1 / d; + } + this._invLocalInertia00 = d00 * d; + this._invLocalInertia01 = -(this._localInertia01 * this._localInertia22 - this._localInertia02 * this._localInertia21) * d; + this._invLocalInertia02 = (this._localInertia01 * this._localInertia12 - this._localInertia02 * this._localInertia11) * d; + this._invLocalInertia10 = -d01 * d; + this._invLocalInertia11 = (this._localInertia00 * this._localInertia22 - this._localInertia02 * this._localInertia20) * d; + this._invLocalInertia12 = -(this._localInertia00 * this._localInertia12 - this._localInertia02 * this._localInertia10) * d; + this._invLocalInertia20 = d02 * d; + this._invLocalInertia21 = -(this._localInertia00 * this._localInertia21 - this._localInertia01 * this._localInertia20) * d; + this._invLocalInertia22 = (this._localInertia00 * this._localInertia11 - this._localInertia01 * this._localInertia10) * d; + this._invLocalInertiaWithoutRotFactor00 = this._invLocalInertia00; + this._invLocalInertiaWithoutRotFactor01 = this._invLocalInertia01; + this._invLocalInertiaWithoutRotFactor02 = this._invLocalInertia02; + this._invLocalInertiaWithoutRotFactor10 = this._invLocalInertia10; + this._invLocalInertiaWithoutRotFactor11 = this._invLocalInertia11; + this._invLocalInertiaWithoutRotFactor12 = this._invLocalInertia12; + this._invLocalInertiaWithoutRotFactor20 = this._invLocalInertia20; + this._invLocalInertiaWithoutRotFactor21 = this._invLocalInertia21; + this._invLocalInertiaWithoutRotFactor22 = this._invLocalInertia22; + this._invLocalInertia00 = this._invLocalInertiaWithoutRotFactor00 * this._rotFactor.x; + this._invLocalInertia01 = this._invLocalInertiaWithoutRotFactor01 * this._rotFactor.x; + this._invLocalInertia02 = this._invLocalInertiaWithoutRotFactor02 * this._rotFactor.x; + this._invLocalInertia10 = this._invLocalInertiaWithoutRotFactor10 * this._rotFactor.y; + this._invLocalInertia11 = this._invLocalInertiaWithoutRotFactor11 * this._rotFactor.y; + this._invLocalInertia12 = this._invLocalInertiaWithoutRotFactor12 * this._rotFactor.y; + this._invLocalInertia20 = this._invLocalInertiaWithoutRotFactor20 * this._rotFactor.z; + this._invLocalInertia21 = this._invLocalInertiaWithoutRotFactor21 * this._rotFactor.z; + this._invLocalInertia22 = this._invLocalInertiaWithoutRotFactor22 * this._rotFactor.z; + } else { + this._invMass = 0; + this._invLocalInertia00 = 0; + this._invLocalInertia01 = 0; + this._invLocalInertia02 = 0; + this._invLocalInertia10 = 0; + this._invLocalInertia11 = 0; + this._invLocalInertia12 = 0; + this._invLocalInertia20 = 0; + this._invLocalInertia21 = 0; + this._invLocalInertia22 = 0; + this._invLocalInertiaWithoutRotFactor00 = 0; + this._invLocalInertiaWithoutRotFactor01 = 0; + this._invLocalInertiaWithoutRotFactor02 = 0; + this._invLocalInertiaWithoutRotFactor10 = 0; + this._invLocalInertiaWithoutRotFactor11 = 0; + this._invLocalInertiaWithoutRotFactor12 = 0; + this._invLocalInertiaWithoutRotFactor20 = 0; + this._invLocalInertiaWithoutRotFactor21 = 0; + this._invLocalInertiaWithoutRotFactor22 = 0; + if(this._type == 0) { + this._type = 1; + } + } + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + this._sleeping = false; + this._sleepTime = 0; + } + getPosition() { + let v = new oimo.common.Vec3(); + v.x = this._transform._positionX; + v.y = this._transform._positionY; + v.z = this._transform._positionZ; + return v; + } + getPositionTo(position) { + position.x = this._transform._positionX; + position.y = this._transform._positionY; + position.z = this._transform._positionZ; + } + setPosition(position) { + this._transform._positionX = position.x; + this._transform._positionY = position.y; + this._transform._positionZ = position.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + translate(translation) { + let diffX; + let diffY; + let diffZ; + diffX = translation.x; + diffY = translation.y; + diffZ = translation.z; + this._transform._positionX += diffX; + this._transform._positionY += diffY; + this._transform._positionZ += diffZ; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getRotation() { + let m = new oimo.common.Mat3(); + m.e00 = this._transform._rotation00; + m.e01 = this._transform._rotation01; + m.e02 = this._transform._rotation02; + m.e10 = this._transform._rotation10; + m.e11 = this._transform._rotation11; + m.e12 = this._transform._rotation12; + m.e20 = this._transform._rotation20; + m.e21 = this._transform._rotation21; + m.e22 = this._transform._rotation22; + return m; + } + getRotationTo(rotation) { + rotation.e00 = this._transform._rotation00; + rotation.e01 = this._transform._rotation01; + rotation.e02 = this._transform._rotation02; + rotation.e10 = this._transform._rotation10; + rotation.e11 = this._transform._rotation11; + rotation.e12 = this._transform._rotation12; + rotation.e20 = this._transform._rotation20; + rotation.e21 = this._transform._rotation21; + rotation.e22 = this._transform._rotation22; + } + setRotation(rotation) { + this._transform._rotation00 = rotation.e00; + this._transform._rotation01 = rotation.e01; + this._transform._rotation02 = rotation.e02; + this._transform._rotation10 = rotation.e10; + this._transform._rotation11 = rotation.e11; + this._transform._rotation12 = rotation.e12; + this._transform._rotation20 = rotation.e20; + this._transform._rotation21 = rotation.e21; + this._transform._rotation22 = rotation.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + setRotationXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + this._transform._rotation00 = cy * cz; + this._transform._rotation01 = -cy * sz; + this._transform._rotation02 = sy; + this._transform._rotation10 = cx * sz + cz * sx * sy; + this._transform._rotation11 = cx * cz - sx * sy * sz; + this._transform._rotation12 = -cy * sx; + this._transform._rotation20 = sx * sz - cx * cz * sy; + this._transform._rotation21 = cz * sx + cx * sy * sz; + this._transform._rotation22 = cx * cy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + rotate(rotation) { + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + rot00 = rotation.e00; + rot01 = rotation.e01; + rot02 = rotation.e02; + rot10 = rotation.e10; + rot11 = rotation.e11; + rot12 = rotation.e12; + rot20 = rotation.e20; + rot21 = rotation.e21; + rot22 = rotation.e22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._transform._rotation00 + rot01 * this._transform._rotation10 + rot02 * this._transform._rotation20; + __tmp__01 = rot00 * this._transform._rotation01 + rot01 * this._transform._rotation11 + rot02 * this._transform._rotation21; + __tmp__02 = rot00 * this._transform._rotation02 + rot01 * this._transform._rotation12 + rot02 * this._transform._rotation22; + __tmp__10 = rot10 * this._transform._rotation00 + rot11 * this._transform._rotation10 + rot12 * this._transform._rotation20; + __tmp__11 = rot10 * this._transform._rotation01 + rot11 * this._transform._rotation11 + rot12 * this._transform._rotation21; + __tmp__12 = rot10 * this._transform._rotation02 + rot11 * this._transform._rotation12 + rot12 * this._transform._rotation22; + __tmp__20 = rot20 * this._transform._rotation00 + rot21 * this._transform._rotation10 + rot22 * this._transform._rotation20; + __tmp__21 = rot20 * this._transform._rotation01 + rot21 * this._transform._rotation11 + rot22 * this._transform._rotation21; + __tmp__22 = rot20 * this._transform._rotation02 + rot21 * this._transform._rotation12 + rot22 * this._transform._rotation22; + this._transform._rotation00 = __tmp__00; + this._transform._rotation01 = __tmp__01; + this._transform._rotation02 = __tmp__02; + this._transform._rotation10 = __tmp__10; + this._transform._rotation11 = __tmp__11; + this._transform._rotation12 = __tmp__12; + this._transform._rotation20 = __tmp__20; + this._transform._rotation21 = __tmp__21; + this._transform._rotation22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__011 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__021 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__101 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__111 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__121 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__201 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__211 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__221 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__012 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__022 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__102 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__112 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__122 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__202 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__212 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__222 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__002; + this._invInertia01 = __tmp__012; + this._invInertia02 = __tmp__022; + this._invInertia10 = __tmp__102; + this._invInertia11 = __tmp__112; + this._invInertia12 = __tmp__122; + this._invInertia20 = __tmp__202; + this._invInertia21 = __tmp__212; + this._invInertia22 = __tmp__222; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + rotateXyz(eulerAngles) { + let xyzX; + let xyzY; + let xyzZ; + let rot00; + let rot01; + let rot02; + let rot10; + let rot11; + let rot12; + let rot20; + let rot21; + let rot22; + xyzX = eulerAngles.x; + xyzY = eulerAngles.y; + xyzZ = eulerAngles.z; + let sx = Math.sin(xyzX); + let sy = Math.sin(xyzY); + let sz = Math.sin(xyzZ); + let cx = Math.cos(xyzX); + let cy = Math.cos(xyzY); + let cz = Math.cos(xyzZ); + rot00 = cy * cz; + rot01 = -cy * sz; + rot02 = sy; + rot10 = cx * sz + cz * sx * sy; + rot11 = cx * cz - sx * sy * sz; + rot12 = -cy * sx; + rot20 = sx * sz - cx * cz * sy; + rot21 = cz * sx + cx * sy * sz; + rot22 = cx * cy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = rot00 * this._transform._rotation00 + rot01 * this._transform._rotation10 + rot02 * this._transform._rotation20; + __tmp__01 = rot00 * this._transform._rotation01 + rot01 * this._transform._rotation11 + rot02 * this._transform._rotation21; + __tmp__02 = rot00 * this._transform._rotation02 + rot01 * this._transform._rotation12 + rot02 * this._transform._rotation22; + __tmp__10 = rot10 * this._transform._rotation00 + rot11 * this._transform._rotation10 + rot12 * this._transform._rotation20; + __tmp__11 = rot10 * this._transform._rotation01 + rot11 * this._transform._rotation11 + rot12 * this._transform._rotation21; + __tmp__12 = rot10 * this._transform._rotation02 + rot11 * this._transform._rotation12 + rot12 * this._transform._rotation22; + __tmp__20 = rot20 * this._transform._rotation00 + rot21 * this._transform._rotation10 + rot22 * this._transform._rotation20; + __tmp__21 = rot20 * this._transform._rotation01 + rot21 * this._transform._rotation11 + rot22 * this._transform._rotation21; + __tmp__22 = rot20 * this._transform._rotation02 + rot21 * this._transform._rotation12 + rot22 * this._transform._rotation22; + this._transform._rotation00 = __tmp__00; + this._transform._rotation01 = __tmp__01; + this._transform._rotation02 = __tmp__02; + this._transform._rotation10 = __tmp__10; + this._transform._rotation11 = __tmp__11; + this._transform._rotation12 = __tmp__12; + this._transform._rotation20 = __tmp__20; + this._transform._rotation21 = __tmp__21; + this._transform._rotation22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__011 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__021 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__101 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__111 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__121 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__201 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__211 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__221 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + let __tmp__002; + let __tmp__012; + let __tmp__022; + let __tmp__102; + let __tmp__112; + let __tmp__122; + let __tmp__202; + let __tmp__212; + let __tmp__222; + __tmp__002 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__012 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__022 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__102 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__112 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__122 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__202 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__212 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__222 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__002; + this._invInertia01 = __tmp__012; + this._invInertia02 = __tmp__022; + this._invInertia10 = __tmp__102; + this._invInertia11 = __tmp__112; + this._invInertia12 = __tmp__122; + this._invInertia20 = __tmp__202; + this._invInertia21 = __tmp__212; + this._invInertia22 = __tmp__222; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getOrientation() { + let q = new oimo.common.Quat(); + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation21 - this._transform._rotation12) * s; + iqY = (this._transform._rotation02 - this._transform._rotation20) * s; + iqZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation02 + this._transform._rotation20) * s; + iqW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + q.x = iqX; + q.y = iqY; + q.z = iqZ; + q.w = iqW; + return q; + } + getOrientationTo(orientation) { + let iqX; + let iqY; + let iqZ; + let iqW; + let e00 = this._transform._rotation00; + let e11 = this._transform._rotation11; + let e22 = this._transform._rotation22; + let t = e00 + e11 + e22; + let s; + if(t > 0) { + s = Math.sqrt(t + 1); + iqW = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation21 - this._transform._rotation12) * s; + iqY = (this._transform._rotation02 - this._transform._rotation20) * s; + iqZ = (this._transform._rotation10 - this._transform._rotation01) * s; + } else if(e00 > e11) { + if(e00 > e22) { + s = Math.sqrt(e00 - e11 - e22 + 1); + iqX = 0.5 * s; + s = 0.5 / s; + iqY = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation02 + this._transform._rotation20) * s; + iqW = (this._transform._rotation21 - this._transform._rotation12) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + } else if(e11 > e22) { + s = Math.sqrt(e11 - e22 - e00 + 1); + iqY = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation01 + this._transform._rotation10) * s; + iqZ = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation02 - this._transform._rotation20) * s; + } else { + s = Math.sqrt(e22 - e00 - e11 + 1); + iqZ = 0.5 * s; + s = 0.5 / s; + iqX = (this._transform._rotation02 + this._transform._rotation20) * s; + iqY = (this._transform._rotation12 + this._transform._rotation21) * s; + iqW = (this._transform._rotation10 - this._transform._rotation01) * s; + } + orientation.x = iqX; + orientation.y = iqY; + orientation.z = iqZ; + orientation.w = iqW; + } + setOrientation(quaternion) { + let qX; + let qY; + let qZ; + let qW; + qX = quaternion.x; + qY = quaternion.y; + qZ = quaternion.z; + qW = quaternion.w; + let x = qX; + let y = qY; + let z = qZ; + let w = qW; + let x2 = 2 * x; + let y2 = 2 * y; + let z2 = 2 * z; + let xx = x * x2; + let yy = y * y2; + let zz = z * z2; + let xy = x * y2; + let yz = y * z2; + let xz = x * z2; + let wx = w * x2; + let wy = w * y2; + let wz = w * z2; + this._transform._rotation00 = 1 - yy - zz; + this._transform._rotation01 = xy - wz; + this._transform._rotation02 = xz + wy; + this._transform._rotation10 = xy + wz; + this._transform._rotation11 = 1 - xx - zz; + this._transform._rotation12 = yz - wx; + this._transform._rotation20 = xz - wy; + this._transform._rotation21 = yz + wx; + this._transform._rotation22 = 1 - xx - yy; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getTransform() { + let _this = this._transform; + let tf = new oimo.common.Transform(); + tf._positionX = _this._positionX; + tf._positionY = _this._positionY; + tf._positionZ = _this._positionZ; + tf._rotation00 = _this._rotation00; + tf._rotation01 = _this._rotation01; + tf._rotation02 = _this._rotation02; + tf._rotation10 = _this._rotation10; + tf._rotation11 = _this._rotation11; + tf._rotation12 = _this._rotation12; + tf._rotation20 = _this._rotation20; + tf._rotation21 = _this._rotation21; + tf._rotation22 = _this._rotation22; + return tf; + } + getTransformTo(transform) { + let transform1 = this._transform; + transform._positionX = transform1._positionX; + transform._positionY = transform1._positionY; + transform._positionZ = transform1._positionZ; + transform._rotation00 = transform1._rotation00; + transform._rotation01 = transform1._rotation01; + transform._rotation02 = transform1._rotation02; + transform._rotation10 = transform1._rotation10; + transform._rotation11 = transform1._rotation11; + transform._rotation12 = transform1._rotation12; + transform._rotation20 = transform1._rotation20; + transform._rotation21 = transform1._rotation21; + transform._rotation22 = transform1._rotation22; + } + setTransform(transform) { + this._transform._positionX = transform._positionX; + this._transform._positionY = transform._positionY; + this._transform._positionZ = transform._positionZ; + this._transform._rotation00 = transform._rotation00; + this._transform._rotation01 = transform._rotation01; + this._transform._rotation02 = transform._rotation02; + this._transform._rotation10 = transform._rotation10; + this._transform._rotation11 = transform._rotation11; + this._transform._rotation12 = transform._rotation12; + this._transform._rotation20 = transform._rotation20; + this._transform._rotation21 = transform._rotation21; + this._transform._rotation22 = transform._rotation22; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + let dst = this._ptransform; + let src = this._transform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + this._sleeping = false; + this._sleepTime = 0; + } + getMass() { + return this._mass; + } + getLocalInertia() { + let m = new oimo.common.Mat3(); + m.e00 = this._localInertia00; + m.e01 = this._localInertia01; + m.e02 = this._localInertia02; + m.e10 = this._localInertia10; + m.e11 = this._localInertia11; + m.e12 = this._localInertia12; + m.e20 = this._localInertia20; + m.e21 = this._localInertia21; + m.e22 = this._localInertia22; + return m; + } + getLocalInertiaTo(inertia) { + inertia.e00 = this._localInertia00; + inertia.e01 = this._localInertia01; + inertia.e02 = this._localInertia02; + inertia.e10 = this._localInertia10; + inertia.e11 = this._localInertia11; + inertia.e12 = this._localInertia12; + inertia.e20 = this._localInertia20; + inertia.e21 = this._localInertia21; + inertia.e22 = this._localInertia22; + } + getMassData() { + let md = new oimo.dynamics.rigidbody.MassData(); + md.mass = this._mass; + let m = md.localInertia; + m.e00 = this._localInertia00; + m.e01 = this._localInertia01; + m.e02 = this._localInertia02; + m.e10 = this._localInertia10; + m.e11 = this._localInertia11; + m.e12 = this._localInertia12; + m.e20 = this._localInertia20; + m.e21 = this._localInertia21; + m.e22 = this._localInertia22; + return md; + } + getMassDataTo(massData) { + massData.mass = this._mass; + let m = massData.localInertia; + m.e00 = this._localInertia00; + m.e01 = this._localInertia01; + m.e02 = this._localInertia02; + m.e10 = this._localInertia10; + m.e11 = this._localInertia11; + m.e12 = this._localInertia12; + m.e20 = this._localInertia20; + m.e21 = this._localInertia21; + m.e22 = this._localInertia22; + } + setMassData(massData) { + this._mass = massData.mass; + let m = massData.localInertia; + this._localInertia00 = m.e00; + this._localInertia01 = m.e01; + this._localInertia02 = m.e02; + this._localInertia10 = m.e10; + this._localInertia11 = m.e11; + this._localInertia12 = m.e12; + this._localInertia20 = m.e20; + this._localInertia21 = m.e21; + this._localInertia22 = m.e22; + if(this._mass > 0 && this._localInertia00 * (this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21) - this._localInertia01 * (this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20) + this._localInertia02 * (this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20) > 0 && this._type == 0) { + this._invMass = 1 / this._mass; + let d00 = this._localInertia11 * this._localInertia22 - this._localInertia12 * this._localInertia21; + let d01 = this._localInertia10 * this._localInertia22 - this._localInertia12 * this._localInertia20; + let d02 = this._localInertia10 * this._localInertia21 - this._localInertia11 * this._localInertia20; + let d = this._localInertia00 * d00 - this._localInertia01 * d01 + this._localInertia02 * d02; + if(d < -1e-32 || d > 1e-32) { + d = 1 / d; + } + this._invLocalInertia00 = d00 * d; + this._invLocalInertia01 = -(this._localInertia01 * this._localInertia22 - this._localInertia02 * this._localInertia21) * d; + this._invLocalInertia02 = (this._localInertia01 * this._localInertia12 - this._localInertia02 * this._localInertia11) * d; + this._invLocalInertia10 = -d01 * d; + this._invLocalInertia11 = (this._localInertia00 * this._localInertia22 - this._localInertia02 * this._localInertia20) * d; + this._invLocalInertia12 = -(this._localInertia00 * this._localInertia12 - this._localInertia02 * this._localInertia10) * d; + this._invLocalInertia20 = d02 * d; + this._invLocalInertia21 = -(this._localInertia00 * this._localInertia21 - this._localInertia01 * this._localInertia20) * d; + this._invLocalInertia22 = (this._localInertia00 * this._localInertia11 - this._localInertia01 * this._localInertia10) * d; + this._invLocalInertiaWithoutRotFactor00 = this._invLocalInertia00; + this._invLocalInertiaWithoutRotFactor01 = this._invLocalInertia01; + this._invLocalInertiaWithoutRotFactor02 = this._invLocalInertia02; + this._invLocalInertiaWithoutRotFactor10 = this._invLocalInertia10; + this._invLocalInertiaWithoutRotFactor11 = this._invLocalInertia11; + this._invLocalInertiaWithoutRotFactor12 = this._invLocalInertia12; + this._invLocalInertiaWithoutRotFactor20 = this._invLocalInertia20; + this._invLocalInertiaWithoutRotFactor21 = this._invLocalInertia21; + this._invLocalInertiaWithoutRotFactor22 = this._invLocalInertia22; + this._invLocalInertia00 = this._invLocalInertiaWithoutRotFactor00 * this._rotFactor.x; + this._invLocalInertia01 = this._invLocalInertiaWithoutRotFactor01 * this._rotFactor.x; + this._invLocalInertia02 = this._invLocalInertiaWithoutRotFactor02 * this._rotFactor.x; + this._invLocalInertia10 = this._invLocalInertiaWithoutRotFactor10 * this._rotFactor.y; + this._invLocalInertia11 = this._invLocalInertiaWithoutRotFactor11 * this._rotFactor.y; + this._invLocalInertia12 = this._invLocalInertiaWithoutRotFactor12 * this._rotFactor.y; + this._invLocalInertia20 = this._invLocalInertiaWithoutRotFactor20 * this._rotFactor.z; + this._invLocalInertia21 = this._invLocalInertiaWithoutRotFactor21 * this._rotFactor.z; + this._invLocalInertia22 = this._invLocalInertiaWithoutRotFactor22 * this._rotFactor.z; + } else { + this._invMass = 0; + this._invLocalInertia00 = 0; + this._invLocalInertia01 = 0; + this._invLocalInertia02 = 0; + this._invLocalInertia10 = 0; + this._invLocalInertia11 = 0; + this._invLocalInertia12 = 0; + this._invLocalInertia20 = 0; + this._invLocalInertia21 = 0; + this._invLocalInertia22 = 0; + this._invLocalInertiaWithoutRotFactor00 = 0; + this._invLocalInertiaWithoutRotFactor01 = 0; + this._invLocalInertiaWithoutRotFactor02 = 0; + this._invLocalInertiaWithoutRotFactor10 = 0; + this._invLocalInertiaWithoutRotFactor11 = 0; + this._invLocalInertiaWithoutRotFactor12 = 0; + this._invLocalInertiaWithoutRotFactor20 = 0; + this._invLocalInertiaWithoutRotFactor21 = 0; + this._invLocalInertiaWithoutRotFactor22 = 0; + if(this._type == 0) { + this._type = 1; + } + } + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + this._sleeping = false; + this._sleepTime = 0; + } + getRotationFactor() { + let _this = this._rotFactor; + return new oimo.common.Vec3(_this.x,_this.y,_this.z); + } + setRotationFactor(rotationFactor) { + let _this = this._rotFactor; + _this.x = rotationFactor.x; + _this.y = rotationFactor.y; + _this.z = rotationFactor.z; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = this._transform._rotation00 * this._invLocalInertia00 + this._transform._rotation01 * this._invLocalInertia10 + this._transform._rotation02 * this._invLocalInertia20; + __tmp__01 = this._transform._rotation00 * this._invLocalInertia01 + this._transform._rotation01 * this._invLocalInertia11 + this._transform._rotation02 * this._invLocalInertia21; + __tmp__02 = this._transform._rotation00 * this._invLocalInertia02 + this._transform._rotation01 * this._invLocalInertia12 + this._transform._rotation02 * this._invLocalInertia22; + __tmp__10 = this._transform._rotation10 * this._invLocalInertia00 + this._transform._rotation11 * this._invLocalInertia10 + this._transform._rotation12 * this._invLocalInertia20; + __tmp__11 = this._transform._rotation10 * this._invLocalInertia01 + this._transform._rotation11 * this._invLocalInertia11 + this._transform._rotation12 * this._invLocalInertia21; + __tmp__12 = this._transform._rotation10 * this._invLocalInertia02 + this._transform._rotation11 * this._invLocalInertia12 + this._transform._rotation12 * this._invLocalInertia22; + __tmp__20 = this._transform._rotation20 * this._invLocalInertia00 + this._transform._rotation21 * this._invLocalInertia10 + this._transform._rotation22 * this._invLocalInertia20; + __tmp__21 = this._transform._rotation20 * this._invLocalInertia01 + this._transform._rotation21 * this._invLocalInertia11 + this._transform._rotation22 * this._invLocalInertia21; + __tmp__22 = this._transform._rotation20 * this._invLocalInertia02 + this._transform._rotation21 * this._invLocalInertia12 + this._transform._rotation22 * this._invLocalInertia22; + this._invInertia00 = __tmp__00; + this._invInertia01 = __tmp__01; + this._invInertia02 = __tmp__02; + this._invInertia10 = __tmp__10; + this._invInertia11 = __tmp__11; + this._invInertia12 = __tmp__12; + this._invInertia20 = __tmp__20; + this._invInertia21 = __tmp__21; + this._invInertia22 = __tmp__22; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = this._invInertia00 * this._transform._rotation00 + this._invInertia01 * this._transform._rotation01 + this._invInertia02 * this._transform._rotation02; + __tmp__011 = this._invInertia00 * this._transform._rotation10 + this._invInertia01 * this._transform._rotation11 + this._invInertia02 * this._transform._rotation12; + __tmp__021 = this._invInertia00 * this._transform._rotation20 + this._invInertia01 * this._transform._rotation21 + this._invInertia02 * this._transform._rotation22; + __tmp__101 = this._invInertia10 * this._transform._rotation00 + this._invInertia11 * this._transform._rotation01 + this._invInertia12 * this._transform._rotation02; + __tmp__111 = this._invInertia10 * this._transform._rotation10 + this._invInertia11 * this._transform._rotation11 + this._invInertia12 * this._transform._rotation12; + __tmp__121 = this._invInertia10 * this._transform._rotation20 + this._invInertia11 * this._transform._rotation21 + this._invInertia12 * this._transform._rotation22; + __tmp__201 = this._invInertia20 * this._transform._rotation00 + this._invInertia21 * this._transform._rotation01 + this._invInertia22 * this._transform._rotation02; + __tmp__211 = this._invInertia20 * this._transform._rotation10 + this._invInertia21 * this._transform._rotation11 + this._invInertia22 * this._transform._rotation12; + __tmp__221 = this._invInertia20 * this._transform._rotation20 + this._invInertia21 * this._transform._rotation21 + this._invInertia22 * this._transform._rotation22; + this._invInertia00 = __tmp__001; + this._invInertia01 = __tmp__011; + this._invInertia02 = __tmp__021; + this._invInertia10 = __tmp__101; + this._invInertia11 = __tmp__111; + this._invInertia12 = __tmp__121; + this._invInertia20 = __tmp__201; + this._invInertia21 = __tmp__211; + this._invInertia22 = __tmp__221; + this._invInertia00 *= this._rotFactor.x; + this._invInertia01 *= this._rotFactor.x; + this._invInertia02 *= this._rotFactor.x; + this._invInertia10 *= this._rotFactor.y; + this._invInertia11 *= this._rotFactor.y; + this._invInertia12 *= this._rotFactor.y; + this._invInertia20 *= this._rotFactor.z; + this._invInertia21 *= this._rotFactor.z; + this._invInertia22 *= this._rotFactor.z; + this._sleeping = false; + this._sleepTime = 0; + } + getLinearVelocity() { + let v = new oimo.common.Vec3(); + v.x = this._velX; + v.y = this._velY; + v.z = this._velZ; + return v; + } + getLinearVelocityTo(linearVelocity) { + linearVelocity.x = this._velX; + linearVelocity.y = this._velY; + linearVelocity.z = this._velZ; + } + setLinearVelocity(linearVelocity) { + if(this._type == 1) { + this._velX = 0; + this._velY = 0; + this._velZ = 0; + } else { + this._velX = linearVelocity.x; + this._velY = linearVelocity.y; + this._velZ = linearVelocity.z; + } + this._sleeping = false; + this._sleepTime = 0; + } + getAngularVelocity() { + let v = new oimo.common.Vec3(); + v.x = this._angVelX; + v.y = this._angVelY; + v.z = this._angVelZ; + return v; + } + getAngularVelocityTo(angularVelocity) { + angularVelocity.x = this._velX; + angularVelocity.y = this._velY; + angularVelocity.z = this._velZ; + } + setAngularVelocity(angularVelocity) { + if(this._type == 1) { + this._angVelX = 0; + this._angVelY = 0; + this._angVelZ = 0; + } else { + this._angVelX = angularVelocity.x; + this._angVelY = angularVelocity.y; + this._angVelZ = angularVelocity.z; + } + this._sleeping = false; + this._sleepTime = 0; + } + addLinearVelocity(linearVelocityChange) { + if(this._type != 1) { + let dX; + let dY; + let dZ; + dX = linearVelocityChange.x; + dY = linearVelocityChange.y; + dZ = linearVelocityChange.z; + this._velX += dX; + this._velY += dY; + this._velZ += dZ; + } + this._sleeping = false; + this._sleepTime = 0; + } + addAngularVelocity(angularVelocityChange) { + if(this._type != 1) { + let dX; + let dY; + let dZ; + dX = angularVelocityChange.x; + dY = angularVelocityChange.y; + dZ = angularVelocityChange.z; + this._angVelX += dX; + this._angVelY += dY; + this._angVelZ += dZ; + } + this._sleeping = false; + this._sleepTime = 0; + } + applyImpulse(impulse,positionInWorld) { + let impX; + let impY; + let impZ; + impX = impulse.x; + impY = impulse.y; + impZ = impulse.z; + this._velX += impX * this._invMass; + this._velY += impY * this._invMass; + this._velZ += impZ * this._invMass; + let aimpX; + let aimpY; + let aimpZ; + let posX; + let posY; + let posZ; + posX = positionInWorld.x; + posY = positionInWorld.y; + posZ = positionInWorld.z; + posX -= this._transform._positionX; + posY -= this._transform._positionY; + posZ -= this._transform._positionZ; + aimpX = posY * impZ - posZ * impY; + aimpY = posZ * impX - posX * impZ; + aimpZ = posX * impY - posY * impX; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._invInertia00 * aimpX + this._invInertia01 * aimpY + this._invInertia02 * aimpZ; + __tmp__Y = this._invInertia10 * aimpX + this._invInertia11 * aimpY + this._invInertia12 * aimpZ; + __tmp__Z = this._invInertia20 * aimpX + this._invInertia21 * aimpY + this._invInertia22 * aimpZ; + aimpX = __tmp__X; + aimpY = __tmp__Y; + aimpZ = __tmp__Z; + this._angVelX += aimpX; + this._angVelY += aimpY; + this._angVelZ += aimpZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyLinearImpulse(impulse) { + let impX; + let impY; + let impZ; + impX = impulse.x; + impY = impulse.y; + impZ = impulse.z; + this._velX += impX * this._invMass; + this._velY += impY * this._invMass; + this._velZ += impZ * this._invMass; + this._sleeping = false; + this._sleepTime = 0; + } + applyAngularImpulse(impulse) { + let impX; + let impY; + let impZ; + impX = impulse.x; + impY = impulse.y; + impZ = impulse.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._invInertia00 * impX + this._invInertia01 * impY + this._invInertia02 * impZ; + __tmp__Y = this._invInertia10 * impX + this._invInertia11 * impY + this._invInertia12 * impZ; + __tmp__Z = this._invInertia20 * impX + this._invInertia21 * impY + this._invInertia22 * impZ; + impX = __tmp__X; + impY = __tmp__Y; + impZ = __tmp__Z; + this._angVelX += impX; + this._angVelY += impY; + this._angVelZ += impZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyForce(force,positionInWorld) { + let iforceX; + let iforceY; + let iforceZ; + iforceX = force.x; + iforceY = force.y; + iforceZ = force.z; + this._forceX += iforceX; + this._forceY += iforceY; + this._forceZ += iforceZ; + let itorqueX; + let itorqueY; + let itorqueZ; + let posX; + let posY; + let posZ; + posX = positionInWorld.x; + posY = positionInWorld.y; + posZ = positionInWorld.z; + posX -= this._transform._positionX; + posY -= this._transform._positionY; + posZ -= this._transform._positionZ; + itorqueX = posY * iforceZ - posZ * iforceY; + itorqueY = posZ * iforceX - posX * iforceZ; + itorqueZ = posX * iforceY - posY * iforceX; + this._torqueX += itorqueX; + this._torqueY += itorqueY; + this._torqueZ += itorqueZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyForceToCenter(force) { + let iforceX; + let iforceY; + let iforceZ; + iforceX = force.x; + iforceY = force.y; + iforceZ = force.z; + this._forceX += iforceX; + this._forceY += iforceY; + this._forceZ += iforceZ; + this._sleeping = false; + this._sleepTime = 0; + } + applyTorque(torque) { + let itorqueX; + let itorqueY; + let itorqueZ; + itorqueX = torque.x; + itorqueY = torque.y; + itorqueZ = torque.z; + this._torqueX += itorqueX; + this._torqueY += itorqueY; + this._torqueZ += itorqueZ; + this._sleeping = false; + this._sleepTime = 0; + } + getLinearContactImpulse() { + let res = new oimo.common.Vec3(); + res.x = this._linearContactImpulseX; + res.y = this._linearContactImpulseY; + res.z = this._linearContactImpulseZ; + return res; + } + getLinearContactImpulseTo(linearContactImpulse) { + linearContactImpulse.x = this._linearContactImpulseX; + linearContactImpulse.y = this._linearContactImpulseY; + linearContactImpulse.z = this._linearContactImpulseZ; + } + getAngularContactImpulse() { + let res = new oimo.common.Vec3(); + res.x = this._angularContactImpulseX; + res.y = this._angularContactImpulseY; + res.z = this._angularContactImpulseZ; + return res; + } + getAngularContactImpulseTo(angularContactImpulse) { + angularContactImpulse.x = this._angularContactImpulseX; + angularContactImpulse.y = this._angularContactImpulseY; + angularContactImpulse.z = this._angularContactImpulseZ; + } + getGravityScale() { + return this._gravityScale; + } + setGravityScale(gravityScale) { + this._gravityScale = gravityScale; + this._sleeping = false; + this._sleepTime = 0; + } + getLocalPoint(worldPoint) { + let vX; + let vY; + let vZ; + vX = worldPoint.x; + vY = worldPoint.y; + vZ = worldPoint.z; + vX -= this._transform._positionX; + vY -= this._transform._positionY; + vZ -= this._transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getLocalPointTo(worldPoint,localPoint) { + let vX; + let vY; + let vZ; + vX = worldPoint.x; + vY = worldPoint.y; + vZ = worldPoint.z; + vX -= this._transform._positionX; + vY -= this._transform._positionY; + vZ -= this._transform._positionZ; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localPoint.x = vX; + localPoint.y = vY; + localPoint.z = vZ; + } + getLocalVector(worldVector) { + let vX; + let vY; + let vZ; + vX = worldVector.x; + vY = worldVector.y; + vZ = worldVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getLocalVectorTo(worldVector,localVector) { + let vX; + let vY; + let vZ; + vX = worldVector.x; + vY = worldVector.y; + vZ = worldVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation10 * vY + this._transform._rotation20 * vZ; + __tmp__Y = this._transform._rotation01 * vX + this._transform._rotation11 * vY + this._transform._rotation21 * vZ; + __tmp__Z = this._transform._rotation02 * vX + this._transform._rotation12 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + localVector.x = vX; + localVector.y = vY; + localVector.z = vZ; + } + getWorldPoint(localPoint) { + let vX; + let vY; + let vZ; + vX = localPoint.x; + vY = localPoint.y; + vZ = localPoint.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += this._transform._positionX; + vY += this._transform._positionY; + vZ += this._transform._positionZ; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getWorldPointTo(localPoint,worldPoint) { + let vX; + let vY; + let vZ; + vX = localPoint.x; + vY = localPoint.y; + vZ = localPoint.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + vX += this._transform._positionX; + vY += this._transform._positionY; + vZ += this._transform._positionZ; + worldPoint.x = vX; + worldPoint.y = vY; + worldPoint.z = vZ; + } + getWorldVector(localVector) { + let vX; + let vY; + let vZ; + vX = localVector.x; + vY = localVector.y; + vZ = localVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + let res = new oimo.common.Vec3(); + res.x = vX; + res.y = vY; + res.z = vZ; + return res; + } + getWorldVectorTo(localVector,worldVector) { + let vX; + let vY; + let vZ; + vX = localVector.x; + vY = localVector.y; + vZ = localVector.z; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = this._transform._rotation00 * vX + this._transform._rotation01 * vY + this._transform._rotation02 * vZ; + __tmp__Y = this._transform._rotation10 * vX + this._transform._rotation11 * vY + this._transform._rotation12 * vZ; + __tmp__Z = this._transform._rotation20 * vX + this._transform._rotation21 * vY + this._transform._rotation22 * vZ; + vX = __tmp__X; + vY = __tmp__Y; + vZ = __tmp__Z; + worldVector.x = vX; + worldVector.y = vY; + worldVector.z = vZ; + } + getNumShapes() { + return this._numShapes; + } + getShapeList() { + return this._shapeList; + } + getNumContectLinks() { + return this._numContactLinks; + } + getContactLinkList() { + return this._contactLinkList; + } + getNumJointLinks() { + return this._numJointLinks; + } + getJointLinkList() { + return this._jointLinkList; + } + addShape(shape) { + if(this._shapeList == null) { + this._shapeList = shape; + this._shapeListLast = shape; + } else { + this._shapeListLast._next = shape; + shape._prev = this._shapeListLast; + this._shapeListLast = shape; + } + this._numShapes++; + shape._rigidBody = this; + if(this._world != null) { + let _this = this._world; + shape._proxy = _this._broadPhase.createProxy(shape,shape._aabb); + shape._id = _this._shapeIdCount++; + _this._numShapes++; + } + this.updateMass(); + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + removeShape(shape) { + let prev = shape._prev; + let next = shape._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(shape == this._shapeList) { + this._shapeList = this._shapeList._next; + } + if(shape == this._shapeListLast) { + this._shapeListLast = this._shapeListLast._prev; + } + shape._next = null; + shape._prev = null; + this._numShapes--; + shape._rigidBody = null; + if(this._world != null) { + let _this = this._world; + _this._broadPhase.destroyProxy(shape._proxy); + shape._proxy = null; + shape._id = -1; + let cl = shape._rigidBody._contactLinkList; + while(cl != null) { + let n = cl._next; + let c = cl._contact; + if(c._s1 == shape || c._s2 == shape) { + let _this1 = cl._other; + _this1._sleeping = false; + _this1._sleepTime = 0; + let _this2 = _this._contactManager; + let prev = c._prev; + let next = c._next; + if(prev != null) { + prev._next = next; + } + if(next != null) { + next._prev = prev; + } + if(c == _this2._contactList) { + _this2._contactList = _this2._contactList._next; + } + if(c == _this2._contactListLast) { + _this2._contactListLast = _this2._contactListLast._prev; + } + c._next = null; + c._prev = null; + if(c._touching) { + let cc1 = c._s1._contactCallback; + let cc2 = c._s2._contactCallback; + if(cc1 == cc2) { + cc2 = null; + } + if(cc1 != null) { + cc1.endContact(c); + } + if(cc2 != null) { + cc2.endContact(c); + } + } + let prev1 = c._link1._prev; + let next1 = c._link1._next; + if(prev1 != null) { + prev1._next = next1; + } + if(next1 != null) { + next1._prev = prev1; + } + if(c._link1 == c._b1._contactLinkList) { + c._b1._contactLinkList = c._b1._contactLinkList._next; + } + if(c._link1 == c._b1._contactLinkListLast) { + c._b1._contactLinkListLast = c._b1._contactLinkListLast._prev; + } + c._link1._next = null; + c._link1._prev = null; + let prev2 = c._link2._prev; + let next2 = c._link2._next; + if(prev2 != null) { + prev2._next = next2; + } + if(next2 != null) { + next2._prev = prev2; + } + if(c._link2 == c._b2._contactLinkList) { + c._b2._contactLinkList = c._b2._contactLinkList._next; + } + if(c._link2 == c._b2._contactLinkListLast) { + c._b2._contactLinkListLast = c._b2._contactLinkListLast._prev; + } + c._link2._next = null; + c._link2._prev = null; + c._b1._numContactLinks--; + c._b2._numContactLinks--; + c._link1._other = null; + c._link2._other = null; + c._link1._contact = null; + c._link2._contact = null; + c._s1 = null; + c._s2 = null; + c._b1 = null; + c._b2 = null; + c._touching = false; + c._cachedDetectorData._clear(); + c._manifold._clear(); + c._detector = null; + let _this3 = c._contactConstraint; + _this3._s1 = null; + _this3._s2 = null; + _this3._b1 = null; + _this3._b2 = null; + _this3._tf1 = null; + _this3._tf2 = null; + c._next = _this2._contactPool; + _this2._contactPool = c; + _this2._numContacts--; + } + cl = n; + } + _this._numShapes--; + } + this.updateMass(); + let s = this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = this._ptransform; + let tf2 = this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + getType() { + return this._type; + } + setType(type) { + this._type = type; + this.updateMass(); + } + wakeUp() { + this._sleeping = false; + this._sleepTime = 0; + } + sleep() { + this._sleeping = true; + this._sleepTime = 0; + } + isSleeping() { + return this._sleeping; + } + getSleepTime() { + return this._sleepTime; + } + setAutoSleep(autoSleepEnabled) { + this._autoSleep = autoSleepEnabled; + this._sleeping = false; + this._sleepTime = 0; + } + getLinearDamping() { + return this._linearDamping; + } + setLinearDamping(damping) { + this._linearDamping = damping; + } + getAngularDamping() { + return this._angularDamping; + } + setAngularDamping(damping) { + this._angularDamping = damping; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.rigidbody.RigidBodyConfig = class oimo_dynamics_rigidbody_RigidBodyConfig { + constructor() { + this.position = new oimo.common.Vec3(); + this.rotation = new oimo.common.Mat3(); + this.linearVelocity = new oimo.common.Vec3(); + this.angularVelocity = new oimo.common.Vec3(); + this.type = 0; + this.autoSleep = true; + this.linearDamping = 0; + this.angularDamping = 0; + } +} +oimo.dynamics.rigidbody.RigidBodyType = class oimo_dynamics_rigidbody_RigidBodyType { +} +oimo.dynamics.rigidbody.Shape = class oimo_dynamics_rigidbody_Shape { + constructor(config) { + this._id = -1; + this._localTransform = new oimo.common.Transform(); + this._ptransform = new oimo.common.Transform(); + this._transform = new oimo.common.Transform(); + let v = config.position; + this._localTransform._positionX = v.x; + this._localTransform._positionY = v.y; + this._localTransform._positionZ = v.z; + let m = config.rotation; + this._localTransform._rotation00 = m.e00; + this._localTransform._rotation01 = m.e01; + this._localTransform._rotation02 = m.e02; + this._localTransform._rotation10 = m.e10; + this._localTransform._rotation11 = m.e11; + this._localTransform._rotation12 = m.e12; + this._localTransform._rotation20 = m.e20; + this._localTransform._rotation21 = m.e21; + this._localTransform._rotation22 = m.e22; + let dst = this._ptransform; + let src = this._localTransform; + dst._positionX = src._positionX; + dst._positionY = src._positionY; + dst._positionZ = src._positionZ; + dst._rotation00 = src._rotation00; + dst._rotation01 = src._rotation01; + dst._rotation02 = src._rotation02; + dst._rotation10 = src._rotation10; + dst._rotation11 = src._rotation11; + dst._rotation12 = src._rotation12; + dst._rotation20 = src._rotation20; + dst._rotation21 = src._rotation21; + dst._rotation22 = src._rotation22; + let dst1 = this._transform; + let src1 = this._localTransform; + dst1._positionX = src1._positionX; + dst1._positionY = src1._positionY; + dst1._positionZ = src1._positionZ; + dst1._rotation00 = src1._rotation00; + dst1._rotation01 = src1._rotation01; + dst1._rotation02 = src1._rotation02; + dst1._rotation10 = src1._rotation10; + dst1._rotation11 = src1._rotation11; + dst1._rotation12 = src1._rotation12; + dst1._rotation20 = src1._rotation20; + dst1._rotation21 = src1._rotation21; + dst1._rotation22 = src1._rotation22; + this._restitution = config.restitution; + this._friction = config.friction; + this._density = config.density; + this._geom = config.geometry; + this._collisionGroup = config.collisionGroup; + this._collisionMask = config.collisionMask; + this._contactCallback = config.contactCallback; + this._aabb = new oimo.collision.geometry.Aabb(); + this._proxy = null; + this.displacement = new oimo.common.Vec3(); + } + getFriction() { + return this._friction; + } + setFriction(friction) { + this._friction = friction; + } + getRestitution() { + return this._restitution; + } + setRestitution(restitution) { + this._restitution = restitution; + } + getLocalTransform() { + let _this = this._localTransform; + let tf = new oimo.common.Transform(); + tf._positionX = _this._positionX; + tf._positionY = _this._positionY; + tf._positionZ = _this._positionZ; + tf._rotation00 = _this._rotation00; + tf._rotation01 = _this._rotation01; + tf._rotation02 = _this._rotation02; + tf._rotation10 = _this._rotation10; + tf._rotation11 = _this._rotation11; + tf._rotation12 = _this._rotation12; + tf._rotation20 = _this._rotation20; + tf._rotation21 = _this._rotation21; + tf._rotation22 = _this._rotation22; + return tf; + } + getLocalTransformTo(transform) { + let transform1 = this._localTransform; + transform._positionX = transform1._positionX; + transform._positionY = transform1._positionY; + transform._positionZ = transform1._positionZ; + transform._rotation00 = transform1._rotation00; + transform._rotation01 = transform1._rotation01; + transform._rotation02 = transform1._rotation02; + transform._rotation10 = transform1._rotation10; + transform._rotation11 = transform1._rotation11; + transform._rotation12 = transform1._rotation12; + transform._rotation20 = transform1._rotation20; + transform._rotation21 = transform1._rotation21; + transform._rotation22 = transform1._rotation22; + } + getTransform() { + let _this = this._transform; + let tf = new oimo.common.Transform(); + tf._positionX = _this._positionX; + tf._positionY = _this._positionY; + tf._positionZ = _this._positionZ; + tf._rotation00 = _this._rotation00; + tf._rotation01 = _this._rotation01; + tf._rotation02 = _this._rotation02; + tf._rotation10 = _this._rotation10; + tf._rotation11 = _this._rotation11; + tf._rotation12 = _this._rotation12; + tf._rotation20 = _this._rotation20; + tf._rotation21 = _this._rotation21; + tf._rotation22 = _this._rotation22; + return tf; + } + getTransformTo(transform) { + let transform1 = this._transform; + transform._positionX = transform1._positionX; + transform._positionY = transform1._positionY; + transform._positionZ = transform1._positionZ; + transform._rotation00 = transform1._rotation00; + transform._rotation01 = transform1._rotation01; + transform._rotation02 = transform1._rotation02; + transform._rotation10 = transform1._rotation10; + transform._rotation11 = transform1._rotation11; + transform._rotation12 = transform1._rotation12; + transform._rotation20 = transform1._rotation20; + transform._rotation21 = transform1._rotation21; + transform._rotation22 = transform1._rotation22; + } + setLocalTransform(transform) { + let _this = this._localTransform; + _this._positionX = transform._positionX; + _this._positionY = transform._positionY; + _this._positionZ = transform._positionZ; + _this._rotation00 = transform._rotation00; + _this._rotation01 = transform._rotation01; + _this._rotation02 = transform._rotation02; + _this._rotation10 = transform._rotation10; + _this._rotation11 = transform._rotation11; + _this._rotation12 = transform._rotation12; + _this._rotation20 = transform._rotation20; + _this._rotation21 = transform._rotation21; + _this._rotation22 = transform._rotation22; + if(this._rigidBody != null) { + let _this = this._rigidBody; + _this.updateMass(); + let s = _this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = _this._ptransform; + let tf2 = _this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } + getDensity() { + return this._density; + } + setDensity(density) { + this._density = density; + if(this._rigidBody != null) { + let _this = this._rigidBody; + _this.updateMass(); + let s = _this._shapeList; + while(s != null) { + let n = s._next; + let tf1 = _this._ptransform; + let tf2 = _this._transform; + let dst = s._ptransform; + let src1 = s._localTransform; + let __tmp__00; + let __tmp__01; + let __tmp__02; + let __tmp__10; + let __tmp__11; + let __tmp__12; + let __tmp__20; + let __tmp__21; + let __tmp__22; + __tmp__00 = tf1._rotation00 * src1._rotation00 + tf1._rotation01 * src1._rotation10 + tf1._rotation02 * src1._rotation20; + __tmp__01 = tf1._rotation00 * src1._rotation01 + tf1._rotation01 * src1._rotation11 + tf1._rotation02 * src1._rotation21; + __tmp__02 = tf1._rotation00 * src1._rotation02 + tf1._rotation01 * src1._rotation12 + tf1._rotation02 * src1._rotation22; + __tmp__10 = tf1._rotation10 * src1._rotation00 + tf1._rotation11 * src1._rotation10 + tf1._rotation12 * src1._rotation20; + __tmp__11 = tf1._rotation10 * src1._rotation01 + tf1._rotation11 * src1._rotation11 + tf1._rotation12 * src1._rotation21; + __tmp__12 = tf1._rotation10 * src1._rotation02 + tf1._rotation11 * src1._rotation12 + tf1._rotation12 * src1._rotation22; + __tmp__20 = tf1._rotation20 * src1._rotation00 + tf1._rotation21 * src1._rotation10 + tf1._rotation22 * src1._rotation20; + __tmp__21 = tf1._rotation20 * src1._rotation01 + tf1._rotation21 * src1._rotation11 + tf1._rotation22 * src1._rotation21; + __tmp__22 = tf1._rotation20 * src1._rotation02 + tf1._rotation21 * src1._rotation12 + tf1._rotation22 * src1._rotation22; + dst._rotation00 = __tmp__00; + dst._rotation01 = __tmp__01; + dst._rotation02 = __tmp__02; + dst._rotation10 = __tmp__10; + dst._rotation11 = __tmp__11; + dst._rotation12 = __tmp__12; + dst._rotation20 = __tmp__20; + dst._rotation21 = __tmp__21; + dst._rotation22 = __tmp__22; + let __tmp__X; + let __tmp__Y; + let __tmp__Z; + __tmp__X = tf1._rotation00 * src1._positionX + tf1._rotation01 * src1._positionY + tf1._rotation02 * src1._positionZ; + __tmp__Y = tf1._rotation10 * src1._positionX + tf1._rotation11 * src1._positionY + tf1._rotation12 * src1._positionZ; + __tmp__Z = tf1._rotation20 * src1._positionX + tf1._rotation21 * src1._positionY + tf1._rotation22 * src1._positionZ; + dst._positionX = __tmp__X; + dst._positionY = __tmp__Y; + dst._positionZ = __tmp__Z; + dst._positionX += tf1._positionX; + dst._positionY += tf1._positionY; + dst._positionZ += tf1._positionZ; + let dst1 = s._transform; + let src11 = s._localTransform; + let __tmp__001; + let __tmp__011; + let __tmp__021; + let __tmp__101; + let __tmp__111; + let __tmp__121; + let __tmp__201; + let __tmp__211; + let __tmp__221; + __tmp__001 = tf2._rotation00 * src11._rotation00 + tf2._rotation01 * src11._rotation10 + tf2._rotation02 * src11._rotation20; + __tmp__011 = tf2._rotation00 * src11._rotation01 + tf2._rotation01 * src11._rotation11 + tf2._rotation02 * src11._rotation21; + __tmp__021 = tf2._rotation00 * src11._rotation02 + tf2._rotation01 * src11._rotation12 + tf2._rotation02 * src11._rotation22; + __tmp__101 = tf2._rotation10 * src11._rotation00 + tf2._rotation11 * src11._rotation10 + tf2._rotation12 * src11._rotation20; + __tmp__111 = tf2._rotation10 * src11._rotation01 + tf2._rotation11 * src11._rotation11 + tf2._rotation12 * src11._rotation21; + __tmp__121 = tf2._rotation10 * src11._rotation02 + tf2._rotation11 * src11._rotation12 + tf2._rotation12 * src11._rotation22; + __tmp__201 = tf2._rotation20 * src11._rotation00 + tf2._rotation21 * src11._rotation10 + tf2._rotation22 * src11._rotation20; + __tmp__211 = tf2._rotation20 * src11._rotation01 + tf2._rotation21 * src11._rotation11 + tf2._rotation22 * src11._rotation21; + __tmp__221 = tf2._rotation20 * src11._rotation02 + tf2._rotation21 * src11._rotation12 + tf2._rotation22 * src11._rotation22; + dst1._rotation00 = __tmp__001; + dst1._rotation01 = __tmp__011; + dst1._rotation02 = __tmp__021; + dst1._rotation10 = __tmp__101; + dst1._rotation11 = __tmp__111; + dst1._rotation12 = __tmp__121; + dst1._rotation20 = __tmp__201; + dst1._rotation21 = __tmp__211; + dst1._rotation22 = __tmp__221; + let __tmp__X1; + let __tmp__Y1; + let __tmp__Z1; + __tmp__X1 = tf2._rotation00 * src11._positionX + tf2._rotation01 * src11._positionY + tf2._rotation02 * src11._positionZ; + __tmp__Y1 = tf2._rotation10 * src11._positionX + tf2._rotation11 * src11._positionY + tf2._rotation12 * src11._positionZ; + __tmp__Z1 = tf2._rotation20 * src11._positionX + tf2._rotation21 * src11._positionY + tf2._rotation22 * src11._positionZ; + dst1._positionX = __tmp__X1; + dst1._positionY = __tmp__Y1; + dst1._positionZ = __tmp__Z1; + dst1._positionX += tf2._positionX; + dst1._positionY += tf2._positionY; + dst1._positionZ += tf2._positionZ; + let minX; + let minY; + let minZ; + let maxX; + let maxY; + let maxZ; + s._geom._computeAabb(s._aabb,s._ptransform); + minX = s._aabb._minX; + minY = s._aabb._minY; + minZ = s._aabb._minZ; + maxX = s._aabb._maxX; + maxY = s._aabb._maxY; + maxZ = s._aabb._maxZ; + s._geom._computeAabb(s._aabb,s._transform); + s._aabb._minX = minX < s._aabb._minX ? minX : s._aabb._minX; + s._aabb._minY = minY < s._aabb._minY ? minY : s._aabb._minY; + s._aabb._minZ = minZ < s._aabb._minZ ? minZ : s._aabb._minZ; + s._aabb._maxX = maxX > s._aabb._maxX ? maxX : s._aabb._maxX; + s._aabb._maxY = maxY > s._aabb._maxY ? maxY : s._aabb._maxY; + s._aabb._maxZ = maxZ > s._aabb._maxZ ? maxZ : s._aabb._maxZ; + if(s._proxy != null) { + let dX; + let dY; + let dZ; + dX = s._transform._positionX - s._ptransform._positionX; + dY = s._transform._positionY - s._ptransform._positionY; + dZ = s._transform._positionZ - s._ptransform._positionZ; + let v = s.displacement; + v.x = dX; + v.y = dY; + v.z = dZ; + s._rigidBody._world._broadPhase.moveProxy(s._proxy,s._aabb,s.displacement); + } + s = n; + } + } + } + getAabb() { + return this._aabb.clone(); + } + getAabbTo(aabb) { + aabb.copyFrom(this._aabb); + } + getGeometry() { + return this._geom; + } + getRigidBody() { + return this._rigidBody; + } + getCollisionGroup() { + return this._collisionGroup; + } + setCollisionGroup(collisionGroup) { + this._collisionGroup = collisionGroup; + } + getCollisionMask() { + return this._collisionMask; + } + setCollisionMask(collisionMask) { + this._collisionMask = collisionMask; + } + getContactCallback() { + return this._contactCallback; + } + setContactCallback(callback) { + this._contactCallback = callback; + } + getPrev() { + return this._prev; + } + getNext() { + return this._next; + } +} +oimo.dynamics.rigidbody.ShapeConfig = class oimo_dynamics_rigidbody_ShapeConfig { + constructor() { + this.position = new oimo.common.Vec3(); + this.rotation = new oimo.common.Mat3(); + this.friction = oimo.common.Setting.defaultFriction; + this.restitution = oimo.common.Setting.defaultRestitution; + this.density = oimo.common.Setting.defaultDensity; + this.collisionGroup = oimo.common.Setting.defaultCollisionGroup; + this.collisionMask = oimo.common.Setting.defaultCollisionMask; + this.geometry = null; + this.contactCallback = null; + } +} +if(!oimo.m) oimo.m = {}; +oimo.m.M = class oimo_m_M { +} + +oimo.collision.broadphase.BroadPhaseType._BRUTE_FORCE = 1; +oimo.collision.broadphase.BroadPhaseType._BVH = 2; +oimo.collision.broadphase.BroadPhaseType.BRUTE_FORCE = 1; +oimo.collision.broadphase.BroadPhaseType.BVH = 2; +oimo.collision.broadphase.bvh.BvhInsertionStrategy.SIMPLE = 0; +oimo.collision.broadphase.bvh.BvhInsertionStrategy.MINIMIZE_SURFACE_AREA = 1; +oimo.collision.geometry.GeometryType._SPHERE = 0; +oimo.collision.geometry.GeometryType._BOX = 1; +oimo.collision.geometry.GeometryType._CYLINDER = 2; +oimo.collision.geometry.GeometryType._CONE = 3; +oimo.collision.geometry.GeometryType._CAPSULE = 4; +oimo.collision.geometry.GeometryType._CONVEX_HULL = 5; +oimo.collision.geometry.GeometryType._CONVEX_MIN = 0; +oimo.collision.geometry.GeometryType._CONVEX_MAX = 5; +oimo.collision.geometry.GeometryType.SPHERE = 0; +oimo.collision.geometry.GeometryType.BOX = 1; +oimo.collision.geometry.GeometryType.CYLINDER = 2; +oimo.collision.geometry.GeometryType.CONE = 3; +oimo.collision.geometry.GeometryType.CAPSULE = 4; +oimo.collision.geometry.GeometryType.CONVEX_HULL = 5; +oimo.collision.narrowphase.detector.BoxBoxDetector.EDGE_BIAS_MULT = 1.0; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.OK = 0; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.INVALID_TRIANGLE = 1; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.NO_ADJACENT_PAIR_INDEX = 2; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.NO_ADJACENT_TRIANGLE = 3; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.EDGE_LOOP_BROKEN = 4; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.NO_OUTER_TRIANGLE = 5; +oimo.collision.narrowphase.detector.gjkepa.EpaPolyhedronState.TRIANGLE_INVISIBLE = 6; +oimo.collision.narrowphase.detector.gjkepa.EpaTriangle.count = 0; +oimo.common.Vec3.numCreations = 0; +oimo.common.Setting.defaultFriction = 0.2; +oimo.common.Setting.defaultRestitution = 0.2; +oimo.common.Setting.defaultDensity = 1; +oimo.common.Setting.defaultCollisionGroup = 1; +oimo.common.Setting.defaultCollisionMask = 1; +oimo.common.Setting.maxTranslationPerStep = 20; +oimo.common.Setting.maxRotationPerStep = 3.14159265358979; +oimo.common.Setting.bvhProxyPadding = 0.1; +oimo.common.Setting.bvhIncrementalCollisionThreshold = 0.45; +oimo.common.Setting.defaultGJKMargin = 0.05; +oimo.common.Setting.enableGJKCaching = true; +oimo.common.Setting.maxEPAVertices = 128; +oimo.common.Setting.maxEPAPolyhedronFaces = 128; +oimo.common.Setting.contactEnableBounceThreshold = 0.5; +oimo.common.Setting.velocityBaumgarte = 0.2; +oimo.common.Setting.positionSplitImpulseBaumgarte = 0.4; +oimo.common.Setting.positionNgsBaumgarte = 1.0; +oimo.common.Setting.contactUseAlternativePositionCorrectionAlgorithmDepthThreshold = 0.05; +oimo.common.Setting.defaultContactPositionCorrectionAlgorithm = 0; +oimo.common.Setting.alternativeContactPositionCorrectionAlgorithm = 1; +oimo.common.Setting.contactPersistenceThreshold = 0.05; +oimo.common.Setting.maxManifoldPoints = 4; +oimo.common.Setting.defaultJointConstraintSolverType = 0; +oimo.common.Setting.defaultJointPositionCorrectionAlgorithm = 0; +oimo.common.Setting.jointWarmStartingFactorForBaungarte = 0.8; +oimo.common.Setting.jointWarmStartingFactor = 0.95; +oimo.common.Setting.minSpringDamperDampingRatio = 1e-6; +oimo.common.Setting.minRagdollMaxSwingAngle = 1e-6; +oimo.common.Setting.maxJacobianRows = 6; +oimo.common.Setting.directMlcpSolverEps = 1e-9; +oimo.common.Setting.islandInitialRigidBodyArraySize = 128; +oimo.common.Setting.islandInitialConstraintArraySize = 128; +oimo.common.Setting.sleepingVelocityThreshold = 0.2; +oimo.common.Setting.sleepingAngularVelocityThreshold = 0.5; +oimo.common.Setting.sleepingTimeThreshold = 1.0; +oimo.common.Setting.disableSleeping = false; +oimo.common.Setting.linearSlop = 0.005; +oimo.common.Setting.angularSlop = 0.017453292519943278; +oimo.collision.narrowphase.detector.gjkepa.GjkEpa.instance = new oimo.collision.narrowphase.detector.gjkepa.GjkEpa(); +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._SUCCEEDED = 0; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._GJK_FAILED_TO_MAKE_TETRAHEDRON = 1; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._GJK_DID_NOT_CONVERGE = 2; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._EPA_FAILED_TO_INIT = 257; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._EPA_FAILED_TO_ADD_VERTEX = 258; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState._EPA_DID_NOT_CONVERGE = 259; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.SUCCEEDED = 0; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.GJK_FAILED_TO_MAKE_TETRAHEDRON = 1; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.GJK_DID_NOT_CONVERGE = 2; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_INIT = 257; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_FAILED_TO_ADD_VERTEX = 258; +oimo.collision.narrowphase.detector.gjkepa.GjkEpaResultState.EPA_DID_NOT_CONVERGE = 259; +oimo.common.Mat3.numCreations = 0; +oimo.common.Mat4.numCreations = 0; +oimo.common.MathUtil.POSITIVE_INFINITY = 1e65536; +oimo.common.MathUtil.NEGATIVE_INFINITY = -1e65536; +oimo.common.MathUtil.PI = 3.14159265358979; +oimo.common.MathUtil.TWO_PI = 6.28318530717958; +oimo.common.MathUtil.HALF_PI = 1.570796326794895; +oimo.common.MathUtil.TO_RADIANS = 0.017453292519943278; +oimo.common.MathUtil.TO_DEGREES = 57.29577951308238; +oimo.common.Quat.numCreations = 0; +oimo.dynamics.common.DebugDraw.SPHERE_PHI_DIVISION = 8; +oimo.dynamics.common.DebugDraw.SPHERE_THETA_DIVISION = 4; +oimo.dynamics.common.DebugDraw.CIRCLE_THETA_DIVISION = 8; +oimo.dynamics.common.Performance.broadPhaseCollisionTime = 0; +oimo.dynamics.common.Performance.narrowPhaseCollisionTime = 0; +oimo.dynamics.common.Performance.dynamicsTime = 0; +oimo.dynamics.common.Performance.totalTime = 0; +oimo.dynamics.constraint.PositionCorrectionAlgorithm._BAUMGARTE = 0; +oimo.dynamics.constraint.PositionCorrectionAlgorithm._SPLIT_IMPULSE = 1; +oimo.dynamics.constraint.PositionCorrectionAlgorithm._NGS = 2; +oimo.dynamics.constraint.PositionCorrectionAlgorithm.BAUMGARTE = 0; +oimo.dynamics.constraint.PositionCorrectionAlgorithm.SPLIT_IMPULSE = 1; +oimo.dynamics.constraint.PositionCorrectionAlgorithm.NGS = 2; +oimo.dynamics.constraint.info.JacobianRow.BIT_LINEAR_SET = 1; +oimo.dynamics.constraint.info.JacobianRow.BIT_ANGULAR_SET = 2; +oimo.dynamics.constraint.joint.JointType._SPHERICAL = 0; +oimo.dynamics.constraint.joint.JointType._REVOLUTE = 1; +oimo.dynamics.constraint.joint.JointType._CYLINDRICAL = 2; +oimo.dynamics.constraint.joint.JointType._PRISMATIC = 3; +oimo.dynamics.constraint.joint.JointType._UNIVERSAL = 4; +oimo.dynamics.constraint.joint.JointType._RAGDOLL = 5; +oimo.dynamics.constraint.joint.JointType._GENERIC = 6; +oimo.dynamics.constraint.joint.JointType.SPHERICAL = 0; +oimo.dynamics.constraint.joint.JointType.REVOLUTE = 1; +oimo.dynamics.constraint.joint.JointType.CYLINDRICAL = 2; +oimo.dynamics.constraint.joint.JointType.PRISMATIC = 3; +oimo.dynamics.constraint.joint.JointType.UNIVERSAL = 4; +oimo.dynamics.constraint.joint.JointType.RAGDOLL = 5; +oimo.dynamics.constraint.joint.JointType.GENERIC = 6; +oimo.dynamics.constraint.solver.ConstraintSolverType._ITERATIVE = 0; +oimo.dynamics.constraint.solver.ConstraintSolverType._DIRECT = 1; +oimo.dynamics.constraint.solver.ConstraintSolverType.ITERATIVE = 0; +oimo.dynamics.constraint.solver.ConstraintSolverType.DIRECT = 1; +oimo.dynamics.rigidbody.RigidBodyType._DYNAMIC = 0; +oimo.dynamics.rigidbody.RigidBodyType._STATIC = 1; +oimo.dynamics.rigidbody.RigidBodyType._KINEMATIC = 2; +oimo.dynamics.rigidbody.RigidBodyType.DYNAMIC = 0; +oimo.dynamics.rigidbody.RigidBodyType.STATIC = 1; +oimo.dynamics.rigidbody.RigidBodyType.KINEMATIC = 2; +export {oimo}; diff --git a/jsm/libs/OimoPhysics/index.js b/jsm/libs/OimoPhysics/index.js new file mode 100644 index 0000000..0bf5789 --- /dev/null +++ b/jsm/libs/OimoPhysics/index.js @@ -0,0 +1,43 @@ +import {oimo} from './OimoPhysics.js'; + +// dynamics +export const World = oimo.dynamics.World; +export const RigidBodyType = oimo.dynamics.rigidbody.RigidBodyType; +export const RigidBodyConfig = oimo.dynamics.rigidbody.RigidBodyConfig; +export const ShapeConfig = oimo.dynamics.rigidbody.ShapeConfig; +export const RigidBody = oimo.dynamics.rigidbody.RigidBody; +export const Shape = oimo.dynamics.rigidbody.Shape; +export const SphericalJoint = oimo.dynamics.constraint.joint.SphericalJoint; +export const RevoluteJointConfig = oimo.dynamics.constraint.joint.RevoluteJointConfig; +export const UniversalJointConfig = oimo.dynamics.constraint.joint.UniversalJointConfig; +export const CylindricalJoint = oimo.dynamics.constraint.joint.CylindricalJoint; +export const PrismaticJoint = oimo.dynamics.constraint.joint.PrismaticJoint; +export const PrismaticJointConfig = oimo.dynamics.constraint.joint.PrismaticJointConfig; +export const RevoluteJoint = oimo.dynamics.constraint.joint.RevoluteJoint; +export const RagdollJoint = oimo.dynamics.constraint.joint.RagdollJoint; +export const CylindricalJointConfig = oimo.dynamics.constraint.joint.CylindricalJointConfig; +export const SphericalJointConfig = oimo.dynamics.constraint.joint.SphericalJointConfig; +export const RagdollJointConfig = oimo.dynamics.constraint.joint.RagdollJointConfig; +export const SpringDamper = oimo.dynamics.constraint.joint.SpringDamper; +export const TranslationalLimitMotor = oimo.dynamics.constraint.joint.TranslationalLimitMotor; +export const RotationalLimitMotor = oimo.dynamics.constraint.joint.RotationalLimitMotor; +export const UniversalJoint = oimo.dynamics.constraint.joint.UniversalJoint; + +// common +export const Vec3 = oimo.common.Vec3; +export const Quat = oimo.common.Quat; +export const Mat3 = oimo.common.Mat3; +export const MathUtil = oimo.common.MathUtil; +export const Transform = oimo.common.Transform; + +// collision +export const OCapsuleGeometry = oimo.collision.geometry.CapsuleGeometry; +export const OConvexHullGeometry = oimo.collision.geometry.ConvexHullGeometry; +export const OBoxGeometry = oimo.collision.geometry.BoxGeometry; +export const OSphereGeometry = oimo.collision.geometry.SphereGeometry; +export const OCylinderGeometry = oimo.collision.geometry.CylinderGeometry; +export const OConeGeometry = oimo.collision.geometry.ConeGeometry; +export const OGeometry = oimo.collision.geometry.Geometry; + +// callback +export const RayCastClosest = oimo.dynamics.callback.RayCastClosest; diff --git a/jsm/libs/chevrotain.module.min.js b/jsm/libs/chevrotain.module.min.js new file mode 100644 index 0000000..424790c --- /dev/null +++ b/jsm/libs/chevrotain.module.min.js @@ -0,0 +1,141 @@ +/*! chevrotain - v9.0.1 */ +var R=(t,e)=>()=>(e||(e={exports:{}},t(e.exports,e)),e.exports);var Er=R(Pt=>{"use strict";Object.defineProperty(Pt,"__esModule",{value:!0});Pt.VERSION=void 0;Pt.VERSION="9.0.1"});var k=R((exports,module)=>{"use strict";var __spreadArray=exports&&exports.__spreadArray||function(t,e){for(var r=0,n=e.length,i=t.length;r{(function(t,e){typeof define=="function"&&define.amd?define([],e):typeof St=="object"&&St.exports?St.exports=e():t.regexpToAst=e()})(typeof self!="undefined"?self:sn,function(){function t(){}t.prototype.saveState=function(){return{idx:this.idx,input:this.input,groupIdx:this.groupIdx}},t.prototype.restoreState=function(u){this.idx=u.idx,this.input=u.input,this.groupIdx=u.groupIdx},t.prototype.pattern=function(u){this.idx=0,this.input=u,this.groupIdx=0,this.consumeChar("/");var d=this.disjunction();this.consumeChar("/");for(var A={type:"Flags",loc:{begin:this.idx,end:u.length},global:!1,ignoreCase:!1,multiLine:!1,unicode:!1,sticky:!1};this.isRegExpFlag();)switch(this.popChar()){case"g":o(A,"global");break;case"i":o(A,"ignoreCase");break;case"m":o(A,"multiLine");break;case"u":o(A,"unicode");break;case"y":o(A,"sticky");break}if(this.idx!==this.input.length)throw Error("Redundant input: "+this.input.substring(this.idx));return{type:"Pattern",flags:A,value:d,loc:this.loc(0)}},t.prototype.disjunction=function(){var u=[],d=this.idx;for(u.push(this.alternative());this.peekChar()==="|";)this.consumeChar("|"),u.push(this.alternative());return{type:"Disjunction",value:u,loc:this.loc(d)}},t.prototype.alternative=function(){for(var u=[],d=this.idx;this.isTerm();)u.push(this.term());return{type:"Alternative",value:u,loc:this.loc(d)}},t.prototype.term=function(){return this.isAssertion()?this.assertion():this.atom()},t.prototype.assertion=function(){var u=this.idx;switch(this.popChar()){case"^":return{type:"StartAnchor",loc:this.loc(u)};case"$":return{type:"EndAnchor",loc:this.loc(u)};case"\\":switch(this.popChar()){case"b":return{type:"WordBoundary",loc:this.loc(u)};case"B":return{type:"NonWordBoundary",loc:this.loc(u)}}throw Error("Invalid Assertion Escape");case"(":this.consumeChar("?");var d;switch(this.popChar()){case"=":d="Lookahead";break;case"!":d="NegativeLookahead";break}s(d);var A=this.disjunction();return this.consumeChar(")"),{type:d,value:A,loc:this.loc(u)}}c()},t.prototype.quantifier=function(u){var d,A=this.idx;switch(this.popChar()){case"*":d={atLeast:0,atMost:Infinity};break;case"+":d={atLeast:1,atMost:Infinity};break;case"?":d={atLeast:0,atMost:1};break;case"{":var _=this.integerIncludingZero();switch(this.popChar()){case"}":d={atLeast:_,atMost:_};break;case",":var g;this.isDigit()?(g=this.integerIncludingZero(),d={atLeast:_,atMost:g}):d={atLeast:_,atMost:Infinity},this.consumeChar("}");break}if(u===!0&&d===void 0)return;s(d);break}if(!(u===!0&&d===void 0))return s(d),this.peekChar(0)==="?"?(this.consumeChar("?"),d.greedy=!1):d.greedy=!0,d.type="Quantifier",d.loc=this.loc(A),d},t.prototype.atom=function(){var u,d=this.idx;switch(this.peekChar()){case".":u=this.dotAll();break;case"\\":u=this.atomEscape();break;case"[":u=this.characterClass();break;case"(":u=this.group();break}return u===void 0&&this.isPatternCharacter()&&(u=this.patternCharacter()),s(u),u.loc=this.loc(d),this.isQuantifier()&&(u.quantifier=this.quantifier()),u},t.prototype.dotAll=function(){return this.consumeChar("."),{type:"Set",complement:!0,value:[i(` +`),i("\r"),i("\u2028"),i("\u2029")]}},t.prototype.atomEscape=function(){switch(this.consumeChar("\\"),this.peekChar()){case"1":case"2":case"3":case"4":case"5":case"6":case"7":case"8":case"9":return this.decimalEscapeAtom();case"d":case"D":case"s":case"S":case"w":case"W":return this.characterClassEscape();case"f":case"n":case"r":case"t":case"v":return this.controlEscapeAtom();case"c":return this.controlLetterEscapeAtom();case"0":return this.nulCharacterAtom();case"x":return this.hexEscapeSequenceAtom();case"u":return this.regExpUnicodeEscapeSequenceAtom();default:return this.identityEscapeAtom()}},t.prototype.decimalEscapeAtom=function(){var u=this.positiveInteger();return{type:"GroupBackReference",value:u}},t.prototype.characterClassEscape=function(){var u,d=!1;switch(this.popChar()){case"d":u=p;break;case"D":u=p,d=!0;break;case"s":u=m;break;case"S":u=m,d=!0;break;case"w":u=l;break;case"W":u=l,d=!0;break}return s(u),{type:"Set",value:u,complement:d}},t.prototype.controlEscapeAtom=function(){var u;switch(this.popChar()){case"f":u=i("\f");break;case"n":u=i(` +`);break;case"r":u=i("\r");break;case"t":u=i(" ");break;case"v":u=i("\v");break}return s(u),{type:"Character",value:u}},t.prototype.controlLetterEscapeAtom=function(){this.consumeChar("c");var u=this.popChar();if(/[a-zA-Z]/.test(u)===!1)throw Error("Invalid ");var d=u.toUpperCase().charCodeAt(0)-64;return{type:"Character",value:d}},t.prototype.nulCharacterAtom=function(){return this.consumeChar("0"),{type:"Character",value:i("\0")}},t.prototype.hexEscapeSequenceAtom=function(){return this.consumeChar("x"),this.parseHexDigits(2)},t.prototype.regExpUnicodeEscapeSequenceAtom=function(){return this.consumeChar("u"),this.parseHexDigits(4)},t.prototype.identityEscapeAtom=function(){var u=this.popChar();return{type:"Character",value:i(u)}},t.prototype.classPatternCharacterAtom=function(){switch(this.peekChar()){case` +`:case"\r":case"\u2028":case"\u2029":case"\\":case"]":throw Error("TBD");default:var u=this.popChar();return{type:"Character",value:i(u)}}},t.prototype.characterClass=function(){var u=[],d=!1;for(this.consumeChar("["),this.peekChar(0)==="^"&&(this.consumeChar("^"),d=!0);this.isClassAtom();){var A=this.classAtom(),_=A.type==="Character";if(_&&this.isRangeDash()){this.consumeChar("-");var g=this.classAtom(),y=g.type==="Character";if(y){if(g.value=this.input.length)throw Error("Unexpected end of input");this.idx++},t.prototype.loc=function(u){return{begin:u,end:this.idx}};var e=/[0-9a-fA-F]/,r=/[0-9]/,n=/[1-9]/;function i(u){return u.charCodeAt(0)}function a(u,d){u.length!==void 0?u.forEach(function(A){d.push(A)}):d.push(u)}function o(u,d){if(u[d]===!0)throw"duplicate flag "+d;u[d]=!0}function s(u){if(u===void 0)throw Error("Internal Error - Should never get here!")}function c(){throw Error("Internal Error - Should never get here!")}var f,p=[];for(f=i("0");f<=i("9");f++)p.push(f);var l=[i("_")].concat(p);for(f=i("a");f<=i("z");f++)l.push(f);for(f=i("A");f<=i("Z");f++)l.push(f);var m=[i(" "),i("\f"),i(` +`),i("\r"),i(" "),i("\v"),i(" "),i("\xA0"),i("\u1680"),i("\u2000"),i("\u2001"),i("\u2002"),i("\u2003"),i("\u2004"),i("\u2005"),i("\u2006"),i("\u2007"),i("\u2008"),i("\u2009"),i("\u200A"),i("\u2028"),i("\u2029"),i("\u202F"),i("\u205F"),i("\u3000"),i("\uFEFF")];function v(){}return v.prototype.visitChildren=function(u){for(var d in u){var A=u[d];u.hasOwnProperty(d)&&(A.type!==void 0?this.visit(A):Array.isArray(A)&&A.forEach(function(_){this.visit(_)},this))}},v.prototype.visit=function(u){switch(u.type){case"Pattern":this.visitPattern(u);break;case"Flags":this.visitFlags(u);break;case"Disjunction":this.visitDisjunction(u);break;case"Alternative":this.visitAlternative(u);break;case"StartAnchor":this.visitStartAnchor(u);break;case"EndAnchor":this.visitEndAnchor(u);break;case"WordBoundary":this.visitWordBoundary(u);break;case"NonWordBoundary":this.visitNonWordBoundary(u);break;case"Lookahead":this.visitLookahead(u);break;case"NegativeLookahead":this.visitNegativeLookahead(u);break;case"Character":this.visitCharacter(u);break;case"Set":this.visitSet(u);break;case"Group":this.visitGroup(u);break;case"GroupBackReference":this.visitGroupBackReference(u);break;case"Quantifier":this.visitQuantifier(u);break}this.visitChildren(u)},v.prototype.visitPattern=function(u){},v.prototype.visitFlags=function(u){},v.prototype.visitDisjunction=function(u){},v.prototype.visitAlternative=function(u){},v.prototype.visitStartAnchor=function(u){},v.prototype.visitEndAnchor=function(u){},v.prototype.visitWordBoundary=function(u){},v.prototype.visitNonWordBoundary=function(u){},v.prototype.visitLookahead=function(u){},v.prototype.visitNegativeLookahead=function(u){},v.prototype.visitCharacter=function(u){},v.prototype.visitSet=function(u){},v.prototype.visitGroup=function(u){},v.prototype.visitGroupBackReference=function(u){},v.prototype.visitQuantifier=function(u){},{RegExpParser:t,BaseRegExpVisitor:v,VERSION:"0.5.0"}})});var Lt=R(He=>{"use strict";Object.defineProperty(He,"__esModule",{value:!0});He.clearRegExpParserCache=He.getRegExpAst=void 0;var Ga=xt(),Ct={},Wa=new Ga.RegExpParser;function Ba(t){var e=t.toString();if(Ct.hasOwnProperty(e))return Ct[e];var r=Wa.pattern(e);return Ct[e]=r,r}He.getRegExpAst=Ba;function qa(){Ct={}}He.clearRegExpParserCache=qa});var pn=R(re=>{"use strict";var ja=re&&re.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(re,"__esModule",{value:!0});re.canMatchCharCode=re.firstCharOptimizedIndices=re.getOptimizedStartCodesIndices=re.failedOptimizationPrefixMsg=void 0;var un=xt(),pe=k(),cn=Lt(),Ce=Tr(),ln="Complement Sets are not supported for first char optimization";re.failedOptimizationPrefixMsg=`Unable to use "first char" lexer optimizations: +`;function Va(t,e){e===void 0&&(e=!1);try{var r=cn.getRegExpAst(t),n=Mt(r.value,{},r.flags.ignoreCase);return n}catch(a){if(a.message===ln)e&&pe.PRINT_WARNING(""+re.failedOptimizationPrefixMsg+(" Unable to optimize: < "+t.toString()+` > +`)+` Complement Sets cannot be automatically optimized. + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#COMPLEMENT for details.`);else{var i="";e&&(i=` + This will disable the lexer's first char optimizations. + See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#REGEXP_PARSING for details.`),pe.PRINT_ERROR(re.failedOptimizationPrefixMsg+` +`+(" Failed parsing: < "+t.toString()+` > +`)+(" Using the regexp-to-ast library version: "+un.VERSION+` +`)+" Please open an issue at: https://github.com/bd82/regexp-to-ast/issues"+i)}}return[]}re.getOptimizedStartCodesIndices=Va;function Mt(t,e,r){switch(t.type){case"Disjunction":for(var n=0;n=Ce.minOptimizationVal)for(var m=p.from>=Ce.minOptimizationVal?p.from:Ce.minOptimizationVal,v=p.to,u=Ce.charCodeToOptimizedIndex(m),d=Ce.charCodeToOptimizedIndex(v),A=u;A<=d;A++)e[A]=A}}});break;case"Group":Mt(o.value,e,r);break;default:throw Error("Non Exhaustive Match")}var s=o.quantifier!==void 0&&o.quantifier.atLeast===0;if(o.type==="Group"&&yr(o)===!1||o.type!=="Group"&&s===!1)break}break;default:throw Error("non exhaustive match!")}return pe.values(e)}re.firstCharOptimizedIndices=Mt;function bt(t,e,r){var n=Ce.charCodeToOptimizedIndex(t);e[n]=n,r===!0&&Ka(t,e)}function Ka(t,e){var r=String.fromCharCode(t),n=r.toUpperCase();if(n!==r){var i=Ce.charCodeToOptimizedIndex(n.charCodeAt(0));e[i]=i}else{var a=r.toLowerCase();if(a!==r){var i=Ce.charCodeToOptimizedIndex(a.charCodeAt(0));e[i]=i}}}function fn(t,e){return pe.find(t.value,function(r){if(typeof r=="number")return pe.contains(e,r);var n=r;return pe.find(e,function(i){return n.from<=i&&i<=n.to})!==void 0})}function yr(t){return t.quantifier&&t.quantifier.atLeast===0?!0:t.value?pe.isArray(t.value)?pe.every(t.value,yr):yr(t.value):!1}var za=function(t){ja(e,t);function e(r){var n=t.call(this)||this;return n.targetCharCodes=r,n.found=!1,n}return e.prototype.visitChildren=function(r){if(this.found!==!0){switch(r.type){case"Lookahead":this.visitLookahead(r);return;case"NegativeLookahead":this.visitNegativeLookahead(r);return}t.prototype.visitChildren.call(this,r)}},e.prototype.visitCharacter=function(r){pe.contains(this.targetCharCodes,r.value)&&(this.found=!0)},e.prototype.visitSet=function(r){r.complement?fn(r,this.targetCharCodes)===void 0&&(this.found=!0):fn(r,this.targetCharCodes)!==void 0&&(this.found=!0)},e}(un.BaseRegExpVisitor);function Ha(t,e){if(e instanceof RegExp){var r=cn.getRegExpAst(e),n=new za(t);return n.visit(r),n.found}else return pe.find(e,function(i){return pe.contains(t,i.charCodeAt(0))})!==void 0}re.canMatchCharCode=Ha});var Tr=R(T=>{"use strict";var hn=T&&T.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(T,"__esModule",{value:!0});T.charCodeToOptimizedIndex=T.minOptimizationVal=T.buildLineBreakIssueMessage=T.LineTerminatorOptimizedTester=T.isShortPattern=T.isCustomPattern=T.cloneEmptyGroups=T.performWarningRuntimeChecks=T.performRuntimeChecks=T.addStickyFlag=T.addStartOfInput=T.findUnreachablePatterns=T.findModesThatDoNotExist=T.findInvalidGroupType=T.findDuplicatePatterns=T.findUnsupportedFlags=T.findStartOfInputAnchor=T.findEmptyMatchRegExps=T.findEndOfInputAnchor=T.findInvalidPatterns=T.findMissingPatterns=T.validatePatterns=T.analyzeTokenTypes=T.enableSticky=T.disableSticky=T.SUPPORT_STICKY=T.MODES=T.DEFAULT_MODE=void 0;var dn=xt(),F=ft(),h=k(),Ye=pn(),vn=Lt(),Ae="PATTERN";T.DEFAULT_MODE="defaultMode";T.MODES="modes";T.SUPPORT_STICKY=typeof new RegExp("(?:)").sticky=="boolean";function Ya(){T.SUPPORT_STICKY=!1}T.disableSticky=Ya;function Xa(){T.SUPPORT_STICKY=!0}T.enableSticky=Xa;function Za(t,e){e=h.defaults(e,{useSticky:T.SUPPORT_STICKY,debug:!1,safeMode:!1,positionTracking:"full",lineTerminatorCharacters:["\r",` +`],tracer:function(g,y){return y()}});var r=e.tracer;r("initCharCodeToOptimizedIndexMap",function(){$a()});var n;r("Reject Lexer.NA",function(){n=h.reject(t,function(g){return g[Ae]===F.Lexer.NA})});var i=!1,a;r("Transform Patterns",function(){i=!1,a=h.map(n,function(g){var y=g[Ae];if(h.isRegExp(y)){var b=y.source;return b.length===1&&b!=="^"&&b!=="$"&&b!=="."&&!y.ignoreCase?b:b.length===2&&b[0]==="\\"&&!h.contains(["d","D","s","S","t","r","n","t","0","c","b","B","f","v","w","W"],b[1])?b[1]:e.useSticky?gr(y):_r(y)}else{if(h.isFunction(y))return i=!0,{exec:y};if(h.has(y,"exec"))return i=!0,y;if(typeof y=="string"){if(y.length===1)return y;var L=y.replace(/[\\^$.*+?()[\]{}|]/g,"\\$&"),se=new RegExp(L);return e.useSticky?gr(se):_r(se)}else throw Error("non exhaustive match")}})});var o,s,c,f,p;r("misc mapping",function(){o=h.map(n,function(g){return g.tokenTypeIdx}),s=h.map(n,function(g){var y=g.GROUP;if(y!==F.Lexer.SKIPPED){if(h.isString(y))return y;if(h.isUndefined(y))return!1;throw Error("non exhaustive match")}}),c=h.map(n,function(g){var y=g.LONGER_ALT;if(y){var b=h.indexOf(n,y);return b}}),f=h.map(n,function(g){return g.PUSH_MODE}),p=h.map(n,function(g){return h.has(g,"POP_MODE")})});var l;r("Line Terminator Handling",function(){var g=Tn(e.lineTerminatorCharacters);l=h.map(n,function(y){return!1}),e.positionTracking!=="onlyOffset"&&(l=h.map(n,function(y){if(h.has(y,"LINE_BREAKS"))return y.LINE_BREAKS;if(En(y,g)===!1)return Ye.canMatchCharCode(g,y.PATTERN)}))});var m,v,u,d;r("Misc Mapping #2",function(){m=h.map(n,Ar),v=h.map(a,mn),u=h.reduce(n,function(g,y){var b=y.GROUP;return h.isString(b)&&b!==F.Lexer.SKIPPED&&(g[b]=[]),g},{}),d=h.map(a,function(g,y){return{pattern:a[y],longerAlt:c[y],canLineTerminator:l[y],isCustom:m[y],short:v[y],group:s[y],push:f[y],pop:p[y],tokenTypeIdx:o[y],tokenType:n[y]}})});var A=!0,_=[];return e.safeMode||r("First Char Optimization",function(){_=h.reduce(n,function(g,y,b){if(typeof y.PATTERN=="string"){var L=y.PATTERN.charCodeAt(0),se=Or(L);Rr(g,se,d[b])}else if(h.isArray(y.START_CHARS_HINT)){var fe;h.forEach(y.START_CHARS_HINT,function(ue){var Q=typeof ue=="string"?ue.charCodeAt(0):ue,te=Or(Q);fe!==te&&(fe=te,Rr(g,te,d[b]))})}else if(h.isRegExp(y.PATTERN))if(y.PATTERN.unicode)A=!1,e.ensureOptimizations&&h.PRINT_ERROR(""+Ye.failedOptimizationPrefixMsg+(" Unable to analyze < "+y.PATTERN.toString()+` > pattern. +`)+` The regexp unicode flag is not currently supported by the regexp-to-ast library. + This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNICODE_OPTIMIZE`);else{var Z=Ye.getOptimizedStartCodesIndices(y.PATTERN,e.ensureOptimizations);h.isEmpty(Z)&&(A=!1),h.forEach(Z,function(ue){Rr(g,ue,d[b])})}else e.ensureOptimizations&&h.PRINT_ERROR(""+Ye.failedOptimizationPrefixMsg+(" TokenType: <"+y.name+`> is using a custom token pattern without providing parameter. +`)+` This will disable the lexer's first char optimizations. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_OPTIMIZE`),A=!1;return g},[])}),r("ArrayPacking",function(){_=h.packArray(_)}),{emptyGroups:u,patternIdxToConfig:d,charCodeToPatternIdxToConfig:_,hasCustom:i,canBeOptimized:A}}T.analyzeTokenTypes=Za;function Ja(t,e){var r=[],n=yn(t);r=r.concat(n.errors);var i=_n(n.valid),a=i.valid;return r=r.concat(i.errors),r=r.concat(Qa(a)),r=r.concat(gn(a)),r=r.concat(An(a,e)),r=r.concat(Rn(a)),r}T.validatePatterns=Ja;function Qa(t){var e=[],r=h.filter(t,function(n){return h.isRegExp(n[Ae])});return e=e.concat(On(r)),e=e.concat(In(r)),e=e.concat(kn(r)),e=e.concat(Pn(r)),e=e.concat(Nn(r)),e}function yn(t){var e=h.filter(t,function(i){return!h.has(i,Ae)}),r=h.map(e,function(i){return{message:"Token Type: ->"+i.name+"<- missing static 'PATTERN' property",type:F.LexerDefinitionErrorType.MISSING_PATTERN,tokenTypes:[i]}}),n=h.difference(t,e);return{errors:r,valid:n}}T.findMissingPatterns=yn;function _n(t){var e=h.filter(t,function(i){var a=i[Ae];return!h.isRegExp(a)&&!h.isFunction(a)&&!h.has(a,"exec")&&!h.isString(a)}),r=h.map(e,function(i){return{message:"Token Type: ->"+i.name+"<- static 'PATTERN' can only be a RegExp, a Function matching the {CustomPatternMatcherFunc} type or an Object matching the {ICustomPattern} interface.",type:F.LexerDefinitionErrorType.INVALID_PATTERN,tokenTypes:[i]}}),n=h.difference(t,e);return{errors:r,valid:n}}T.findInvalidPatterns=_n;var eo=/[^\\][\$]/;function On(t){var e=function(i){hn(a,i);function a(){var o=i!==null&&i.apply(this,arguments)||this;return o.found=!1,o}return a.prototype.visitEndAnchor=function(o){this.found=!0},a}(dn.BaseRegExpVisitor),r=h.filter(t,function(i){var a=i[Ae];try{var o=vn.getRegExpAst(a),s=new e;return s.visit(o),s.found}catch(c){return eo.test(a.source)}}),n=h.map(r,function(i){return{message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain end of input anchor '$' + See chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:F.LexerDefinitionErrorType.EOI_ANCHOR_FOUND,tokenTypes:[i]}});return n}T.findEndOfInputAnchor=On;function Nn(t){var e=h.filter(t,function(n){var i=n[Ae];return i.test("")}),r=h.map(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'PATTERN' must not match an empty string",type:F.LexerDefinitionErrorType.EMPTY_MATCH_PATTERN,tokenTypes:[n]}});return r}T.findEmptyMatchRegExps=Nn;var to=/[^\\[][\^]|^\^/;function In(t){var e=function(i){hn(a,i);function a(){var o=i!==null&&i.apply(this,arguments)||this;return o.found=!1,o}return a.prototype.visitStartAnchor=function(o){this.found=!0},a}(dn.BaseRegExpVisitor),r=h.filter(t,function(i){var a=i[Ae];try{var o=vn.getRegExpAst(a),s=new e;return s.visit(o),s.found}catch(c){return to.test(a.source)}}),n=h.map(r,function(i){return{message:`Unexpected RegExp Anchor Error: + Token Type: ->`+i.name+`<- static 'PATTERN' cannot contain start of input anchor '^' + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#ANCHORS for details.`,type:F.LexerDefinitionErrorType.SOI_ANCHOR_FOUND,tokenTypes:[i]}});return n}T.findStartOfInputAnchor=In;function kn(t){var e=h.filter(t,function(n){var i=n[Ae];return i instanceof RegExp&&(i.multiline||i.global)}),r=h.map(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'PATTERN' may NOT contain global('g') or multiline('m')",type:F.LexerDefinitionErrorType.UNSUPPORTED_FLAGS_FOUND,tokenTypes:[n]}});return r}T.findUnsupportedFlags=kn;function Pn(t){var e=[],r=h.map(t,function(a){return h.reduce(t,function(o,s){return a.PATTERN.source===s.PATTERN.source&&!h.contains(e,s)&&s.PATTERN!==F.Lexer.NA&&(e.push(s),o.push(s)),o},[])});r=h.compact(r);var n=h.filter(r,function(a){return a.length>1}),i=h.map(n,function(a){var o=h.map(a,function(c){return c.name}),s=h.first(a).PATTERN;return{message:"The same RegExp pattern ->"+s+"<-"+("has been used in all of the following Token Types: "+o.join(", ")+" <-"),type:F.LexerDefinitionErrorType.DUPLICATE_PATTERNS_FOUND,tokenTypes:a}});return i}T.findDuplicatePatterns=Pn;function gn(t){var e=h.filter(t,function(n){if(!h.has(n,"GROUP"))return!1;var i=n.GROUP;return i!==F.Lexer.SKIPPED&&i!==F.Lexer.NA&&!h.isString(i)}),r=h.map(e,function(n){return{message:"Token Type: ->"+n.name+"<- static 'GROUP' can only be Lexer.SKIPPED/Lexer.NA/A String",type:F.LexerDefinitionErrorType.INVALID_GROUP_TYPE_FOUND,tokenTypes:[n]}});return r}T.findInvalidGroupType=gn;function An(t,e){var r=h.filter(t,function(i){return i.PUSH_MODE!==void 0&&!h.contains(e,i.PUSH_MODE)}),n=h.map(r,function(i){var a="Token Type: ->"+i.name+"<- static 'PUSH_MODE' value cannot refer to a Lexer Mode ->"+i.PUSH_MODE+"<-which does not exist";return{message:a,type:F.LexerDefinitionErrorType.PUSH_MODE_DOES_NOT_EXIST,tokenTypes:[i]}});return n}T.findModesThatDoNotExist=An;function Rn(t){var e=[],r=h.reduce(t,function(n,i,a){var o=i.PATTERN;return o===F.Lexer.NA||(h.isString(o)?n.push({str:o,idx:a,tokenType:i}):h.isRegExp(o)&&no(o)&&n.push({str:o.source,idx:a,tokenType:i})),n},[]);return h.forEach(t,function(n,i){h.forEach(r,function(a){var o=a.str,s=a.idx,c=a.tokenType;if(i"+n.name+"<-")+`in the lexer's definition. +See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#UNREACHABLE`;e.push({message:f,type:F.LexerDefinitionErrorType.UNREACHABLE_PATTERN,tokenTypes:[n,c]})}})}),e}T.findUnreachablePatterns=Rn;function ro(t,e){if(h.isRegExp(e)){var r=e.exec(t);return r!==null&&r.index===0}else{if(h.isFunction(e))return e(t,0,[],{});if(h.has(e,"exec"))return e.exec(t,0,[],{});if(typeof e=="string")return e===t;throw Error("non exhaustive match")}}function no(t){var e=[".","\\","[","]","|","^","$","(",")","?","*","+","{"];return h.find(e,function(r){return t.source.indexOf(r)!==-1})===void 0}function _r(t){var e=t.ignoreCase?"i":"";return new RegExp("^(?:"+t.source+")",e)}T.addStartOfInput=_r;function gr(t){var e=t.ignoreCase?"iy":"y";return new RegExp(""+t.source,e)}T.addStickyFlag=gr;function io(t,e,r){var n=[];return h.has(t,T.DEFAULT_MODE)||n.push({message:"A MultiMode Lexer cannot be initialized without a <"+T.DEFAULT_MODE+`> property in its definition +`,type:F.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE}),h.has(t,T.MODES)||n.push({message:"A MultiMode Lexer cannot be initialized without a <"+T.MODES+`> property in its definition +`,type:F.LexerDefinitionErrorType.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY}),h.has(t,T.MODES)&&h.has(t,T.DEFAULT_MODE)&&!h.has(t.modes,t.defaultMode)&&n.push({message:"A MultiMode Lexer cannot be initialized with a "+T.DEFAULT_MODE+": <"+t.defaultMode+`>which does not exist +`,type:F.LexerDefinitionErrorType.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST}),h.has(t,T.MODES)&&h.forEach(t.modes,function(i,a){h.forEach(i,function(o,s){h.isUndefined(o)&&n.push({message:"A Lexer cannot be initialized using an undefined Token Type. Mode:"+("<"+a+"> at index: <"+s+`> +`),type:F.LexerDefinitionErrorType.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED})})}),n}T.performRuntimeChecks=io;function ao(t,e,r){var n=[],i=!1,a=h.compact(h.flatten(h.mapValues(t.modes,function(c){return c}))),o=h.reject(a,function(c){return c[Ae]===F.Lexer.NA}),s=Tn(r);return e&&h.forEach(o,function(c){var f=En(c,s);if(f!==!1){var p=Sn(c,f),l={message:p,type:f.issue,tokenType:c};n.push(l)}else h.has(c,"LINE_BREAKS")?c.LINE_BREAKS===!0&&(i=!0):Ye.canMatchCharCode(s,c.PATTERN)&&(i=!0)}),e&&!i&&n.push({message:`Warning: No LINE_BREAKS Found. + This Lexer has been defined to track line and column information, + But none of the Token Types can be identified as matching a line terminator. + See https://chevrotain.io/docs/guide/resolving_lexer_errors.html#LINE_BREAKS + for details.`,type:F.LexerDefinitionErrorType.NO_LINE_BREAKS_FLAGS}),n}T.performWarningRuntimeChecks=ao;function oo(t){var e={},r=h.keys(t);return h.forEach(r,function(n){var i=t[n];if(h.isArray(i))e[n]=[];else throw Error("non exhaustive match")}),e}T.cloneEmptyGroups=oo;function Ar(t){var e=t.PATTERN;if(h.isRegExp(e))return!1;if(h.isFunction(e))return!0;if(h.has(e,"exec"))return!0;if(h.isString(e))return!1;throw Error("non exhaustive match")}T.isCustomPattern=Ar;function mn(t){return h.isString(t)&&t.length===1?t.charCodeAt(0):!1}T.isShortPattern=mn;T.LineTerminatorOptimizedTester={test:function(t){for(var e=t.length,r=this.lastIndex;r Token Type +`)+(" Root cause: "+e.errMsg+`. +`)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#IDENTIFY_TERMINATOR";if(e.issue===F.LexerDefinitionErrorType.CUSTOM_LINE_BREAK)return`Warning: A Custom Token Pattern should specify the option. +`+(" The problem is in the <"+t.name+`> Token Type +`)+" For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#CUSTOM_LINE_BREAK";throw Error("non exhaustive match")}T.buildLineBreakIssueMessage=Sn;function Tn(t){var e=h.map(t,function(r){return h.isString(r)&&r.length>0?r.charCodeAt(0):r});return e}function Rr(t,e,r){t[e]===void 0?t[e]=[r]:t[e].push(r)}T.minOptimizationVal=256;var Ft=[];function Or(t){return t255?255+~~(t/255):t}}});var Xe=R(N=>{"use strict";Object.defineProperty(N,"__esModule",{value:!0});N.isTokenType=N.hasExtendingTokensTypesMapProperty=N.hasExtendingTokensTypesProperty=N.hasCategoriesProperty=N.hasShortKeyProperty=N.singleAssignCategoriesToksMap=N.assignCategoriesMapProp=N.assignCategoriesTokensProp=N.assignTokenDefaultProps=N.expandCategories=N.augmentTokenTypes=N.tokenIdxToClass=N.tokenShortNameIdx=N.tokenStructuredMatcherNoCategories=N.tokenStructuredMatcher=void 0;var V=k();function so(t,e){var r=t.tokenTypeIdx;return r===e.tokenTypeIdx?!0:e.isParent===!0&&e.categoryMatchesMap[r]===!0}N.tokenStructuredMatcher=so;function uo(t,e){return t.tokenTypeIdx===e.tokenTypeIdx}N.tokenStructuredMatcherNoCategories=uo;N.tokenShortNameIdx=1;N.tokenIdxToClass={};function co(t){var e=xn(t);Cn(e),Mn(e),Ln(e),V.forEach(e,function(r){r.isParent=r.categoryMatches.length>0})}N.augmentTokenTypes=co;function xn(t){for(var e=V.cloneArr(t),r=t,n=!0;n;){r=V.compact(V.flatten(V.map(r,function(a){return a.CATEGORIES})));var i=V.difference(r,e);e=e.concat(i),V.isEmpty(i)?n=!1:r=i}return e}N.expandCategories=xn;function Cn(t){V.forEach(t,function(e){bn(e)||(N.tokenIdxToClass[N.tokenShortNameIdx]=e,e.tokenTypeIdx=N.tokenShortNameIdx++),Nr(e)&&!V.isArray(e.CATEGORIES)&&(e.CATEGORIES=[e.CATEGORIES]),Nr(e)||(e.CATEGORIES=[]),Fn(e)||(e.categoryMatches=[]),wn(e)||(e.categoryMatchesMap={})})}N.assignTokenDefaultProps=Cn;function Ln(t){V.forEach(t,function(e){e.categoryMatches=[],V.forEach(e.categoryMatchesMap,function(r,n){e.categoryMatches.push(N.tokenIdxToClass[n].tokenTypeIdx)})})}N.assignCategoriesTokensProp=Ln;function Mn(t){V.forEach(t,function(e){Ir([],e)})}N.assignCategoriesMapProp=Mn;function Ir(t,e){V.forEach(t,function(r){e.categoryMatchesMap[r.tokenTypeIdx]=!0}),V.forEach(e.CATEGORIES,function(r){var n=t.concat(e);V.contains(n,r)||Ir(n,r)})}N.singleAssignCategoriesToksMap=Ir;function bn(t){return V.has(t,"tokenTypeIdx")}N.hasShortKeyProperty=bn;function Nr(t){return V.has(t,"CATEGORIES")}N.hasCategoriesProperty=Nr;function Fn(t){return V.has(t,"categoryMatches")}N.hasExtendingTokensTypesProperty=Fn;function wn(t){return V.has(t,"categoryMatchesMap")}N.hasExtendingTokensTypesMapProperty=wn;function lo(t){return V.has(t,"tokenTypeIdx")}N.isTokenType=lo});var kr=R(wt=>{"use strict";Object.defineProperty(wt,"__esModule",{value:!0});wt.defaultLexerErrorProvider=void 0;wt.defaultLexerErrorProvider={buildUnableToPopLexerModeMessage:function(t){return"Unable to pop Lexer Mode after encountering Token ->"+t.image+"<- The Mode Stack is empty"},buildUnexpectedCharactersMessage:function(t,e,r,n,i){return"unexpected character: ->"+t.charAt(e)+"<- at offset: "+e+","+(" skipped "+r+" characters.")}}});var ft=R(qe=>{"use strict";Object.defineProperty(qe,"__esModule",{value:!0});qe.Lexer=qe.LexerDefinitionErrorType=void 0;var Ee=Tr(),w=k(),fo=Xe(),po=kr(),ho=Lt(),vo;(function(t){t[t.MISSING_PATTERN=0]="MISSING_PATTERN",t[t.INVALID_PATTERN=1]="INVALID_PATTERN",t[t.EOI_ANCHOR_FOUND=2]="EOI_ANCHOR_FOUND",t[t.UNSUPPORTED_FLAGS_FOUND=3]="UNSUPPORTED_FLAGS_FOUND",t[t.DUPLICATE_PATTERNS_FOUND=4]="DUPLICATE_PATTERNS_FOUND",t[t.INVALID_GROUP_TYPE_FOUND=5]="INVALID_GROUP_TYPE_FOUND",t[t.PUSH_MODE_DOES_NOT_EXIST=6]="PUSH_MODE_DOES_NOT_EXIST",t[t.MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE=7]="MULTI_MODE_LEXER_WITHOUT_DEFAULT_MODE",t[t.MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY=8]="MULTI_MODE_LEXER_WITHOUT_MODES_PROPERTY",t[t.MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST=9]="MULTI_MODE_LEXER_DEFAULT_MODE_VALUE_DOES_NOT_EXIST",t[t.LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED=10]="LEXER_DEFINITION_CANNOT_CONTAIN_UNDEFINED",t[t.SOI_ANCHOR_FOUND=11]="SOI_ANCHOR_FOUND",t[t.EMPTY_MATCH_PATTERN=12]="EMPTY_MATCH_PATTERN",t[t.NO_LINE_BREAKS_FLAGS=13]="NO_LINE_BREAKS_FLAGS",t[t.UNREACHABLE_PATTERN=14]="UNREACHABLE_PATTERN",t[t.IDENTIFY_TERMINATOR=15]="IDENTIFY_TERMINATOR",t[t.CUSTOM_LINE_BREAK=16]="CUSTOM_LINE_BREAK"})(vo=qe.LexerDefinitionErrorType||(qe.LexerDefinitionErrorType={}));var pt={deferDefinitionErrorsHandling:!1,positionTracking:"full",lineTerminatorsPattern:/\n|\r\n?/g,lineTerminatorCharacters:[` +`,"\r"],ensureOptimizations:!1,safeMode:!1,errorMessageProvider:po.defaultLexerErrorProvider,traceInitPerf:!1,skipValidations:!1};Object.freeze(pt);var mo=function(){function t(e,r){var n=this;if(r===void 0&&(r=pt),this.lexerDefinition=e,this.lexerDefinitionErrors=[],this.lexerDefinitionWarning=[],this.patternIdxToConfig={},this.charCodeToPatternIdxToConfig={},this.modes=[],this.emptyGroups={},this.config=void 0,this.trackStartLines=!0,this.trackEndLines=!0,this.hasCustom=!1,this.canModeBeOptimized={},typeof r=="boolean")throw Error(`The second argument to the Lexer constructor is now an ILexerConfig Object. +a boolean 2nd argument is no longer supported`);this.config=w.merge(pt,r);var i=this.config.traceInitPerf;i===!0?(this.traceInitMaxIdent=Infinity,this.traceInitPerf=!0):typeof i=="number"&&(this.traceInitMaxIdent=i,this.traceInitPerf=!0),this.traceInitIndent=-1,this.TRACE_INIT("Lexer Constructor",function(){var a,o=!0;n.TRACE_INIT("Lexer Config handling",function(){if(n.config.lineTerminatorsPattern===pt.lineTerminatorsPattern)n.config.lineTerminatorsPattern=Ee.LineTerminatorOptimizedTester;else if(n.config.lineTerminatorCharacters===pt.lineTerminatorCharacters)throw Error(`Error: Missing property on the Lexer config. + For details See: https://chevrotain.io/docs/guide/resolving_lexer_errors.html#MISSING_LINE_TERM_CHARS`);if(r.safeMode&&r.ensureOptimizations)throw Error('"safeMode" and "ensureOptimizations" flags are mutually exclusive.');n.trackStartLines=/full|onlyStart/i.test(n.config.positionTracking),n.trackEndLines=/full/i.test(n.config.positionTracking),w.isArray(e)?(a={modes:{}},a.modes[Ee.DEFAULT_MODE]=w.cloneArr(e),a[Ee.DEFAULT_MODE]=Ee.DEFAULT_MODE):(o=!1,a=w.cloneObj(e))}),n.config.skipValidations===!1&&(n.TRACE_INIT("performRuntimeChecks",function(){n.lexerDefinitionErrors=n.lexerDefinitionErrors.concat(Ee.performRuntimeChecks(a,n.trackStartLines,n.config.lineTerminatorCharacters))}),n.TRACE_INIT("performWarningRuntimeChecks",function(){n.lexerDefinitionWarning=n.lexerDefinitionWarning.concat(Ee.performWarningRuntimeChecks(a,n.trackStartLines,n.config.lineTerminatorCharacters))})),a.modes=a.modes?a.modes:{},w.forEach(a.modes,function(p,l){a.modes[l]=w.reject(p,function(m){return w.isUndefined(m)})});var s=w.keys(a.modes);if(w.forEach(a.modes,function(p,l){n.TRACE_INIT("Mode: <"+l+"> processing",function(){if(n.modes.push(l),n.config.skipValidations===!1&&n.TRACE_INIT("validatePatterns",function(){n.lexerDefinitionErrors=n.lexerDefinitionErrors.concat(Ee.validatePatterns(p,s))}),w.isEmpty(n.lexerDefinitionErrors)){fo.augmentTokenTypes(p);var m;n.TRACE_INIT("analyzeTokenTypes",function(){m=Ee.analyzeTokenTypes(p,{lineTerminatorCharacters:n.config.lineTerminatorCharacters,positionTracking:r.positionTracking,ensureOptimizations:r.ensureOptimizations,safeMode:r.safeMode,tracer:n.TRACE_INIT.bind(n)})}),n.patternIdxToConfig[l]=m.patternIdxToConfig,n.charCodeToPatternIdxToConfig[l]=m.charCodeToPatternIdxToConfig,n.emptyGroups=w.merge(n.emptyGroups,m.emptyGroups),n.hasCustom=m.hasCustom||n.hasCustom,n.canModeBeOptimized[l]=m.canBeOptimized}})}),n.defaultMode=a.defaultMode,!w.isEmpty(n.lexerDefinitionErrors)&&!n.config.deferDefinitionErrorsHandling){var c=w.map(n.lexerDefinitionErrors,function(p){return p.message}),f=c.join(`----------------------- +`);throw new Error(`Errors detected in definition of Lexer: +`+f)}w.forEach(n.lexerDefinitionWarning,function(p){w.PRINT_WARNING(p.message)}),n.TRACE_INIT("Choosing sub-methods implementations",function(){if(Ee.SUPPORT_STICKY?(n.chopInput=w.IDENTITY,n.match=n.matchWithTest):(n.updateLastIndex=w.NOOP,n.match=n.matchWithExec),o&&(n.handleModes=w.NOOP),n.trackStartLines===!1&&(n.computeNewColumn=w.IDENTITY),n.trackEndLines===!1&&(n.updateTokenEndLineColumnLocation=w.NOOP),/full/i.test(n.config.positionTracking))n.createTokenInstance=n.createFullToken;else if(/onlyStart/i.test(n.config.positionTracking))n.createTokenInstance=n.createStartOnlyToken;else if(/onlyOffset/i.test(n.config.positionTracking))n.createTokenInstance=n.createOffsetOnlyToken;else throw Error('Invalid config option: "'+n.config.positionTracking+'"');n.hasCustom?(n.addToken=n.addTokenUsingPush,n.handlePayload=n.handlePayloadWithCustom):(n.addToken=n.addTokenUsingMemberAccess,n.handlePayload=n.handlePayloadNoCustom)}),n.TRACE_INIT("Failed Optimization Warnings",function(){var p=w.reduce(n.canModeBeOptimized,function(l,m,v){return m===!1&&l.push(v),l},[]);if(r.ensureOptimizations&&!w.isEmpty(p))throw Error("Lexer Modes: < "+p.join(", ")+` > cannot be optimized. + Disable the "ensureOptimizations" lexer config flag to silently ignore this and run the lexer in an un-optimized mode. + Or inspect the console log for details on how to resolve these issues.`)}),n.TRACE_INIT("clearRegExpParserCache",function(){ho.clearRegExpParserCache()}),n.TRACE_INIT("toFastProperties",function(){w.toFastProperties(n)})})}return t.prototype.tokenize=function(e,r){if(r===void 0&&(r=this.defaultMode),!w.isEmpty(this.lexerDefinitionErrors)){var n=w.map(this.lexerDefinitionErrors,function(o){return o.message}),i=n.join(`----------------------- +`);throw new Error(`Unable to Tokenize because Errors detected in definition of Lexer: +`+i)}var a=this.tokenizeInternal(e,r);return a},t.prototype.tokenizeInternal=function(e,r){var n=this,i,a,o,s,c,f,p,l,m,v,u,d,A,_,g,y=e,b=y.length,L=0,se=0,fe=this.hasCustom?0:Math.floor(e.length/10),Z=new Array(fe),ue=[],Q=this.trackStartLines?1:void 0,te=this.trackStartLines?1:void 0,xe=Ee.cloneEmptyGroups(this.emptyGroups),it=this.trackStartLines,at=this.config.lineTerminatorsPattern,Ke=0,we=[],ot=[],It=[],Qr=[];Object.freeze(Qr);var st=void 0;function Jr(){return we}function en(J){var lt=Ee.charCodeToOptimizedIndex(J),ze=ot[lt];return ze===void 0?Qr:ze}var wa=function(J){if(It.length===1&&J.tokenType.PUSH_MODE===void 0){var lt=n.config.errorMessageProvider.buildUnableToPopLexerModeMessage(J);ue.push({offset:J.startOffset,line:J.startLine!==void 0?J.startLine:void 0,column:J.startColumn!==void 0?J.startColumn:void 0,length:J.image.length,message:lt})}else{It.pop();var ze=w.last(It);we=n.patternIdxToConfig[ze],ot=n.charCodeToPatternIdxToConfig[ze],Ke=we.length;var Ua=n.canModeBeOptimized[ze]&&n.config.safeMode===!1;ot&&Ua?st=en:st=Jr}};function tn(J){It.push(J),ot=this.charCodeToPatternIdxToConfig[J],we=this.patternIdxToConfig[J],Ke=we.length,Ke=we.length;var lt=this.canModeBeOptimized[J]&&this.config.safeMode===!1;ot&<?st=en:st=Jr}tn.call(this,r);for(var me;Lc.length&&(c=o,f=p,me=fr)}break}}if(c!==null){if(l=c.length,m=me.group,m!==void 0&&(v=me.tokenTypeIdx,u=this.createTokenInstance(c,L,v,me.tokenType,Q,te,l),this.handlePayload(u,f),m===!1?se=this.addToken(Z,se,u):xe[m].push(u)),e=this.chopInput(e,l),L=L+l,te=this.computeNewColumn(te,l),it===!0&&me.canLineTerminator===!0){var kt=0,hr=void 0,dr=void 0;at.lastIndex=0;do hr=at.test(c),hr===!0&&(dr=at.lastIndex-1,kt++);while(hr===!0);kt!==0&&(Q=Q+kt,te=l-dr,this.updateTokenEndLineColumnLocation(u,m,dr,kt,Q,te,l))}this.handleModes(me,wa,tn,u)}else{for(var vr=L,an=Q,on=te,ct=!1;!ct&&L <"+e+">");var i=w.timer(r),a=i.time,o=i.value,s=a>10?console.warn:console.log;return this.traceInitIndent time: "+a+"ms"),this.traceInitIndent--,o}else return r()},t.SKIPPED="This marks a skipped Token pattern, this means each token identified by it willbe consumed and then thrown into oblivion, this can be used to for example to completely ignore whitespace.",t.NA=/NOT_APPLICABLE/,t}();qe.Lexer=mo});var Ue=R(H=>{"use strict";Object.defineProperty(H,"__esModule",{value:!0});H.tokenMatcher=H.createTokenInstance=H.EOF=H.createToken=H.hasTokenLabel=H.tokenName=H.tokenLabel=void 0;var Te=k(),Eo=ft(),Pr=Xe();function To(t){return Dn(t)?t.LABEL:t.name}H.tokenLabel=To;function yo(t){return t.name}H.tokenName=yo;function Dn(t){return Te.isString(t.LABEL)&&t.LABEL!==""}H.hasTokenLabel=Dn;var _o="parent",Un="categories",Gn="label",Wn="group",Bn="push_mode",qn="pop_mode",jn="longer_alt",Vn="line_breaks",Kn="start_chars_hint";function zn(t){return go(t)}H.createToken=zn;function go(t){var e=t.pattern,r={};if(r.name=t.name,Te.isUndefined(e)||(r.PATTERN=e),Te.has(t,_o))throw`The parent property is no longer supported. +See: https://github.com/chevrotain/chevrotain/issues/564#issuecomment-349062346 for details.`;return Te.has(t,Un)&&(r.CATEGORIES=t[Un]),Pr.augmentTokenTypes([r]),Te.has(t,Gn)&&(r.LABEL=t[Gn]),Te.has(t,Wn)&&(r.GROUP=t[Wn]),Te.has(t,qn)&&(r.POP_MODE=t[qn]),Te.has(t,Bn)&&(r.PUSH_MODE=t[Bn]),Te.has(t,jn)&&(r.LONGER_ALT=t[jn]),Te.has(t,Vn)&&(r.LINE_BREAKS=t[Vn]),Te.has(t,Kn)&&(r.START_CHARS_HINT=t[Kn]),r}H.EOF=zn({name:"EOF",pattern:Eo.Lexer.NA});Pr.augmentTokenTypes([H.EOF]);function Ao(t,e,r,n,i,a,o,s){return{image:e,startOffset:r,endOffset:n,startLine:i,endLine:a,startColumn:o,endColumn:s,tokenTypeIdx:t.tokenTypeIdx,tokenType:t}}H.createTokenInstance=Ao;function Ro(t,e){return Pr.tokenStructuredMatcher(t,e)}H.tokenMatcher=Ro});var ne=R(S=>{"use strict";var Le=S&&S.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(S,"__esModule",{value:!0});S.serializeProduction=S.serializeGrammar=S.Terminal=S.Alternation=S.RepetitionWithSeparator=S.Repetition=S.RepetitionMandatoryWithSeparator=S.RepetitionMandatory=S.Option=S.Alternative=S.Rule=S.NonTerminal=S.AbstractProduction=void 0;var G=k(),Oo=Ue(),Re=function(){function t(e){this._definition=e}return Object.defineProperty(t.prototype,"definition",{get:function(){return this._definition},set:function(e){this._definition=e},enumerable:!1,configurable:!0}),t.prototype.accept=function(e){e.visit(this),G.forEach(this.definition,function(r){r.accept(e)})},t}();S.AbstractProduction=Re;var Hn=function(t){Le(e,t);function e(r){var n=t.call(this,[])||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return Object.defineProperty(e.prototype,"definition",{get:function(){return this.referencedRule!==void 0?this.referencedRule.definition:[]},set:function(r){},enumerable:!1,configurable:!0}),e.prototype.accept=function(r){r.visit(this)},e}(Re);S.NonTerminal=Hn;var Yn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.orgText="",G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Rule=Yn;var Xn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.ignoreAmbiguities=!1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Alternative=Xn;var $n=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Option=$n;var Zn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.RepetitionMandatory=Zn;var Qn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.RepetitionMandatoryWithSeparator=Qn;var Jn=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.Repetition=Jn;var ei=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return e}(Re);S.RepetitionWithSeparator=ei;var ti=function(t){Le(e,t);function e(r){var n=t.call(this,r.definition)||this;return n.idx=1,n.ignoreAmbiguities=!1,n.hasPredicates=!1,G.assign(n,G.pick(r,function(i){return i!==void 0})),n}return Object.defineProperty(e.prototype,"definition",{get:function(){return this._definition},set:function(r){this._definition=r},enumerable:!1,configurable:!0}),e}(Re);S.Alternation=ti;var Dt=function(){function t(e){this.idx=1,G.assign(this,G.pick(e,function(r){return r!==void 0}))}return t.prototype.accept=function(e){e.visit(this)},t}();S.Terminal=Dt;function No(t){return G.map(t,ht)}S.serializeGrammar=No;function ht(t){function e(i){return G.map(i,ht)}if(t instanceof Hn)return{type:"NonTerminal",name:t.nonTerminalName,idx:t.idx};if(t instanceof Xn)return{type:"Alternative",definition:e(t.definition)};if(t instanceof $n)return{type:"Option",idx:t.idx,definition:e(t.definition)};if(t instanceof Zn)return{type:"RepetitionMandatory",idx:t.idx,definition:e(t.definition)};if(t instanceof Qn)return{type:"RepetitionMandatoryWithSeparator",idx:t.idx,separator:ht(new Dt({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof ei)return{type:"RepetitionWithSeparator",idx:t.idx,separator:ht(new Dt({terminalType:t.separator})),definition:e(t.definition)};if(t instanceof Jn)return{type:"Repetition",idx:t.idx,definition:e(t.definition)};if(t instanceof ti)return{type:"Alternation",idx:t.idx,definition:e(t.definition)};if(t instanceof Dt){var r={type:"Terminal",name:t.terminalType.name,label:Oo.tokenLabel(t.terminalType),idx:t.idx},n=t.terminalType.PATTERN;return t.terminalType.PATTERN&&(r.pattern=G.isRegExp(n)?n.source:n),r}else{if(t instanceof Yn)return{type:"Rule",name:t.name,orgText:t.orgText,definition:e(t.definition)};throw Error("non exhaustive match")}}S.serializeProduction=ht});var Gt=R(Ut=>{"use strict";Object.defineProperty(Ut,"__esModule",{value:!0});Ut.RestWalker=void 0;var Sr=k(),ie=ne(),Io=function(){function t(){}return t.prototype.walk=function(e,r){var n=this;r===void 0&&(r=[]),Sr.forEach(e.definition,function(i,a){var o=Sr.drop(e.definition,a+1);if(i instanceof ie.NonTerminal)n.walkProdRef(i,o,r);else if(i instanceof ie.Terminal)n.walkTerminal(i,o,r);else if(i instanceof ie.Alternative)n.walkFlat(i,o,r);else if(i instanceof ie.Option)n.walkOption(i,o,r);else if(i instanceof ie.RepetitionMandatory)n.walkAtLeastOne(i,o,r);else if(i instanceof ie.RepetitionMandatoryWithSeparator)n.walkAtLeastOneSep(i,o,r);else if(i instanceof ie.RepetitionWithSeparator)n.walkManySep(i,o,r);else if(i instanceof ie.Repetition)n.walkMany(i,o,r);else if(i instanceof ie.Alternation)n.walkOr(i,o,r);else throw Error("non exhaustive match")})},t.prototype.walkTerminal=function(e,r,n){},t.prototype.walkProdRef=function(e,r,n){},t.prototype.walkFlat=function(e,r,n){var i=r.concat(n);this.walk(e,i)},t.prototype.walkOption=function(e,r,n){var i=r.concat(n);this.walk(e,i)},t.prototype.walkAtLeastOne=function(e,r,n){var i=[new ie.Option({definition:e.definition})].concat(r,n);this.walk(e,i)},t.prototype.walkAtLeastOneSep=function(e,r,n){var i=ri(e,r,n);this.walk(e,i)},t.prototype.walkMany=function(e,r,n){var i=[new ie.Option({definition:e.definition})].concat(r,n);this.walk(e,i)},t.prototype.walkManySep=function(e,r,n){var i=ri(e,r,n);this.walk(e,i)},t.prototype.walkOr=function(e,r,n){var i=this,a=r.concat(n);Sr.forEach(e.definition,function(o){var s=new ie.Alternative({definition:[o]});i.walk(s,a)})},t}();Ut.RestWalker=Io;function ri(t,e,r){var n=[new ie.Option({definition:[new ie.Terminal({terminalType:t.separator})].concat(t.definition)})],i=n.concat(e,r);return i}});var $e=R(Wt=>{"use strict";Object.defineProperty(Wt,"__esModule",{value:!0});Wt.GAstVisitor=void 0;var Oe=ne(),ko=function(){function t(){}return t.prototype.visit=function(e){var r=e;switch(r.constructor){case Oe.NonTerminal:return this.visitNonTerminal(r);case Oe.Alternative:return this.visitAlternative(r);case Oe.Option:return this.visitOption(r);case Oe.RepetitionMandatory:return this.visitRepetitionMandatory(r);case Oe.RepetitionMandatoryWithSeparator:return this.visitRepetitionMandatoryWithSeparator(r);case Oe.RepetitionWithSeparator:return this.visitRepetitionWithSeparator(r);case Oe.Repetition:return this.visitRepetition(r);case Oe.Alternation:return this.visitAlternation(r);case Oe.Terminal:return this.visitTerminal(r);case Oe.Rule:return this.visitRule(r);default:throw Error("non exhaustive match")}},t.prototype.visitNonTerminal=function(e){},t.prototype.visitAlternative=function(e){},t.prototype.visitOption=function(e){},t.prototype.visitRepetition=function(e){},t.prototype.visitRepetitionMandatory=function(e){},t.prototype.visitRepetitionMandatoryWithSeparator=function(e){},t.prototype.visitRepetitionWithSeparator=function(e){},t.prototype.visitAlternation=function(e){},t.prototype.visitTerminal=function(e){},t.prototype.visitRule=function(e){},t}();Wt.GAstVisitor=ko});var vt=R(X=>{"use strict";var Po=X&&X.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(X,"__esModule",{value:!0});X.collectMethods=X.DslMethodsCollectorVisitor=X.getProductionDslName=X.isBranchingProd=X.isOptionalProd=X.isSequenceProd=void 0;var dt=k(),W=ne(),So=$e();function xo(t){return t instanceof W.Alternative||t instanceof W.Option||t instanceof W.Repetition||t instanceof W.RepetitionMandatory||t instanceof W.RepetitionMandatoryWithSeparator||t instanceof W.RepetitionWithSeparator||t instanceof W.Terminal||t instanceof W.Rule}X.isSequenceProd=xo;function xr(t,e){e===void 0&&(e=[]);var r=t instanceof W.Option||t instanceof W.Repetition||t instanceof W.RepetitionWithSeparator;return r?!0:t instanceof W.Alternation?dt.some(t.definition,function(n){return xr(n,e)}):t instanceof W.NonTerminal&&dt.contains(e,t)?!1:t instanceof W.AbstractProduction?(t instanceof W.NonTerminal&&e.push(t),dt.every(t.definition,function(n){return xr(n,e)})):!1}X.isOptionalProd=xr;function Co(t){return t instanceof W.Alternation}X.isBranchingProd=Co;function Lo(t){if(t instanceof W.NonTerminal)return"SUBRULE";if(t instanceof W.Option)return"OPTION";if(t instanceof W.Alternation)return"OR";if(t instanceof W.RepetitionMandatory)return"AT_LEAST_ONE";if(t instanceof W.RepetitionMandatoryWithSeparator)return"AT_LEAST_ONE_SEP";if(t instanceof W.RepetitionWithSeparator)return"MANY_SEP";if(t instanceof W.Repetition)return"MANY";if(t instanceof W.Terminal)return"CONSUME";throw Error("non exhaustive match")}X.getProductionDslName=Lo;var ni=function(t){Po(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.separator="-",r.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]},r}return e.prototype.reset=function(){this.dslMethods={option:[],alternation:[],repetition:[],repetitionWithSeparator:[],repetitionMandatory:[],repetitionMandatoryWithSeparator:[]}},e.prototype.visitTerminal=function(r){var n=r.terminalType.name+this.separator+"Terminal";dt.has(this.dslMethods,n)||(this.dslMethods[n]=[]),this.dslMethods[n].push(r)},e.prototype.visitNonTerminal=function(r){var n=r.nonTerminalName+this.separator+"Terminal";dt.has(this.dslMethods,n)||(this.dslMethods[n]=[]),this.dslMethods[n].push(r)},e.prototype.visitOption=function(r){this.dslMethods.option.push(r)},e.prototype.visitRepetitionWithSeparator=function(r){this.dslMethods.repetitionWithSeparator.push(r)},e.prototype.visitRepetitionMandatory=function(r){this.dslMethods.repetitionMandatory.push(r)},e.prototype.visitRepetitionMandatoryWithSeparator=function(r){this.dslMethods.repetitionMandatoryWithSeparator.push(r)},e.prototype.visitRepetition=function(r){this.dslMethods.repetition.push(r)},e.prototype.visitAlternation=function(r){this.dslMethods.alternation.push(r)},e}(So.GAstVisitor);X.DslMethodsCollectorVisitor=ni;var Bt=new ni;function Mo(t){Bt.reset(),t.accept(Bt);var e=Bt.dslMethods;return Bt.reset(),e}X.collectMethods=Mo});var Lr=R(Ne=>{"use strict";Object.defineProperty(Ne,"__esModule",{value:!0});Ne.firstForTerminal=Ne.firstForBranching=Ne.firstForSequence=Ne.first=void 0;var qt=k(),ii=ne(),Cr=vt();function jt(t){if(t instanceof ii.NonTerminal)return jt(t.referencedRule);if(t instanceof ii.Terminal)return si(t);if(Cr.isSequenceProd(t))return ai(t);if(Cr.isBranchingProd(t))return oi(t);throw Error("non exhaustive match")}Ne.first=jt;function ai(t){for(var e=[],r=t.definition,n=0,i=r.length>n,a,o=!0;i&&o;)a=r[n],o=Cr.isOptionalProd(a),e=e.concat(jt(a)),n=n+1,i=r.length>n;return qt.uniq(e)}Ne.firstForSequence=ai;function oi(t){var e=qt.map(t.definition,function(r){return jt(r)});return qt.uniq(qt.flatten(e))}Ne.firstForBranching=oi;function si(t){return[t.terminalType]}Ne.firstForTerminal=si});var Mr=R(Vt=>{"use strict";Object.defineProperty(Vt,"__esModule",{value:!0});Vt.IN=void 0;Vt.IN="_~IN~_"});var pi=R(he=>{"use strict";var bo=he&&he.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(he,"__esModule",{value:!0});he.buildInProdFollowPrefix=he.buildBetweenProdsFollowPrefix=he.computeAllProdsFollows=he.ResyncFollowsWalker=void 0;var Fo=Gt(),wo=Lr(),ui=k(),ci=Mr(),Do=ne(),fi=function(t){bo(e,t);function e(r){var n=t.call(this)||this;return n.topProd=r,n.follows={},n}return e.prototype.startWalking=function(){return this.walk(this.topProd),this.follows},e.prototype.walkTerminal=function(r,n,i){},e.prototype.walkProdRef=function(r,n,i){var a=li(r.referencedRule,r.idx)+this.topProd.name,o=n.concat(i),s=new Do.Alternative({definition:o}),c=wo.first(s);this.follows[a]=c},e}(Fo.RestWalker);he.ResyncFollowsWalker=fi;function Uo(t){var e={};return ui.forEach(t,function(r){var n=new fi(r).startWalking();ui.assign(e,n)}),e}he.computeAllProdsFollows=Uo;function li(t,e){return t.name+e+ci.IN}he.buildBetweenProdsFollowPrefix=li;function Go(t){var e=t.terminalType.name;return e+t.idx+ci.IN}he.buildInProdFollowPrefix=Go});var mt=R(Me=>{"use strict";Object.defineProperty(Me,"__esModule",{value:!0});Me.defaultGrammarValidatorErrorProvider=Me.defaultGrammarResolverErrorProvider=Me.defaultParserErrorProvider=void 0;var Ze=Ue(),Wo=k(),ye=k(),br=ne(),hi=vt();Me.defaultParserErrorProvider={buildMismatchTokenMessage:function(t){var e=t.expected,r=t.actual,n=t.previous,i=t.ruleName,a=Ze.hasTokenLabel(e),o=a?"--> "+Ze.tokenLabel(e)+" <--":"token of type --> "+e.name+" <--",s="Expecting "+o+" but found --> '"+r.image+"' <--";return s},buildNotAllInputParsedMessage:function(t){var e=t.firstRedundant,r=t.ruleName;return"Redundant input, expecting EOF but found: "+e.image},buildNoViableAltMessage:function(t){var e=t.expectedPathsPerAlt,r=t.actual,n=t.previous,i=t.customUserDescription,a=t.ruleName,o="Expecting: ",s=ye.first(r).image,c=` +but found: '`+s+"'";if(i)return o+i+c;var f=ye.reduce(e,function(v,u){return v.concat(u)},[]),p=ye.map(f,function(v){return"["+ye.map(v,function(u){return Ze.tokenLabel(u)}).join(", ")+"]"}),l=ye.map(p,function(v,u){return" "+(u+1)+". "+v}),m=`one of these possible Token sequences: +`+l.join(` +`);return o+m+c},buildEarlyExitMessage:function(t){var e=t.expectedIterationPaths,r=t.actual,n=t.customUserDescription,i=t.ruleName,a="Expecting: ",o=ye.first(r).image,s=` +but found: '`+o+"'";if(n)return a+n+s;var c=ye.map(e,function(p){return"["+ye.map(p,function(l){return Ze.tokenLabel(l)}).join(",")+"]"}),f=`expecting at least one iteration which starts with one of these possible Token sequences:: + `+("<"+c.join(" ,")+">");return a+f+s}};Object.freeze(Me.defaultParserErrorProvider);Me.defaultGrammarResolverErrorProvider={buildRuleNotFoundError:function(t,e){var r="Invalid grammar, reference to a rule which is not defined: ->"+e.nonTerminalName+`<- +inside top level rule: ->`+t.name+"<-";return r}};Me.defaultGrammarValidatorErrorProvider={buildDuplicateFoundError:function(t,e){function r(p){return p instanceof br.Terminal?p.terminalType.name:p instanceof br.NonTerminal?p.nonTerminalName:""}var n=t.name,i=ye.first(e),a=i.idx,o=hi.getProductionDslName(i),s=r(i),c=a>0,f="->"+o+(c?a:"")+"<- "+(s?"with argument: ->"+s+"<-":"")+` + appears more than once (`+e.length+" times) in the top level rule: ->"+n+`<-. + For further details see: https://chevrotain.io/docs/FAQ.html#NUMERICAL_SUFFIXES + `;return f=f.replace(/[ \t]+/g," "),f=f.replace(/\s\s+/g,` +`),f},buildNamespaceConflictError:function(t){var e=`Namespace conflict found in grammar. +`+("The grammar has both a Terminal(Token) and a Non-Terminal(Rule) named: <"+t.name+`>. +`)+`To resolve this make sure each Terminal and Non-Terminal names are unique +This is easy to accomplish by using the convention that Terminal names start with an uppercase letter +and Non-Terminal names start with a lower case letter.`;return e},buildAlternationPrefixAmbiguityError:function(t){var e=ye.map(t.prefixPath,function(i){return Ze.tokenLabel(i)}).join(", "),r=t.alternation.idx===0?"":t.alternation.idx,n="Ambiguous alternatives: <"+t.ambiguityIndices.join(" ,")+`> due to common lookahead prefix +`+("in inside <"+t.topLevelRule.name+`> Rule, +`)+("<"+e+`> may appears as a prefix path in all these alternatives. +`)+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#COMMON_PREFIX +For Further details.`;return n},buildAlternationAmbiguityError:function(t){var e=ye.map(t.prefixPath,function(i){return Ze.tokenLabel(i)}).join(", "),r=t.alternation.idx===0?"":t.alternation.idx,n="Ambiguous Alternatives Detected: <"+t.ambiguityIndices.join(" ,")+"> in "+(" inside <"+t.topLevelRule.name+`> Rule, +`)+("<"+e+`> may appears as a prefix path in all these alternatives. +`);return n=n+`See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#AMBIGUOUS_ALTERNATIVES +For Further details.`,n},buildEmptyRepetitionError:function(t){var e=hi.getProductionDslName(t.repetition);t.repetition.idx!==0&&(e+=t.repetition.idx);var r="The repetition <"+e+"> within Rule <"+t.topLevelRule.name+`> can never consume any tokens. +This could lead to an infinite loop.`;return r},buildTokenNameError:function(t){return"deprecated"},buildEmptyAlternationError:function(t){var e="Ambiguous empty alternative: <"+(t.emptyChoiceIdx+1)+">"+(" in inside <"+t.topLevelRule.name+`> Rule. +`)+"Only the last alternative may be an empty alternative.";return e},buildTooManyAlternativesError:function(t){var e=`An Alternation cannot have more than 256 alternatives: +`+(" inside <"+t.topLevelRule.name+`> Rule. + has `+(t.alternation.definition.length+1)+" alternatives.");return e},buildLeftRecursionError:function(t){var e=t.topLevelRule.name,r=Wo.map(t.leftRecursionPath,function(a){return a.name}),n=e+" --> "+r.concat([e]).join(" --> "),i=`Left Recursion found in grammar. +`+("rule: <"+e+`> can be invoked from itself (directly or indirectly) +`)+(`without consuming any Tokens. The grammar path that causes this is: + `+n+` +`)+` To fix this refactor your grammar to remove the left recursion. +see: https://en.wikipedia.org/wiki/LL_parser#Left_Factoring.`;return i},buildInvalidRuleNameError:function(t){return"deprecated"},buildDuplicateRuleNameError:function(t){var e;t.topLevelRule instanceof br.Rule?e=t.topLevelRule.name:e=t.topLevelRule;var r="Duplicate definition, rule: ->"+e+"<- is already defined in the grammar: ->"+t.grammarName+"<-";return r}}});var mi=R(Ge=>{"use strict";var Bo=Ge&&Ge.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(Ge,"__esModule",{value:!0});Ge.GastRefResolverVisitor=Ge.resolveGrammar=void 0;var qo=ce(),di=k(),jo=$e();function Vo(t,e){var r=new vi(t,e);return r.resolveRefs(),r.errors}Ge.resolveGrammar=Vo;var vi=function(t){Bo(e,t);function e(r,n){var i=t.call(this)||this;return i.nameToTopRule=r,i.errMsgProvider=n,i.errors=[],i}return e.prototype.resolveRefs=function(){var r=this;di.forEach(di.values(this.nameToTopRule),function(n){r.currTopLevel=n,n.accept(r)})},e.prototype.visitNonTerminal=function(r){var n=this.nameToTopRule[r.nonTerminalName];if(n)r.referencedRule=n;else{var i=this.errMsgProvider.buildRuleNotFoundError(this.currTopLevel,r);this.errors.push({message:i,type:qo.ParserDefinitionErrorType.UNRESOLVED_SUBRULE_REF,ruleName:this.currTopLevel.name,unresolvedRefName:r.nonTerminalName})}},e}(jo.GAstVisitor);Ge.GastRefResolverVisitor=vi});var Tt=R(j=>{"use strict";var je=j&&j.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(j,"__esModule",{value:!0});j.nextPossibleTokensAfter=j.possiblePathsFrom=j.NextTerminalAfterAtLeastOneSepWalker=j.NextTerminalAfterAtLeastOneWalker=j.NextTerminalAfterManySepWalker=j.NextTerminalAfterManyWalker=j.AbstractNextTerminalAfterProductionWalker=j.NextAfterTokenWalker=j.AbstractNextPossibleTokensWalker=void 0;var Ei=Gt(),I=k(),Ko=Lr(),O=ne(),Ti=function(t){je(e,t);function e(r,n){var i=t.call(this)||this;return i.topProd=r,i.path=n,i.possibleTokTypes=[],i.nextProductionName="",i.nextProductionOccurrence=0,i.found=!1,i.isAtEndOfPath=!1,i}return e.prototype.startWalking=function(){if(this.found=!1,this.path.ruleStack[0]!==this.topProd.name)throw Error("The path does not start with the walker's top Rule!");return this.ruleStack=I.cloneArr(this.path.ruleStack).reverse(),this.occurrenceStack=I.cloneArr(this.path.occurrenceStack).reverse(),this.ruleStack.pop(),this.occurrenceStack.pop(),this.updateExpectedNext(),this.walk(this.topProd),this.possibleTokTypes},e.prototype.walk=function(r,n){n===void 0&&(n=[]),this.found||t.prototype.walk.call(this,r,n)},e.prototype.walkProdRef=function(r,n,i){if(r.referencedRule.name===this.nextProductionName&&r.idx===this.nextProductionOccurrence){var a=n.concat(i);this.updateExpectedNext(),this.walk(r.referencedRule,a)}},e.prototype.updateExpectedNext=function(){I.isEmpty(this.ruleStack)?(this.nextProductionName="",this.nextProductionOccurrence=0,this.isAtEndOfPath=!0):(this.nextProductionName=this.ruleStack.pop(),this.nextProductionOccurrence=this.occurrenceStack.pop())},e}(Ei.RestWalker);j.AbstractNextPossibleTokensWalker=Ti;var zo=function(t){je(e,t);function e(r,n){var i=t.call(this,r,n)||this;return i.path=n,i.nextTerminalName="",i.nextTerminalOccurrence=0,i.nextTerminalName=i.path.lastTok.name,i.nextTerminalOccurrence=i.path.lastTokOccurrence,i}return e.prototype.walkTerminal=function(r,n,i){if(this.isAtEndOfPath&&r.terminalType.name===this.nextTerminalName&&r.idx===this.nextTerminalOccurrence&&!this.found){var a=n.concat(i),o=new O.Alternative({definition:a});this.possibleTokTypes=Ko.first(o),this.found=!0}},e}(Ti);j.NextAfterTokenWalker=zo;var Et=function(t){je(e,t);function e(r,n){var i=t.call(this)||this;return i.topRule=r,i.occurrence=n,i.result={token:void 0,occurrence:void 0,isEndOfRule:void 0},i}return e.prototype.startWalking=function(){return this.walk(this.topRule),this.result},e}(Ei.RestWalker);j.AbstractNextTerminalAfterProductionWalker=Et;var Ho=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkMany=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkMany.call(this,r,n,i)},e}(Et);j.NextTerminalAfterManyWalker=Ho;var Yo=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkManySep=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkManySep.call(this,r,n,i)},e}(Et);j.NextTerminalAfterManySepWalker=Yo;var Xo=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkAtLeastOne=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkAtLeastOne.call(this,r,n,i)},e}(Et);j.NextTerminalAfterAtLeastOneWalker=Xo;var $o=function(t){je(e,t);function e(){return t!==null&&t.apply(this,arguments)||this}return e.prototype.walkAtLeastOneSep=function(r,n,i){if(r.idx===this.occurrence){var a=I.first(n.concat(i));this.result.isEndOfRule=a===void 0,a instanceof O.Terminal&&(this.result.token=a.terminalType,this.result.occurrence=a.idx)}else t.prototype.walkAtLeastOneSep.call(this,r,n,i)},e}(Et);j.NextTerminalAfterAtLeastOneSepWalker=$o;function yi(t,e,r){r===void 0&&(r=[]),r=I.cloneArr(r);var n=[],i=0;function a(f){return f.concat(I.drop(t,i+1))}function o(f){var p=yi(a(f),e,r);return n.concat(p)}for(;r.length=0;it--){var at=_.definition[it],Ke={idx:u,def:at.definition.concat(I.drop(v)),ruleStack:d,occurrenceStack:A};l.push(Ke),l.push(o)}else if(_ instanceof O.Alternative)l.push({idx:u,def:_.definition.concat(I.drop(v)),ruleStack:d,occurrenceStack:A});else if(_ instanceof O.Rule)l.push(Zo(_,u,d,A));else throw Error("non exhaustive match")}}return p}j.nextPossibleTokensAfter=Qo;function Zo(t,e,r,n){var i=I.cloneArr(r);i.push(t.name);var a=I.cloneArr(n);return a.push(1),{idx:e,def:t.definition,ruleStack:i,occurrenceStack:a}}});var yt=R(C=>{"use strict";var _i=C&&C.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(C,"__esModule",{value:!0});C.areTokenCategoriesNotUsed=C.isStrictPrefixOfPath=C.containsPath=C.getLookaheadPathsForOptionalProd=C.getLookaheadPathsForOr=C.lookAheadSequenceFromAlternatives=C.buildSingleAlternativeLookaheadFunction=C.buildAlternativesLookAheadFunc=C.buildLookaheadFuncForOptionalProd=C.buildLookaheadFuncForOr=C.getProdType=C.PROD_TYPE=void 0;var D=k(),gi=Tt(),Jo=Gt(),Kt=Xe(),We=ne(),es=$e(),z;(function(t){t[t.OPTION=0]="OPTION",t[t.REPETITION=1]="REPETITION",t[t.REPETITION_MANDATORY=2]="REPETITION_MANDATORY",t[t.REPETITION_MANDATORY_WITH_SEPARATOR=3]="REPETITION_MANDATORY_WITH_SEPARATOR",t[t.REPETITION_WITH_SEPARATOR=4]="REPETITION_WITH_SEPARATOR",t[t.ALTERNATION=5]="ALTERNATION"})(z=C.PROD_TYPE||(C.PROD_TYPE={}));function ts(t){if(t instanceof We.Option)return z.OPTION;if(t instanceof We.Repetition)return z.REPETITION;if(t instanceof We.RepetitionMandatory)return z.REPETITION_MANDATORY;if(t instanceof We.RepetitionMandatoryWithSeparator)return z.REPETITION_MANDATORY_WITH_SEPARATOR;if(t instanceof We.RepetitionWithSeparator)return z.REPETITION_WITH_SEPARATOR;if(t instanceof We.Alternation)return z.ALTERNATION;throw Error("non exhaustive match")}C.getProdType=ts;function rs(t,e,r,n,i,a){var o=Ai(t,e,r),s=Fr(o)?Kt.tokenStructuredMatcherNoCategories:Kt.tokenStructuredMatcher;return a(o,n,s,i)}C.buildLookaheadFuncForOr=rs;function ns(t,e,r,n,i,a){var o=Ri(t,e,i,r),s=Fr(o)?Kt.tokenStructuredMatcherNoCategories:Kt.tokenStructuredMatcher;return a(o[0],s,n)}C.buildLookaheadFuncForOptionalProd=ns;function is(t,e,r,n){var i=t.length,a=D.every(t,function(c){return D.every(c,function(f){return f.length===1})});if(e)return function(c){for(var f=D.map(c,function(y){return y.GATE}),p=0;p{"use strict";var Ur=x&&x.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(x,"__esModule",{value:!0});x.checkPrefixAlternativesAmbiguities=x.validateSomeNonEmptyLookaheadPath=x.validateTooManyAlts=x.RepetionCollector=x.validateAmbiguousAlternationAlternatives=x.validateEmptyOrAlternative=x.getFirstNoneTerminal=x.validateNoLeftRecursion=x.validateRuleIsOverridden=x.validateRuleDoesNotAlreadyExist=x.OccurrenceValidationCollector=x.identifyProductionForDuplicates=x.validateGrammar=void 0;var M=k(),B=k(),Ie=ce(),Gr=vt(),Qe=yt(),cs=Tt(),_e=ne(),Wr=$e();function ps(t,e,r,n,i){var a=M.map(t,function(v){return ls(v,n)}),o=M.map(t,function(v){return Br(v,v,n)}),s=[],c=[],f=[];B.every(o,B.isEmpty)&&(s=B.map(t,function(v){return Pi(v,n)}),c=B.map(t,function(v){return Si(v,e,n)}),f=Ci(t,e,n));var p=fs(t,r,n),l=B.map(t,function(v){return xi(v,n)}),m=B.map(t,function(v){return ki(v,t,i,n)});return M.flatten(a.concat(f,o,s,c,p,l,m))}x.validateGrammar=ps;function ls(t,e){var r=new bi;t.accept(r);var n=r.allProductions,i=M.groupBy(n,Li),a=M.pick(i,function(s){return s.length>1}),o=M.map(M.values(a),function(s){var c=M.first(s),f=e.buildDuplicateFoundError(t,s),p=Gr.getProductionDslName(c),l={message:f,type:Ie.ParserDefinitionErrorType.DUPLICATE_PRODUCTIONS,ruleName:t.name,dslName:p,occurrence:c.idx},m=Mi(c);return m&&(l.parameter=m),l});return o}function Li(t){return Gr.getProductionDslName(t)+"_#_"+t.idx+"_#_"+Mi(t)}x.identifyProductionForDuplicates=Li;function Mi(t){return t instanceof _e.Terminal?t.terminalType.name:t instanceof _e.NonTerminal?t.nonTerminalName:""}var bi=function(t){Ur(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.allProductions=[],r}return e.prototype.visitNonTerminal=function(r){this.allProductions.push(r)},e.prototype.visitOption=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatory=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatoryWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetition=function(r){this.allProductions.push(r)},e.prototype.visitAlternation=function(r){this.allProductions.push(r)},e.prototype.visitTerminal=function(r){this.allProductions.push(r)},e}(Wr.GAstVisitor);x.OccurrenceValidationCollector=bi;function ki(t,e,r,n){var i=[],a=B.reduce(e,function(s,c){return c.name===t.name?s+1:s},0);if(a>1){var o=n.buildDuplicateRuleNameError({topLevelRule:t,grammarName:r});i.push({message:o,type:Ie.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:t.name})}return i}x.validateRuleDoesNotAlreadyExist=ki;function hs(t,e,r){var n=[],i;return M.contains(e,t)||(i="Invalid rule override, rule: ->"+t+"<- cannot be overridden in the grammar: ->"+r+"<-as it is not defined in any of the super grammars ",n.push({message:i,type:Ie.ParserDefinitionErrorType.INVALID_RULE_OVERRIDE,ruleName:t})),n}x.validateRuleIsOverridden=hs;function Br(t,e,r,n){n===void 0&&(n=[]);var i=[],a=_t(e.definition);if(M.isEmpty(a))return[];var o=t.name,s=M.contains(a,t);s&&i.push({message:r.buildLeftRecursionError({topLevelRule:t,leftRecursionPath:n}),type:Ie.ParserDefinitionErrorType.LEFT_RECURSION,ruleName:o});var c=M.difference(a,n.concat([t])),f=M.map(c,function(p){var l=M.cloneArr(n);return l.push(p),Br(t,p,r,l)});return i.concat(M.flatten(f))}x.validateNoLeftRecursion=Br;function _t(t){var e=[];if(M.isEmpty(t))return e;var r=M.first(t);if(r instanceof _e.NonTerminal)e.push(r.referencedRule);else if(r instanceof _e.Alternative||r instanceof _e.Option||r instanceof _e.RepetitionMandatory||r instanceof _e.RepetitionMandatoryWithSeparator||r instanceof _e.RepetitionWithSeparator||r instanceof _e.Repetition)e=e.concat(_t(r.definition));else if(r instanceof _e.Alternation)e=M.flatten(M.map(r.definition,function(o){return _t(o.definition)}));else if(!(r instanceof _e.Terminal))throw Error("non exhaustive match");var n=Gr.isOptionalProd(r),i=t.length>1;if(n&&i){var a=M.drop(t);return e.concat(_t(a))}else return e}x.getFirstNoneTerminal=_t;var qr=function(t){Ur(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.alternations=[],r}return e.prototype.visitAlternation=function(r){this.alternations.push(r)},e}(Wr.GAstVisitor);function Pi(t,e){var r=new qr;t.accept(r);var n=r.alternations,i=M.reduce(n,function(a,o){var s=M.dropRight(o.definition),c=M.map(s,function(f,p){var l=cs.nextPossibleTokensAfter([f],[],null,1);return M.isEmpty(l)?{message:e.buildEmptyAlternationError({topLevelRule:t,alternation:o,emptyChoiceIdx:p}),type:Ie.ParserDefinitionErrorType.NONE_LAST_EMPTY_ALT,ruleName:t.name,occurrence:o.idx,alternative:p+1}:null});return a.concat(M.compact(c))},[]);return i}x.validateEmptyOrAlternative=Pi;function Si(t,e,r){var n=new qr;t.accept(n);var i=n.alternations;i=B.reject(i,function(o){return o.ignoreAmbiguities===!0});var a=M.reduce(i,function(o,s){var c=s.idx,f=s.maxLookahead||e,p=Qe.getLookaheadPathsForOr(c,t,f,s),l=ds(p,s,t,r),m=Fi(p,s,t,r);return o.concat(l,m)},[]);return a}x.validateAmbiguousAlternationAlternatives=Si;var wi=function(t){Ur(e,t);function e(){var r=t!==null&&t.apply(this,arguments)||this;return r.allProductions=[],r}return e.prototype.visitRepetitionWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatory=function(r){this.allProductions.push(r)},e.prototype.visitRepetitionMandatoryWithSeparator=function(r){this.allProductions.push(r)},e.prototype.visitRepetition=function(r){this.allProductions.push(r)},e}(Wr.GAstVisitor);x.RepetionCollector=wi;function xi(t,e){var r=new qr;t.accept(r);var n=r.alternations,i=M.reduce(n,function(a,o){return o.definition.length>255&&a.push({message:e.buildTooManyAlternativesError({topLevelRule:t,alternation:o}),type:Ie.ParserDefinitionErrorType.TOO_MANY_ALTS,ruleName:t.name,occurrence:o.idx}),a},[]);return i}x.validateTooManyAlts=xi;function Ci(t,e,r){var n=[];return B.forEach(t,function(i){var a=new wi;i.accept(a);var o=a.allProductions;B.forEach(o,function(s){var c=Qe.getProdType(s),f=s.maxLookahead||e,p=s.idx,l=Qe.getLookaheadPathsForOptionalProd(p,i,c,f),m=l[0];if(B.isEmpty(B.flatten(m))){var v=r.buildEmptyRepetitionError({topLevelRule:i,repetition:s});n.push({message:v,type:Ie.ParserDefinitionErrorType.NO_NON_EMPTY_LOOKAHEAD,ruleName:i.name})}})}),n}x.validateSomeNonEmptyLookaheadPath=Ci;function ds(t,e,r,n){var i=[],a=B.reduce(t,function(s,c,f){return e.definition[f].ignoreAmbiguities===!0||B.forEach(c,function(p){var l=[f];B.forEach(t,function(m,v){f!==v&&Qe.containsPath(m,p)&&e.definition[v].ignoreAmbiguities!==!0&&l.push(v)}),l.length>1&&!Qe.containsPath(i,p)&&(i.push(p),s.push({alts:l,path:p}))}),s},[]),o=M.map(a,function(s){var c=B.map(s.alts,function(p){return p+1}),f=n.buildAlternationAmbiguityError({topLevelRule:r,alternation:e,ambiguityIndices:c,prefixPath:s.path});return{message:f,type:Ie.ParserDefinitionErrorType.AMBIGUOUS_ALTS,ruleName:r.name,occurrence:e.idx,alternatives:[s.alts]}});return o}function Fi(t,e,r,n){var i=[],a=B.reduce(t,function(o,s,c){var f=B.map(s,function(p){return{idx:c,path:p}});return o.concat(f)},[]);return B.forEach(a,function(o){var s=e.definition[o.idx];if(s.ignoreAmbiguities!==!0){var c=o.idx,f=o.path,p=B.findAll(a,function(m){return e.definition[m.idx].ignoreAmbiguities!==!0&&m.idx{"use strict";Object.defineProperty(Je,"__esModule",{value:!0});Je.validateGrammar=Je.resolveGrammar=void 0;var Vr=k(),vs=mi(),ms=jr(),Di=mt();function Es(t){t=Vr.defaults(t,{errMsgProvider:Di.defaultGrammarResolverErrorProvider});var e={};return Vr.forEach(t.rules,function(r){e[r.name]=r}),vs.resolveGrammar(e,t.errMsgProvider)}Je.resolveGrammar=Es;function Ts(t){return t=Vr.defaults(t,{errMsgProvider:Di.defaultGrammarValidatorErrorProvider}),ms.validateGrammar(t.rules,t.maxLookahead,t.tokenTypes,t.errMsgProvider,t.grammarName)}Je.validateGrammar=Ts});var et=R(ae=>{"use strict";var gt=ae&&ae.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(ae,"__esModule",{value:!0});ae.EarlyExitException=ae.NotAllInputParsedException=ae.NoViableAltException=ae.MismatchedTokenException=ae.isRecognitionException=void 0;var ys=k(),Gi="MismatchedTokenException",Wi="NoViableAltException",Bi="EarlyExitException",qi="NotAllInputParsedException",ji=[Gi,Wi,Bi,qi];Object.freeze(ji);function _s(t){return ys.contains(ji,t.name)}ae.isRecognitionException=_s;var zt=function(t){gt(e,t);function e(r,n){var i=this.constructor,a=t.call(this,r)||this;return a.token=n,a.resyncedTokens=[],Object.setPrototypeOf(a,i.prototype),Error.captureStackTrace&&Error.captureStackTrace(a,a.constructor),a}return e}(Error),gs=function(t){gt(e,t);function e(r,n,i){var a=t.call(this,r,n)||this;return a.previousToken=i,a.name=Gi,a}return e}(zt);ae.MismatchedTokenException=gs;var As=function(t){gt(e,t);function e(r,n,i){var a=t.call(this,r,n)||this;return a.previousToken=i,a.name=Wi,a}return e}(zt);ae.NoViableAltException=As;var Rs=function(t){gt(e,t);function e(r,n){var i=t.call(this,r,n)||this;return i.name=qi,i}return e}(zt);ae.NotAllInputParsedException=Rs;var Os=function(t){gt(e,t);function e(r,n,i){var a=t.call(this,r,n)||this;return a.previousToken=i,a.name=Bi,a}return e}(zt);ae.EarlyExitException=Os});var zr=R($=>{"use strict";Object.defineProperty($,"__esModule",{value:!0});$.attemptInRepetitionRecovery=$.Recoverable=$.InRuleRecoveryException=$.IN_RULE_RECOVERY_EXCEPTION=$.EOF_FOLLOW_KEY=void 0;var Ht=Ue(),de=k(),Ns=et(),Is=Mr(),ks=ce();$.EOF_FOLLOW_KEY={};$.IN_RULE_RECOVERY_EXCEPTION="InRuleRecoveryException";function Kr(t){this.name=$.IN_RULE_RECOVERY_EXCEPTION,this.message=t}$.InRuleRecoveryException=Kr;Kr.prototype=Error.prototype;var Ps=function(){function t(){}return t.prototype.initRecoverable=function(e){this.firstAfterRepMap={},this.resyncFollows={},this.recoveryEnabled=de.has(e,"recoveryEnabled")?e.recoveryEnabled:ks.DEFAULT_PARSER_CONFIG.recoveryEnabled,this.recoveryEnabled&&(this.attemptInRepetitionRecovery=Vi)},t.prototype.getTokenToInsert=function(e){var r=Ht.createTokenInstance(e,"",NaN,NaN,NaN,NaN,NaN,NaN);return r.isInsertedInRecovery=!0,r},t.prototype.canTokenTypeBeInsertedInRecovery=function(e){return!0},t.prototype.tryInRepetitionRecovery=function(e,r,n,i){for(var a=this,o=this.findReSyncTokenType(),s=this.exportLexerState(),c=[],f=!1,p=this.LA(1),l=this.LA(1),m=function(){var v=a.LA(0),u=a.errorMessageProvider.buildMismatchTokenMessage({expected:i,actual:p,previous:v,ruleName:a.getCurrRuleFullName()}),d=new Ns.MismatchedTokenException(u,p,a.LA(0));d.resyncedTokens=de.dropRight(c),a.SAVE_ERROR(d)};!f;)if(this.tokenMatcher(l,i)){m();return}else if(n.call(this)){m(),e.apply(this,r);return}else this.tokenMatcher(l,o)?f=!0:(l=this.SKIP_TOKEN(),this.addToResyncTokens(l,c));this.importLexerState(s)},t.prototype.shouldInRepetitionRecoveryBeTried=function(e,r,n){return!(n===!1||e===void 0||r===void 0||this.tokenMatcher(this.LA(1),e)||this.isBackTracking()||this.canPerformInRuleRecovery(e,this.getFollowsForInRuleRecovery(e,r)))},t.prototype.getFollowsForInRuleRecovery=function(e,r){var n=this.getCurrentGrammarPath(e,r),i=this.getNextPossibleTokenTypes(n);return i},t.prototype.tryInRuleRecovery=function(e,r){if(this.canRecoverWithSingleTokenInsertion(e,r)){var n=this.getTokenToInsert(e);return n}if(this.canRecoverWithSingleTokenDeletion(e)){var i=this.SKIP_TOKEN();return this.consumeToken(),i}throw new Kr("sad sad panda")},t.prototype.canPerformInRuleRecovery=function(e,r){return this.canRecoverWithSingleTokenInsertion(e,r)||this.canRecoverWithSingleTokenDeletion(e)},t.prototype.canRecoverWithSingleTokenInsertion=function(e,r){var n=this;if(!this.canTokenTypeBeInsertedInRecovery(e)||de.isEmpty(r))return!1;var i=this.LA(1),a=de.find(r,function(o){return n.tokenMatcher(i,o)})!==void 0;return a},t.prototype.canRecoverWithSingleTokenDeletion=function(e){var r=this.tokenMatcher(this.LA(2),e);return r},t.prototype.isInCurrentRuleReSyncSet=function(e){var r=this.getCurrFollowKey(),n=this.getFollowSetFromFollowKey(r);return de.contains(n,e)},t.prototype.findReSyncTokenType=function(){for(var e=this.flattenFollowSet(),r=this.LA(1),n=2;;){var i=r.tokenType;if(de.contains(e,i))return i;r=this.LA(n),n++}},t.prototype.getCurrFollowKey=function(){if(this.RULE_STACK.length===1)return $.EOF_FOLLOW_KEY;var e=this.getLastExplicitRuleShortName(),r=this.getLastExplicitRuleOccurrenceIndex(),n=this.getPreviousExplicitRuleShortName();return{ruleName:this.shortRuleNameToFullName(e),idxInCallingRule:r,inRule:this.shortRuleNameToFullName(n)}},t.prototype.buildFullFollowKeyStack=function(){var e=this,r=this.RULE_STACK,n=this.RULE_OCCURRENCE_STACK;return de.map(r,function(i,a){return a===0?$.EOF_FOLLOW_KEY:{ruleName:e.shortRuleNameToFullName(i),idxInCallingRule:n[a],inRule:e.shortRuleNameToFullName(r[a-1])}})},t.prototype.flattenFollowSet=function(){var e=this,r=de.map(this.buildFullFollowKeyStack(),function(n){return e.getFollowSetFromFollowKey(n)});return de.flatten(r)},t.prototype.getFollowSetFromFollowKey=function(e){if(e===$.EOF_FOLLOW_KEY)return[Ht.EOF];var r=e.ruleName+e.idxInCallingRule+Is.IN+e.inRule;return this.resyncFollows[r]},t.prototype.addToResyncTokens=function(e,r){return this.tokenMatcher(e,Ht.EOF)||r.push(e),r},t.prototype.reSyncTo=function(e){for(var r=[],n=this.LA(1);this.tokenMatcher(n,e)===!1;)n=this.SKIP_TOKEN(),this.addToResyncTokens(n,r);return de.dropRight(r)},t.prototype.attemptInRepetitionRecovery=function(e,r,n,i,a,o,s){},t.prototype.getCurrentGrammarPath=function(e,r){var n=this.getHumanReadableRuleStack(),i=de.cloneArr(this.RULE_OCCURRENCE_STACK),a={ruleStack:n,occurrenceStack:i,lastTok:e,lastTokOccurrence:r};return a},t.prototype.getHumanReadableRuleStack=function(){var e=this;return de.map(this.RULE_STACK,function(r){return e.shortRuleNameToFullName(r)})},t}();$.Recoverable=Ps;function Vi(t,e,r,n,i,a,o){var s=this.getKeyForAutomaticLookahead(n,i),c=this.firstAfterRepMap[s];if(c===void 0){var f=this.getCurrRuleFullName(),p=this.getGAstProductions()[f],l=new a(p,i);c=l.startWalking(),this.firstAfterRepMap[s]=c}var m=c.token,v=c.occurrence,u=c.isEndOfRule;this.RULE_STACK.length===1&&u&&m===void 0&&(m=Ht.EOF,v=1),this.shouldInRepetitionRecoveryBeTried(m,v,o)&&this.tryInRepetitionRecovery(t,e,r,m)}$.attemptInRepetitionRecovery=Vi});var Yt=R(P=>{"use strict";Object.defineProperty(P,"__esModule",{value:!0});P.getKeyForAutomaticLookahead=P.AT_LEAST_ONE_SEP_IDX=P.MANY_SEP_IDX=P.AT_LEAST_ONE_IDX=P.MANY_IDX=P.OPTION_IDX=P.OR_IDX=P.BITS_FOR_ALT_IDX=P.BITS_FOR_RULE_IDX=P.BITS_FOR_OCCURRENCE_IDX=P.BITS_FOR_METHOD_TYPE=void 0;P.BITS_FOR_METHOD_TYPE=4;P.BITS_FOR_OCCURRENCE_IDX=8;P.BITS_FOR_RULE_IDX=12;P.BITS_FOR_ALT_IDX=8;P.OR_IDX=1<{"use strict";Object.defineProperty(Xt,"__esModule",{value:!0});Xt.LooksAhead=void 0;var be=yt(),ge=k(),Ki=ce(),Fe=Yt(),Ve=vt(),xs=function(){function t(){}return t.prototype.initLooksAhead=function(e){this.dynamicTokensEnabled=ge.has(e,"dynamicTokensEnabled")?e.dynamicTokensEnabled:Ki.DEFAULT_PARSER_CONFIG.dynamicTokensEnabled,this.maxLookahead=ge.has(e,"maxLookahead")?e.maxLookahead:Ki.DEFAULT_PARSER_CONFIG.maxLookahead,this.lookAheadFuncsCache=ge.isES2015MapSupported()?new Map:[],ge.isES2015MapSupported()?(this.getLaFuncFromCache=this.getLaFuncFromMap,this.setLaFuncCache=this.setLaFuncCacheUsingMap):(this.getLaFuncFromCache=this.getLaFuncFromObj,this.setLaFuncCache=this.setLaFuncUsingObj)},t.prototype.preComputeLookaheadFunctions=function(e){var r=this;ge.forEach(e,function(n){r.TRACE_INIT(n.name+" Rule Lookahead",function(){var i=Ve.collectMethods(n),a=i.alternation,o=i.repetition,s=i.option,c=i.repetitionMandatory,f=i.repetitionMandatoryWithSeparator,p=i.repetitionWithSeparator;ge.forEach(a,function(l){var m=l.idx===0?"":l.idx;r.TRACE_INIT(""+Ve.getProductionDslName(l)+m,function(){var v=be.buildLookaheadFuncForOr(l.idx,n,l.maxLookahead||r.maxLookahead,l.hasPredicates,r.dynamicTokensEnabled,r.lookAheadBuilderForAlternatives),u=Fe.getKeyForAutomaticLookahead(r.fullRuleNameToShort[n.name],Fe.OR_IDX,l.idx);r.setLaFuncCache(u,v)})}),ge.forEach(o,function(l){r.computeLookaheadFunc(n,l.idx,Fe.MANY_IDX,be.PROD_TYPE.REPETITION,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(s,function(l){r.computeLookaheadFunc(n,l.idx,Fe.OPTION_IDX,be.PROD_TYPE.OPTION,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(c,function(l){r.computeLookaheadFunc(n,l.idx,Fe.AT_LEAST_ONE_IDX,be.PROD_TYPE.REPETITION_MANDATORY,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(f,function(l){r.computeLookaheadFunc(n,l.idx,Fe.AT_LEAST_ONE_SEP_IDX,be.PROD_TYPE.REPETITION_MANDATORY_WITH_SEPARATOR,l.maxLookahead,Ve.getProductionDslName(l))}),ge.forEach(p,function(l){r.computeLookaheadFunc(n,l.idx,Fe.MANY_SEP_IDX,be.PROD_TYPE.REPETITION_WITH_SEPARATOR,l.maxLookahead,Ve.getProductionDslName(l))})})})},t.prototype.computeLookaheadFunc=function(e,r,n,i,a,o){var s=this;this.TRACE_INIT(""+o+(r===0?"":r),function(){var c=be.buildLookaheadFuncForOptionalProd(r,e,a||s.maxLookahead,s.dynamicTokensEnabled,i,s.lookAheadBuilderForOptional),f=Fe.getKeyForAutomaticLookahead(s.fullRuleNameToShort[e.name],n,r);s.setLaFuncCache(f,c)})},t.prototype.lookAheadBuilderForOptional=function(e,r,n){return be.buildSingleAlternativeLookaheadFunction(e,r,n)},t.prototype.lookAheadBuilderForAlternatives=function(e,r,n,i){return be.buildAlternativesLookAheadFunc(e,r,n,i)},t.prototype.getKeyForAutomaticLookahead=function(e,r){var n=this.getLastExplicitRuleShortName();return Fe.getKeyForAutomaticLookahead(n,e,r)},t.prototype.getLaFuncFromCache=function(e){},t.prototype.getLaFuncFromMap=function(e){return this.lookAheadFuncsCache.get(e)},t.prototype.getLaFuncFromObj=function(e){return this.lookAheadFuncsCache[e]},t.prototype.setLaFuncCache=function(e,r){},t.prototype.setLaFuncCacheUsingMap=function(e,r){this.lookAheadFuncsCache.set(e,r)},t.prototype.setLaFuncUsingObj=function(e,r){this.lookAheadFuncsCache[e]=r},t}();Xt.LooksAhead=xs});var Hi=R(ke=>{"use strict";Object.defineProperty(ke,"__esModule",{value:!0});ke.addNoneTerminalToCst=ke.addTerminalToCst=ke.setNodeLocationFull=ke.setNodeLocationOnlyOffset=void 0;function Cs(t,e){isNaN(t.startOffset)===!0?(t.startOffset=e.startOffset,t.endOffset=e.endOffset):t.endOffset{"use strict";Object.defineProperty(Be,"__esModule",{value:!0});Be.defineNameProp=Be.functionName=Be.classNameFromInstance=void 0;var Fs=k();function ws(t){return Yi(t.constructor)}Be.classNameFromInstance=ws;var Xi="name";function Yi(t){var e=t.name;return e||"anonymous"}Be.functionName=Yi;function Ds(t,e){var r=Object.getOwnPropertyDescriptor(t,Xi);return Fs.isUndefined(r)||r.configurable?(Object.defineProperty(t,Xi,{enumerable:!1,configurable:!0,writable:!1,value:e}),!0):!1}Be.defineNameProp=Ds});var ea=R(Y=>{"use strict";Object.defineProperty(Y,"__esModule",{value:!0});Y.validateRedundantMethods=Y.validateMissingCstMethods=Y.validateVisitor=Y.CstVisitorDefinitionError=Y.createBaseVisitorConstructorWithDefaults=Y.createBaseSemanticVisitorConstructor=Y.defaultVisit=void 0;var ve=k(),At=Hr();function $i(t,e){for(var r=ve.keys(t),n=r.length,i=0;i: + `+(""+a.join(` + +`).replace(/\n/g,` + `)))}}};return r.prototype=n,r.prototype.constructor=r,r._RULE_NAMES=e,r}Y.createBaseSemanticVisitorConstructor=Us;function Gs(t,e,r){var n=function(){};At.defineNameProp(n,t+"BaseSemanticsWithDefaults");var i=Object.create(r.prototype);return ve.forEach(e,function(a){i[a]=$i}),n.prototype=i,n.prototype.constructor=n,n}Y.createBaseVisitorConstructorWithDefaults=Gs;var Yr;(function(t){t[t.REDUNDANT_METHOD=0]="REDUNDANT_METHOD",t[t.MISSING_METHOD=1]="MISSING_METHOD"})(Yr=Y.CstVisitorDefinitionError||(Y.CstVisitorDefinitionError={}));function Zi(t,e){var r=Qi(t,e),n=Ji(t,e);return r.concat(n)}Y.validateVisitor=Zi;function Qi(t,e){var r=ve.map(e,function(n){if(!ve.isFunction(t[n]))return{msg:"Missing visitor method: <"+n+"> on "+At.functionName(t.constructor)+" CST Visitor.",type:Yr.MISSING_METHOD,methodName:n}});return ve.compact(r)}Y.validateMissingCstMethods=Qi;var Ws=["constructor","visit","validateVisitor"];function Ji(t,e){var r=[];for(var n in t)ve.isFunction(t[n])&&!ve.contains(Ws,n)&&!ve.contains(e,n)&&r.push({msg:"Redundant visitor method: <"+n+"> on "+At.functionName(t.constructor)+` CST Visitor +There is no Grammar Rule corresponding to this method's name. +`,type:Yr.REDUNDANT_METHOD,methodName:n});return r}Y.validateRedundantMethods=Ji});var ra=R($t=>{"use strict";Object.defineProperty($t,"__esModule",{value:!0});$t.TreeBuilder=void 0;var tt=Hi(),K=k(),ta=ea(),Bs=ce(),qs=function(){function t(){}return t.prototype.initTreeBuilder=function(e){if(this.CST_STACK=[],this.outputCst=e.outputCst,this.nodeLocationTracking=K.has(e,"nodeLocationTracking")?e.nodeLocationTracking:Bs.DEFAULT_PARSER_CONFIG.nodeLocationTracking,!this.outputCst)this.cstInvocationStateUpdate=K.NOOP,this.cstFinallyStateUpdate=K.NOOP,this.cstPostTerminal=K.NOOP,this.cstPostNonTerminal=K.NOOP,this.cstPostRule=K.NOOP;else if(/full/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=tt.setNodeLocationFull,this.setNodeLocationFromNode=tt.setNodeLocationFull,this.cstPostRule=K.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationFullRecovery):(this.setNodeLocationFromToken=K.NOOP,this.setNodeLocationFromNode=K.NOOP,this.cstPostRule=this.cstPostRuleFull,this.setInitialNodeLocation=this.setInitialNodeLocationFullRegular);else if(/onlyOffset/i.test(this.nodeLocationTracking))this.recoveryEnabled?(this.setNodeLocationFromToken=tt.setNodeLocationOnlyOffset,this.setNodeLocationFromNode=tt.setNodeLocationOnlyOffset,this.cstPostRule=K.NOOP,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRecovery):(this.setNodeLocationFromToken=K.NOOP,this.setNodeLocationFromNode=K.NOOP,this.cstPostRule=this.cstPostRuleOnlyOffset,this.setInitialNodeLocation=this.setInitialNodeLocationOnlyOffsetRegular);else if(/none/i.test(this.nodeLocationTracking))this.setNodeLocationFromToken=K.NOOP,this.setNodeLocationFromNode=K.NOOP,this.cstPostRule=K.NOOP,this.setInitialNodeLocation=K.NOOP;else throw Error('Invalid config option: "'+e.nodeLocationTracking+'"')},t.prototype.setInitialNodeLocationOnlyOffsetRecovery=function(e){e.location={startOffset:NaN,endOffset:NaN}},t.prototype.setInitialNodeLocationOnlyOffsetRegular=function(e){e.location={startOffset:this.LA(1).startOffset,endOffset:NaN}},t.prototype.setInitialNodeLocationFullRecovery=function(e){e.location={startOffset:NaN,startLine:NaN,startColumn:NaN,endOffset:NaN,endLine:NaN,endColumn:NaN}},t.prototype.setInitialNodeLocationFullRegular=function(e){var r=this.LA(1);e.location={startOffset:r.startOffset,startLine:r.startLine,startColumn:r.startColumn,endOffset:NaN,endLine:NaN,endColumn:NaN}},t.prototype.cstInvocationStateUpdate=function(e,r){var n={name:e,children:{}};this.setInitialNodeLocation(n),this.CST_STACK.push(n)},t.prototype.cstFinallyStateUpdate=function(){this.CST_STACK.pop()},t.prototype.cstPostRuleFull=function(e){var r=this.LA(0),n=e.location;n.startOffset<=r.startOffset?(n.endOffset=r.endOffset,n.endLine=r.endLine,n.endColumn=r.endColumn):(n.startOffset=NaN,n.startLine=NaN,n.startColumn=NaN)},t.prototype.cstPostRuleOnlyOffset=function(e){var r=this.LA(0),n=e.location;n.startOffset<=r.startOffset?n.endOffset=r.endOffset:n.startOffset=NaN},t.prototype.cstPostTerminal=function(e,r){var n=this.CST_STACK[this.CST_STACK.length-1];tt.addTerminalToCst(n,r,e),this.setNodeLocationFromToken(n.location,r)},t.prototype.cstPostNonTerminal=function(e,r){var n=this.CST_STACK[this.CST_STACK.length-1];tt.addNoneTerminalToCst(n,r,e),this.setNodeLocationFromNode(n.location,e.location)},t.prototype.getBaseCstVisitorConstructor=function(){if(K.isUndefined(this.baseCstVisitorConstructor)){var e=ta.createBaseSemanticVisitorConstructor(this.className,K.keys(this.gastProductionsCache));return this.baseCstVisitorConstructor=e,e}return this.baseCstVisitorConstructor},t.prototype.getBaseCstVisitorConstructorWithDefaults=function(){if(K.isUndefined(this.baseCstVisitorWithDefaultsConstructor)){var e=ta.createBaseVisitorConstructorWithDefaults(this.className,K.keys(this.gastProductionsCache),this.getBaseCstVisitorConstructor());return this.baseCstVisitorWithDefaultsConstructor=e,e}return this.baseCstVisitorWithDefaultsConstructor},t.prototype.getLastExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-1]},t.prototype.getPreviousExplicitRuleShortName=function(){var e=this.RULE_STACK;return e[e.length-2]},t.prototype.getLastExplicitRuleOccurrenceIndex=function(){var e=this.RULE_OCCURRENCE_STACK;return e[e.length-1]},t}();$t.TreeBuilder=qs});var ia=R(Zt=>{"use strict";Object.defineProperty(Zt,"__esModule",{value:!0});Zt.LexerAdapter=void 0;var na=ce(),js=function(){function t(){}return t.prototype.initLexerAdapter=function(){this.tokVector=[],this.tokVectorLength=0,this.currIdx=-1},Object.defineProperty(t.prototype,"input",{get:function(){return this.tokVector},set:function(e){if(this.selfAnalysisDone!==!0)throw Error("Missing invocation at the end of the Parser's constructor.");this.reset(),this.tokVector=e,this.tokVectorLength=e.length},enumerable:!1,configurable:!0}),t.prototype.SKIP_TOKEN=function(){return this.currIdx<=this.tokVector.length-2?(this.consumeToken(),this.LA(1)):na.END_OF_FILE},t.prototype.LA=function(e){var r=this.currIdx+e;return r<0||this.tokVectorLength<=r?na.END_OF_FILE:this.tokVector[r]},t.prototype.consumeToken=function(){this.currIdx++},t.prototype.exportLexerState=function(){return this.currIdx},t.prototype.importLexerState=function(e){this.currIdx=e},t.prototype.resetLexerState=function(){this.currIdx=-1},t.prototype.moveToTerminatedState=function(){this.currIdx=this.tokVector.length-1},t.prototype.getLexerPosition=function(){return this.exportLexerState()},t}();Zt.LexerAdapter=js});var oa=R(Qt=>{"use strict";Object.defineProperty(Qt,"__esModule",{value:!0});Qt.RecognizerApi=void 0;var aa=k(),Vs=et(),Xr=ce(),Ks=mt(),zs=jr(),Hs=ne(),Ys=function(){function t(){}return t.prototype.ACTION=function(e){return e.call(this)},t.prototype.consume=function(e,r,n){return this.consumeInternal(r,e,n)},t.prototype.subrule=function(e,r,n){return this.subruleInternal(r,e,n)},t.prototype.option=function(e,r){return this.optionInternal(r,e)},t.prototype.or=function(e,r){return this.orInternal(r,e)},t.prototype.many=function(e,r){return this.manyInternal(e,r)},t.prototype.atLeastOne=function(e,r){return this.atLeastOneInternal(e,r)},t.prototype.CONSUME=function(e,r){return this.consumeInternal(e,0,r)},t.prototype.CONSUME1=function(e,r){return this.consumeInternal(e,1,r)},t.prototype.CONSUME2=function(e,r){return this.consumeInternal(e,2,r)},t.prototype.CONSUME3=function(e,r){return this.consumeInternal(e,3,r)},t.prototype.CONSUME4=function(e,r){return this.consumeInternal(e,4,r)},t.prototype.CONSUME5=function(e,r){return this.consumeInternal(e,5,r)},t.prototype.CONSUME6=function(e,r){return this.consumeInternal(e,6,r)},t.prototype.CONSUME7=function(e,r){return this.consumeInternal(e,7,r)},t.prototype.CONSUME8=function(e,r){return this.consumeInternal(e,8,r)},t.prototype.CONSUME9=function(e,r){return this.consumeInternal(e,9,r)},t.prototype.SUBRULE=function(e,r){return this.subruleInternal(e,0,r)},t.prototype.SUBRULE1=function(e,r){return this.subruleInternal(e,1,r)},t.prototype.SUBRULE2=function(e,r){return this.subruleInternal(e,2,r)},t.prototype.SUBRULE3=function(e,r){return this.subruleInternal(e,3,r)},t.prototype.SUBRULE4=function(e,r){return this.subruleInternal(e,4,r)},t.prototype.SUBRULE5=function(e,r){return this.subruleInternal(e,5,r)},t.prototype.SUBRULE6=function(e,r){return this.subruleInternal(e,6,r)},t.prototype.SUBRULE7=function(e,r){return this.subruleInternal(e,7,r)},t.prototype.SUBRULE8=function(e,r){return this.subruleInternal(e,8,r)},t.prototype.SUBRULE9=function(e,r){return this.subruleInternal(e,9,r)},t.prototype.OPTION=function(e){return this.optionInternal(e,0)},t.prototype.OPTION1=function(e){return this.optionInternal(e,1)},t.prototype.OPTION2=function(e){return this.optionInternal(e,2)},t.prototype.OPTION3=function(e){return this.optionInternal(e,3)},t.prototype.OPTION4=function(e){return this.optionInternal(e,4)},t.prototype.OPTION5=function(e){return this.optionInternal(e,5)},t.prototype.OPTION6=function(e){return this.optionInternal(e,6)},t.prototype.OPTION7=function(e){return this.optionInternal(e,7)},t.prototype.OPTION8=function(e){return this.optionInternal(e,8)},t.prototype.OPTION9=function(e){return this.optionInternal(e,9)},t.prototype.OR=function(e){return this.orInternal(e,0)},t.prototype.OR1=function(e){return this.orInternal(e,1)},t.prototype.OR2=function(e){return this.orInternal(e,2)},t.prototype.OR3=function(e){return this.orInternal(e,3)},t.prototype.OR4=function(e){return this.orInternal(e,4)},t.prototype.OR5=function(e){return this.orInternal(e,5)},t.prototype.OR6=function(e){return this.orInternal(e,6)},t.prototype.OR7=function(e){return this.orInternal(e,7)},t.prototype.OR8=function(e){return this.orInternal(e,8)},t.prototype.OR9=function(e){return this.orInternal(e,9)},t.prototype.MANY=function(e){this.manyInternal(0,e)},t.prototype.MANY1=function(e){this.manyInternal(1,e)},t.prototype.MANY2=function(e){this.manyInternal(2,e)},t.prototype.MANY3=function(e){this.manyInternal(3,e)},t.prototype.MANY4=function(e){this.manyInternal(4,e)},t.prototype.MANY5=function(e){this.manyInternal(5,e)},t.prototype.MANY6=function(e){this.manyInternal(6,e)},t.prototype.MANY7=function(e){this.manyInternal(7,e)},t.prototype.MANY8=function(e){this.manyInternal(8,e)},t.prototype.MANY9=function(e){this.manyInternal(9,e)},t.prototype.MANY_SEP=function(e){this.manySepFirstInternal(0,e)},t.prototype.MANY_SEP1=function(e){this.manySepFirstInternal(1,e)},t.prototype.MANY_SEP2=function(e){this.manySepFirstInternal(2,e)},t.prototype.MANY_SEP3=function(e){this.manySepFirstInternal(3,e)},t.prototype.MANY_SEP4=function(e){this.manySepFirstInternal(4,e)},t.prototype.MANY_SEP5=function(e){this.manySepFirstInternal(5,e)},t.prototype.MANY_SEP6=function(e){this.manySepFirstInternal(6,e)},t.prototype.MANY_SEP7=function(e){this.manySepFirstInternal(7,e)},t.prototype.MANY_SEP8=function(e){this.manySepFirstInternal(8,e)},t.prototype.MANY_SEP9=function(e){this.manySepFirstInternal(9,e)},t.prototype.AT_LEAST_ONE=function(e){this.atLeastOneInternal(0,e)},t.prototype.AT_LEAST_ONE1=function(e){return this.atLeastOneInternal(1,e)},t.prototype.AT_LEAST_ONE2=function(e){this.atLeastOneInternal(2,e)},t.prototype.AT_LEAST_ONE3=function(e){this.atLeastOneInternal(3,e)},t.prototype.AT_LEAST_ONE4=function(e){this.atLeastOneInternal(4,e)},t.prototype.AT_LEAST_ONE5=function(e){this.atLeastOneInternal(5,e)},t.prototype.AT_LEAST_ONE6=function(e){this.atLeastOneInternal(6,e)},t.prototype.AT_LEAST_ONE7=function(e){this.atLeastOneInternal(7,e)},t.prototype.AT_LEAST_ONE8=function(e){this.atLeastOneInternal(8,e)},t.prototype.AT_LEAST_ONE9=function(e){this.atLeastOneInternal(9,e)},t.prototype.AT_LEAST_ONE_SEP=function(e){this.atLeastOneSepFirstInternal(0,e)},t.prototype.AT_LEAST_ONE_SEP1=function(e){this.atLeastOneSepFirstInternal(1,e)},t.prototype.AT_LEAST_ONE_SEP2=function(e){this.atLeastOneSepFirstInternal(2,e)},t.prototype.AT_LEAST_ONE_SEP3=function(e){this.atLeastOneSepFirstInternal(3,e)},t.prototype.AT_LEAST_ONE_SEP4=function(e){this.atLeastOneSepFirstInternal(4,e)},t.prototype.AT_LEAST_ONE_SEP5=function(e){this.atLeastOneSepFirstInternal(5,e)},t.prototype.AT_LEAST_ONE_SEP6=function(e){this.atLeastOneSepFirstInternal(6,e)},t.prototype.AT_LEAST_ONE_SEP7=function(e){this.atLeastOneSepFirstInternal(7,e)},t.prototype.AT_LEAST_ONE_SEP8=function(e){this.atLeastOneSepFirstInternal(8,e)},t.prototype.AT_LEAST_ONE_SEP9=function(e){this.atLeastOneSepFirstInternal(9,e)},t.prototype.RULE=function(e,r,n){if(n===void 0&&(n=Xr.DEFAULT_RULE_CONFIG),aa.contains(this.definedRulesNames,e)){var i=Ks.defaultGrammarValidatorErrorProvider.buildDuplicateRuleNameError({topLevelRule:e,grammarName:this.className}),a={message:i,type:Xr.ParserDefinitionErrorType.DUPLICATE_RULE_NAME,ruleName:e};this.definitionErrors.push(a)}this.definedRulesNames.push(e);var o=this.defineRule(e,r,n);return this[e]=o,o},t.prototype.OVERRIDE_RULE=function(e,r,n){n===void 0&&(n=Xr.DEFAULT_RULE_CONFIG);var i=[];i=i.concat(zs.validateRuleIsOverridden(e,this.definedRulesNames,this.className)),this.definitionErrors=this.definitionErrors.concat(i);var a=this.defineRule(e,r,n);return this[e]=a,a},t.prototype.BACKTRACK=function(e,r){return function(){this.isBackTrackingStack.push(1);var n=this.saveRecogState();try{return e.apply(this,r),!0}catch(i){if(Vs.isRecognitionException(i))return!1;throw i}finally{this.reloadRecogState(n),this.isBackTrackingStack.pop()}}},t.prototype.getGAstProductions=function(){return this.gastProductionsCache},t.prototype.getSerializedGastProductions=function(){return Hs.serializeGrammar(aa.values(this.gastProductionsCache))},t}();Qt.RecognizerApi=Ys});var la=R(Jt=>{"use strict";Object.defineProperty(Jt,"__esModule",{value:!0});Jt.RecognizerEngine=void 0;var q=k(),le=Yt(),er=et(),sa=yt(),rt=Tt(),ua=ce(),Xs=zr(),ca=Ue(),Rt=Xe(),$s=Hr(),Zs=function(){function t(){}return t.prototype.initRecognizerEngine=function(e,r){if(this.className=$s.classNameFromInstance(this),this.shortRuleNameToFull={},this.fullRuleNameToShort={},this.ruleShortNameIdx=256,this.tokenMatcher=Rt.tokenStructuredMatcherNoCategories,this.definedRulesNames=[],this.tokensMap={},this.isBackTrackingStack=[],this.RULE_STACK=[],this.RULE_OCCURRENCE_STACK=[],this.gastProductionsCache={},q.has(r,"serializedGrammar"))throw Error(`The Parser's configuration can no longer contain a property. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_6-0-0 + For Further details.`);if(q.isArray(e)){if(q.isEmpty(e))throw Error(`A Token Vocabulary cannot be empty. + Note that the first argument for the parser constructor + is no longer a Token vector (since v4.0).`);if(typeof e[0].startOffset=="number")throw Error(`The Parser constructor no longer accepts a token vector as the first argument. + See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_4-0-0 + For Further details.`)}if(q.isArray(e))this.tokensMap=q.reduce(e,function(o,s){return o[s.name]=s,o},{});else if(q.has(e,"modes")&&q.every(q.flatten(q.values(e.modes)),Rt.isTokenType)){var n=q.flatten(q.values(e.modes)),i=q.uniq(n);this.tokensMap=q.reduce(i,function(o,s){return o[s.name]=s,o},{})}else if(q.isObject(e))this.tokensMap=q.cloneObj(e);else throw new Error(" argument must be An Array of Token constructors, A dictionary of Token constructors or an IMultiModeLexerDefinition");this.tokensMap.EOF=ca.EOF;var a=q.every(q.values(e),function(o){return q.isEmpty(o.categoryMatches)});this.tokenMatcher=a?Rt.tokenStructuredMatcherNoCategories:Rt.tokenStructuredMatcher,Rt.augmentTokenTypes(q.values(this.tokensMap))},t.prototype.defineRule=function(e,r,n){if(this.selfAnalysisDone)throw Error("Grammar rule <"+e+`> may not be defined after the 'performSelfAnalysis' method has been called' +Make sure that all grammar rule definitions are done before 'performSelfAnalysis' is called.`);var i=q.has(n,"resyncEnabled")?n.resyncEnabled:ua.DEFAULT_RULE_CONFIG.resyncEnabled,a=q.has(n,"recoveryValueFunc")?n.recoveryValueFunc:ua.DEFAULT_RULE_CONFIG.recoveryValueFunc,o=this.ruleShortNameIdx<r},t.prototype.orInternal=function(e,r){var n=this.getKeyForAutomaticLookahead(le.OR_IDX,r),i=q.isArray(e)?e:e.DEF,a=this.getLaFuncFromCache(n),o=a.call(this,i);if(o!==void 0){var s=i[o];return s.ALT.call(this)}this.raiseNoAltException(r,e.ERR_MSG)},t.prototype.ruleFinallyStateUpdate=function(){if(this.RULE_STACK.pop(),this.RULE_OCCURRENCE_STACK.pop(),this.cstFinallyStateUpdate(),this.RULE_STACK.length===0&&this.isAtEndOfInput()===!1){var e=this.LA(1),r=this.errorMessageProvider.buildNotAllInputParsedMessage({firstRedundant:e,ruleName:this.getCurrRuleFullName()});this.SAVE_ERROR(new er.NotAllInputParsedException(r,e))}},t.prototype.subruleInternal=function(e,r,n){var i;try{var a=n!==void 0?n.ARGS:void 0;return i=e.call(this,r,a),this.cstPostNonTerminal(i,n!==void 0&&n.LABEL!==void 0?n.LABEL:e.ruleName),i}catch(o){this.subruleInternalError(o,n,e.ruleName)}},t.prototype.subruleInternalError=function(e,r,n){throw er.isRecognitionException(e)&&e.partialCstResult!==void 0&&(this.cstPostNonTerminal(e.partialCstResult,r!==void 0&&r.LABEL!==void 0?r.LABEL:n),delete e.partialCstResult),e},t.prototype.consumeInternal=function(e,r,n){var i;try{var a=this.LA(1);this.tokenMatcher(a,e)===!0?(this.consumeToken(),i=a):this.consumeInternalError(e,a,n)}catch(o){i=this.consumeInternalRecovery(e,r,o)}return this.cstPostTerminal(n!==void 0&&n.LABEL!==void 0?n.LABEL:e.name,i),i},t.prototype.consumeInternalError=function(e,r,n){var i,a=this.LA(0);throw n!==void 0&&n.ERR_MSG?i=n.ERR_MSG:i=this.errorMessageProvider.buildMismatchTokenMessage({expected:e,actual:r,previous:a,ruleName:this.getCurrRuleFullName()}),this.SAVE_ERROR(new er.MismatchedTokenException(i,r,a))},t.prototype.consumeInternalRecovery=function(e,r,n){if(this.recoveryEnabled&&n.name==="MismatchedTokenException"&&!this.isBackTracking()){var i=this.getFollowsForInRuleRecovery(e,r);try{return this.tryInRuleRecovery(e,i)}catch(a){throw a.name===Xs.IN_RULE_RECOVERY_EXCEPTION?n:a}}else throw n},t.prototype.saveRecogState=function(){var e=this.errors,r=q.cloneArr(this.RULE_STACK);return{errors:e,lexerState:this.exportLexerState(),RULE_STACK:r,CST_STACK:this.CST_STACK}},t.prototype.reloadRecogState=function(e){this.errors=e.errors,this.importLexerState(e.lexerState),this.RULE_STACK=e.RULE_STACK},t.prototype.ruleInvocationStateUpdate=function(e,r,n){this.RULE_OCCURRENCE_STACK.push(n),this.RULE_STACK.push(e),this.cstInvocationStateUpdate(r,e)},t.prototype.isBackTracking=function(){return this.isBackTrackingStack.length!==0},t.prototype.getCurrRuleFullName=function(){var e=this.getLastExplicitRuleShortName();return this.shortRuleNameToFull[e]},t.prototype.shortRuleNameToFullName=function(e){return this.shortRuleNameToFull[e]},t.prototype.isAtEndOfInput=function(){return this.tokenMatcher(this.LA(1),ca.EOF)},t.prototype.reset=function(){this.resetLexerState(),this.isBackTrackingStack=[],this.errors=[],this.RULE_STACK=[],this.CST_STACK=[],this.RULE_OCCURRENCE_STACK=[]},t}();Jt.RecognizerEngine=Zs});var pa=R(tr=>{"use strict";Object.defineProperty(tr,"__esModule",{value:!0});tr.ErrorHandler=void 0;var $r=et(),Zr=k(),fa=yt(),Qs=ce(),Js=function(){function t(){}return t.prototype.initErrorHandler=function(e){this._errors=[],this.errorMessageProvider=Zr.has(e,"errorMessageProvider")?e.errorMessageProvider:Qs.DEFAULT_PARSER_CONFIG.errorMessageProvider},t.prototype.SAVE_ERROR=function(e){if($r.isRecognitionException(e))return e.context={ruleStack:this.getHumanReadableRuleStack(),ruleOccurrenceStack:Zr.cloneArr(this.RULE_OCCURRENCE_STACK)},this._errors.push(e),e;throw Error("Trying to save an Error which is not a RecognitionException")},Object.defineProperty(t.prototype,"errors",{get:function(){return Zr.cloneArr(this._errors)},set:function(e){this._errors=e},enumerable:!1,configurable:!0}),t.prototype.raiseEarlyExitException=function(e,r,n){for(var i=this.getCurrRuleFullName(),a=this.getGAstProductions()[i],o=fa.getLookaheadPathsForOptionalProd(e,a,r,this.maxLookahead),s=o[0],c=[],f=1;f<=this.maxLookahead;f++)c.push(this.LA(f));var p=this.errorMessageProvider.buildEarlyExitMessage({expectedIterationPaths:s,actual:c,previous:this.LA(0),customUserDescription:n,ruleName:i});throw this.SAVE_ERROR(new $r.EarlyExitException(p,this.LA(1),this.LA(0)))},t.prototype.raiseNoAltException=function(e,r){for(var n=this.getCurrRuleFullName(),i=this.getGAstProductions()[n],a=fa.getLookaheadPathsForOr(e,i,this.maxLookahead),o=[],s=1;s<=this.maxLookahead;s++)o.push(this.LA(s));var c=this.LA(0),f=this.errorMessageProvider.buildNoViableAltMessage({expectedPathsPerAlt:a,actual:o,previous:c,customUserDescription:r,ruleName:this.getCurrRuleFullName()});throw this.SAVE_ERROR(new $r.NoViableAltException(f,this.LA(1),c))},t}();tr.ErrorHandler=Js});var va=R(rr=>{"use strict";Object.defineProperty(rr,"__esModule",{value:!0});rr.ContentAssist=void 0;var ha=Tt(),da=k(),eu=function(){function t(){}return t.prototype.initContentAssist=function(){},t.prototype.computeContentAssist=function(e,r){var n=this.gastProductionsCache[e];if(da.isUndefined(n))throw Error("Rule ->"+e+"<- does not exist in this grammar.");return ha.nextPossibleTokensAfter([n],r,this.tokenMatcher,this.maxLookahead)},t.prototype.getNextPossibleTokenTypes=function(e){var r=da.first(e.ruleStack),n=this.getGAstProductions(),i=n[r],a=new ha.NextAfterTokenWalker(i,e).startWalking();return a},t}();rr.ContentAssist=eu});var Ra=R(nr=>{"use strict";Object.defineProperty(nr,"__esModule",{value:!0});nr.GastRecorder=void 0;var oe=k(),Pe=ne(),tu=ft(),ma=Xe(),Ea=Ue(),ru=ce(),nu=Yt(),ir={description:"This Object indicates the Parser is during Recording Phase"};Object.freeze(ir);var Ta=!0,ya=Math.pow(2,nu.BITS_FOR_OCCURRENCE_IDX)-1,_a=Ea.createToken({name:"RECORDING_PHASE_TOKEN",pattern:tu.Lexer.NA});ma.augmentTokenTypes([_a]);var ga=Ea.createTokenInstance(_a,`This IToken indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,-1,-1,-1,-1,-1,-1);Object.freeze(ga);var iu={name:`This CSTNode indicates the Parser is in Recording Phase + See: https://chevrotain.io/docs/guide/internals.html#grammar-recording for details`,children:{}},ou=function(){function t(){}return t.prototype.initGastRecorder=function(e){this.recordingProdStack=[],this.RECORDING_PHASE=!1},t.prototype.enableRecording=function(){var e=this;this.RECORDING_PHASE=!0,this.TRACE_INIT("Enable Recording",function(){for(var r=function(i){var a=i>0?i:"";e["CONSUME"+a]=function(o,s){return this.consumeInternalRecord(o,i,s)},e["SUBRULE"+a]=function(o,s){return this.subruleInternalRecord(o,i,s)},e["OPTION"+a]=function(o){return this.optionInternalRecord(o,i)},e["OR"+a]=function(o){return this.orInternalRecord(o,i)},e["MANY"+a]=function(o){this.manyInternalRecord(i,o)},e["MANY_SEP"+a]=function(o){this.manySepFirstInternalRecord(i,o)},e["AT_LEAST_ONE"+a]=function(o){this.atLeastOneInternalRecord(i,o)},e["AT_LEAST_ONE_SEP"+a]=function(o){this.atLeastOneSepFirstInternalRecord(i,o)}},n=0;n<10;n++)r(n);e.consume=function(i,a,o){return this.consumeInternalRecord(a,i,o)},e.subrule=function(i,a,o){return this.subruleInternalRecord(a,i,o)},e.option=function(i,a){return this.optionInternalRecord(a,i)},e.or=function(i,a){return this.orInternalRecord(a,i)},e.many=function(i,a){this.manyInternalRecord(i,a)},e.atLeastOne=function(i,a){this.atLeastOneInternalRecord(i,a)},e.ACTION=e.ACTION_RECORD,e.BACKTRACK=e.BACKTRACK_RECORD,e.LA=e.LA_RECORD})},t.prototype.disableRecording=function(){var e=this;this.RECORDING_PHASE=!1,this.TRACE_INIT("Deleting Recording methods",function(){for(var r=0;r<10;r++){var n=r>0?r:"";delete e["CONSUME"+n],delete e["SUBRULE"+n],delete e["OPTION"+n],delete e["OR"+n],delete e["MANY"+n],delete e["MANY_SEP"+n],delete e["AT_LEAST_ONE"+n],delete e["AT_LEAST_ONE_SEP"+n]}delete e.consume,delete e.subrule,delete e.option,delete e.or,delete e.many,delete e.atLeastOne,delete e.ACTION,delete e.BACKTRACK,delete e.LA})},t.prototype.ACTION_RECORD=function(e){},t.prototype.BACKTRACK_RECORD=function(e,r){return function(){return!0}},t.prototype.LA_RECORD=function(e){return ru.END_OF_FILE},t.prototype.topLevelRuleRecord=function(e,r){try{var n=new Pe.Rule({definition:[],name:e});return n.name=e,this.recordingProdStack.push(n),r.call(this),this.recordingProdStack.pop(),n}catch(i){if(i.KNOWN_RECORDER_ERROR!==!0)try{i.message=i.message+` + This error was thrown during the "grammar recording phase" For more info see: + https://chevrotain.io/docs/guide/internals.html#grammar-recording`}catch(a){throw i}throw i}},t.prototype.optionInternalRecord=function(e,r){return Ot.call(this,Pe.Option,e,r)},t.prototype.atLeastOneInternalRecord=function(e,r){Ot.call(this,Pe.RepetitionMandatory,r,e)},t.prototype.atLeastOneSepFirstInternalRecord=function(e,r){Ot.call(this,Pe.RepetitionMandatoryWithSeparator,r,e,Ta)},t.prototype.manyInternalRecord=function(e,r){Ot.call(this,Pe.Repetition,r,e)},t.prototype.manySepFirstInternalRecord=function(e,r){Ot.call(this,Pe.RepetitionWithSeparator,r,e,Ta)},t.prototype.orInternalRecord=function(e,r){return au.call(this,e,r)},t.prototype.subruleInternalRecord=function(e,r,n){if(ar(r),!e||oe.has(e,"ruleName")===!1){var i=new Error(" argument is invalid"+(" expecting a Parser method reference but got: <"+JSON.stringify(e)+">")+(` + inside top level rule: <`+this.recordingProdStack[0].name+">"));throw i.KNOWN_RECORDER_ERROR=!0,i}var a=oe.peek(this.recordingProdStack),o=e.ruleName,s=new Pe.NonTerminal({idx:r,nonTerminalName:o,referencedRule:void 0});return a.definition.push(s),this.outputCst?iu:ir},t.prototype.consumeInternalRecord=function(e,r,n){if(ar(r),!ma.hasShortKeyProperty(e)){var i=new Error(" argument is invalid"+(" expecting a TokenType reference but got: <"+JSON.stringify(e)+">")+(` + inside top level rule: <`+this.recordingProdStack[0].name+">"));throw i.KNOWN_RECORDER_ERROR=!0,i}var a=oe.peek(this.recordingProdStack),o=new Pe.Terminal({idx:r,terminalType:e});return a.definition.push(o),ga},t}();nr.GastRecorder=ou;function Ot(t,e,r,n){n===void 0&&(n=!1),ar(r);var i=oe.peek(this.recordingProdStack),a=oe.isFunction(e)?e:e.DEF,o=new t({definition:[],idx:r});return n&&(o.separator=e.SEP),oe.has(e,"MAX_LOOKAHEAD")&&(o.maxLookahead=e.MAX_LOOKAHEAD),this.recordingProdStack.push(o),a.call(this),i.definition.push(o),this.recordingProdStack.pop(),ir}function au(t,e){var r=this;ar(e);var n=oe.peek(this.recordingProdStack),i=oe.isArray(t)===!1,a=i===!1?t:t.DEF,o=new Pe.Alternation({definition:[],idx:e,ignoreAmbiguities:i&&t.IGNORE_AMBIGUITIES===!0});oe.has(t,"MAX_LOOKAHEAD")&&(o.maxLookahead=t.MAX_LOOKAHEAD);var s=oe.some(a,function(c){return oe.isFunction(c.GATE)});return o.hasPredicates=s,n.definition.push(o),oe.forEach(a,function(c){var f=new Pe.Alternative({definition:[]});o.definition.push(f),oe.has(c,"IGNORE_AMBIGUITIES")?f.ignoreAmbiguities=c.IGNORE_AMBIGUITIES:oe.has(c,"GATE")&&(f.ignoreAmbiguities=!0),r.recordingProdStack.push(f),c.ALT.call(r),r.recordingProdStack.pop()}),ir}function Aa(t){return t===0?"":""+t}function ar(t){if(t<0||t>ya){var e=new Error("Invalid DSL Method idx value: <"+t+`> + `+("Idx value must be a none negative value smaller than "+(ya+1)));throw e.KNOWN_RECORDER_ERROR=!0,e}}});var Na=R(or=>{"use strict";Object.defineProperty(or,"__esModule",{value:!0});or.PerformanceTracer=void 0;var Oa=k(),su=ce(),uu=function(){function t(){}return t.prototype.initPerformanceTracer=function(e){if(Oa.has(e,"traceInitPerf")){var r=e.traceInitPerf,n=typeof r=="number";this.traceInitMaxIdent=n?r:Infinity,this.traceInitPerf=n?r>0:r}else this.traceInitMaxIdent=0,this.traceInitPerf=su.DEFAULT_PARSER_CONFIG.traceInitPerf;this.traceInitIndent=-1},t.prototype.TRACE_INIT=function(e,r){if(this.traceInitPerf===!0){this.traceInitIndent++;var n=new Array(this.traceInitIndent+1).join(" ");this.traceInitIndent <"+e+">");var i=Oa.timer(r),a=i.time,o=i.value,s=a>10?console.warn:console.log;return this.traceInitIndent time: "+a+"ms"),this.traceInitIndent--,o}else return r()},t}();or.PerformanceTracer=uu});var Ia=R(sr=>{"use strict";Object.defineProperty(sr,"__esModule",{value:!0});sr.applyMixins=void 0;function cu(t,e){e.forEach(function(r){var n=r.prototype;Object.getOwnPropertyNames(n).forEach(function(i){if(i!=="constructor"){var a=Object.getOwnPropertyDescriptor(n,i);a&&(a.get||a.set)?Object.defineProperty(t.prototype,i,a):t.prototype[i]=r.prototype[i]}})})}sr.applyMixins=cu});var ce=R(U=>{"use strict";var ka=U&&U.__extends||function(){var t=function(e,r){return t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,i){n.__proto__=i}||function(n,i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(n[a]=i[a])},t(e,r)};return function(e,r){if(typeof r!="function"&&r!==null)throw new TypeError("Class extends value "+String(r)+" is not a constructor or null");t(e,r);function n(){this.constructor=e}e.prototype=r===null?Object.create(r):(n.prototype=r.prototype,new n)}}();Object.defineProperty(U,"__esModule",{value:!0});U.EmbeddedActionsParser=U.CstParser=U.Parser=U.EMPTY_ALT=U.ParserDefinitionErrorType=U.DEFAULT_RULE_CONFIG=U.DEFAULT_PARSER_CONFIG=U.END_OF_FILE=void 0;var ee=k(),lu=pi(),Pa=Ue(),Sa=mt(),xa=Ui(),fu=zr(),pu=zi(),hu=ra(),du=ia(),vu=oa(),mu=la(),Eu=pa(),Tu=va(),yu=Ra(),_u=Na(),gu=Ia();U.END_OF_FILE=Pa.createTokenInstance(Pa.EOF,"",NaN,NaN,NaN,NaN,NaN,NaN);Object.freeze(U.END_OF_FILE);U.DEFAULT_PARSER_CONFIG=Object.freeze({recoveryEnabled:!1,maxLookahead:3,dynamicTokensEnabled:!1,outputCst:!0,errorMessageProvider:Sa.defaultParserErrorProvider,nodeLocationTracking:"none",traceInitPerf:!1,skipValidations:!1});U.DEFAULT_RULE_CONFIG=Object.freeze({recoveryValueFunc:function(){},resyncEnabled:!0});var Au;(function(t){t[t.INVALID_RULE_NAME=0]="INVALID_RULE_NAME",t[t.DUPLICATE_RULE_NAME=1]="DUPLICATE_RULE_NAME",t[t.INVALID_RULE_OVERRIDE=2]="INVALID_RULE_OVERRIDE",t[t.DUPLICATE_PRODUCTIONS=3]="DUPLICATE_PRODUCTIONS",t[t.UNRESOLVED_SUBRULE_REF=4]="UNRESOLVED_SUBRULE_REF",t[t.LEFT_RECURSION=5]="LEFT_RECURSION",t[t.NONE_LAST_EMPTY_ALT=6]="NONE_LAST_EMPTY_ALT",t[t.AMBIGUOUS_ALTS=7]="AMBIGUOUS_ALTS",t[t.CONFLICT_TOKENS_RULES_NAMESPACE=8]="CONFLICT_TOKENS_RULES_NAMESPACE",t[t.INVALID_TOKEN_NAME=9]="INVALID_TOKEN_NAME",t[t.NO_NON_EMPTY_LOOKAHEAD=10]="NO_NON_EMPTY_LOOKAHEAD",t[t.AMBIGUOUS_PREFIX_ALTS=11]="AMBIGUOUS_PREFIX_ALTS",t[t.TOO_MANY_ALTS=12]="TOO_MANY_ALTS"})(Au=U.ParserDefinitionErrorType||(U.ParserDefinitionErrorType={}));function Ru(t){return t===void 0&&(t=void 0),function(){return t}}U.EMPTY_ALT=Ru;var ur=function(){function t(e,r){this.definitionErrors=[],this.selfAnalysisDone=!1;var n=this;if(n.initErrorHandler(r),n.initLexerAdapter(),n.initLooksAhead(r),n.initRecognizerEngine(e,r),n.initRecoverable(r),n.initTreeBuilder(r),n.initContentAssist(),n.initGastRecorder(r),n.initPerformanceTracer(r),ee.has(r,"ignoredIssues"))throw new Error(`The IParserConfig property has been deprecated. + Please use the flag on the relevant DSL method instead. + See: https://chevrotain.io/docs/guide/resolving_grammar_errors.html#IGNORING_AMBIGUITIES + For further details.`);this.skipValidations=ee.has(r,"skipValidations")?r.skipValidations:U.DEFAULT_PARSER_CONFIG.skipValidations}return t.performSelfAnalysis=function(e){throw Error("The **static** `performSelfAnalysis` method has been deprecated. \nUse the **instance** method with the same name instead.")},t.prototype.performSelfAnalysis=function(){var e=this;this.TRACE_INIT("performSelfAnalysis",function(){var r;e.selfAnalysisDone=!0;var n=e.className;e.TRACE_INIT("toFastProps",function(){ee.toFastProperties(e)}),e.TRACE_INIT("Grammar Recording",function(){try{e.enableRecording(),ee.forEach(e.definedRulesNames,function(a){var o=e[a],s=o.originalGrammarAction,c=void 0;e.TRACE_INIT(a+" Rule",function(){c=e.topLevelRuleRecord(a,s)}),e.gastProductionsCache[a]=c})}finally{e.disableRecording()}});var i=[];if(e.TRACE_INIT("Grammar Resolving",function(){i=xa.resolveGrammar({rules:ee.values(e.gastProductionsCache)}),e.definitionErrors=e.definitionErrors.concat(i)}),e.TRACE_INIT("Grammar Validations",function(){if(ee.isEmpty(i)&&e.skipValidations===!1){var a=xa.validateGrammar({rules:ee.values(e.gastProductionsCache),maxLookahead:e.maxLookahead,tokenTypes:ee.values(e.tokensMap),errMsgProvider:Sa.defaultGrammarValidatorErrorProvider,grammarName:n});e.definitionErrors=e.definitionErrors.concat(a)}}),ee.isEmpty(e.definitionErrors)&&(e.recoveryEnabled&&e.TRACE_INIT("computeAllProdsFollows",function(){var a=lu.computeAllProdsFollows(ee.values(e.gastProductionsCache));e.resyncFollows=a}),e.TRACE_INIT("ComputeLookaheadFunctions",function(){e.preComputeLookaheadFunctions(ee.values(e.gastProductionsCache))})),!t.DEFER_DEFINITION_ERRORS_HANDLING&&!ee.isEmpty(e.definitionErrors))throw r=ee.map(e.definitionErrors,function(a){return a.message}),new Error(`Parser Definition Errors detected: + `+r.join(` +------------------------------- +`))})},t.DEFER_DEFINITION_ERRORS_HANDLING=!1,t}();U.Parser=ur;gu.applyMixins(ur,[fu.Recoverable,pu.LooksAhead,hu.TreeBuilder,du.LexerAdapter,mu.RecognizerEngine,vu.RecognizerApi,Eu.ErrorHandler,Tu.ContentAssist,yu.GastRecorder,_u.PerformanceTracer]);var Ou=function(t){ka(e,t);function e(r,n){n===void 0&&(n=U.DEFAULT_PARSER_CONFIG);var i=this,a=ee.cloneObj(n);return a.outputCst=!0,i=t.call(this,r,a)||this,i}return e}(ur);U.CstParser=Ou;var Nu=function(t){ka(e,t);function e(r,n){n===void 0&&(n=U.DEFAULT_PARSER_CONFIG);var i=this,a=ee.cloneObj(n);return a.outputCst=!1,i=t.call(this,r,a)||this,i}return e}(ur);U.EmbeddedActionsParser=Nu});var La=R(cr=>{"use strict";Object.defineProperty(cr,"__esModule",{value:!0});cr.createSyntaxDiagramsCode=void 0;var Ca=Er();function Iu(t,e){var r=e===void 0?{}:e,n=r.resourceBase,i=n===void 0?"https://unpkg.com/chevrotain@"+Ca.VERSION+"/diagrams/":n,a=r.css,o=a===void 0?"https://unpkg.com/chevrotain@"+Ca.VERSION+"/diagrams/diagrams.css":a,s=` + + + + + +`,c=` + +`,f=` + + + + +`,p=` +
+`,l=` + +`,m=` + +`;return s+c+f+p+l+m}cr.createSyntaxDiagramsCode=Iu});var Fa=R(E=>{"use strict";Object.defineProperty(E,"__esModule",{value:!0});E.Parser=E.createSyntaxDiagramsCode=E.clearCache=E.GAstVisitor=E.serializeProduction=E.serializeGrammar=E.Terminal=E.Rule=E.RepetitionWithSeparator=E.RepetitionMandatoryWithSeparator=E.RepetitionMandatory=E.Repetition=E.Option=E.NonTerminal=E.Alternative=E.Alternation=E.defaultLexerErrorProvider=E.NoViableAltException=E.NotAllInputParsedException=E.MismatchedTokenException=E.isRecognitionException=E.EarlyExitException=E.defaultParserErrorProvider=E.tokenName=E.tokenMatcher=E.tokenLabel=E.EOF=E.createTokenInstance=E.createToken=E.LexerDefinitionErrorType=E.Lexer=E.EMPTY_ALT=E.ParserDefinitionErrorType=E.EmbeddedActionsParser=E.CstParser=E.VERSION=void 0;var ku=Er();Object.defineProperty(E,"VERSION",{enumerable:!0,get:function(){return ku.VERSION}});var lr=ce();Object.defineProperty(E,"CstParser",{enumerable:!0,get:function(){return lr.CstParser}});Object.defineProperty(E,"EmbeddedActionsParser",{enumerable:!0,get:function(){return lr.EmbeddedActionsParser}});Object.defineProperty(E,"ParserDefinitionErrorType",{enumerable:!0,get:function(){return lr.ParserDefinitionErrorType}});Object.defineProperty(E,"EMPTY_ALT",{enumerable:!0,get:function(){return lr.EMPTY_ALT}});var Ma=ft();Object.defineProperty(E,"Lexer",{enumerable:!0,get:function(){return Ma.Lexer}});Object.defineProperty(E,"LexerDefinitionErrorType",{enumerable:!0,get:function(){return Ma.LexerDefinitionErrorType}});var nt=Ue();Object.defineProperty(E,"createToken",{enumerable:!0,get:function(){return nt.createToken}});Object.defineProperty(E,"createTokenInstance",{enumerable:!0,get:function(){return nt.createTokenInstance}});Object.defineProperty(E,"EOF",{enumerable:!0,get:function(){return nt.EOF}});Object.defineProperty(E,"tokenLabel",{enumerable:!0,get:function(){return nt.tokenLabel}});Object.defineProperty(E,"tokenMatcher",{enumerable:!0,get:function(){return nt.tokenMatcher}});Object.defineProperty(E,"tokenName",{enumerable:!0,get:function(){return nt.tokenName}});var Pu=mt();Object.defineProperty(E,"defaultParserErrorProvider",{enumerable:!0,get:function(){return Pu.defaultParserErrorProvider}});var Nt=et();Object.defineProperty(E,"EarlyExitException",{enumerable:!0,get:function(){return Nt.EarlyExitException}});Object.defineProperty(E,"isRecognitionException",{enumerable:!0,get:function(){return Nt.isRecognitionException}});Object.defineProperty(E,"MismatchedTokenException",{enumerable:!0,get:function(){return Nt.MismatchedTokenException}});Object.defineProperty(E,"NotAllInputParsedException",{enumerable:!0,get:function(){return Nt.NotAllInputParsedException}});Object.defineProperty(E,"NoViableAltException",{enumerable:!0,get:function(){return Nt.NoViableAltException}});var Su=kr();Object.defineProperty(E,"defaultLexerErrorProvider",{enumerable:!0,get:function(){return Su.defaultLexerErrorProvider}});var Se=ne();Object.defineProperty(E,"Alternation",{enumerable:!0,get:function(){return Se.Alternation}});Object.defineProperty(E,"Alternative",{enumerable:!0,get:function(){return Se.Alternative}});Object.defineProperty(E,"NonTerminal",{enumerable:!0,get:function(){return Se.NonTerminal}});Object.defineProperty(E,"Option",{enumerable:!0,get:function(){return Se.Option}});Object.defineProperty(E,"Repetition",{enumerable:!0,get:function(){return Se.Repetition}});Object.defineProperty(E,"RepetitionMandatory",{enumerable:!0,get:function(){return Se.RepetitionMandatory}});Object.defineProperty(E,"RepetitionMandatoryWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionMandatoryWithSeparator}});Object.defineProperty(E,"RepetitionWithSeparator",{enumerable:!0,get:function(){return Se.RepetitionWithSeparator}});Object.defineProperty(E,"Rule",{enumerable:!0,get:function(){return Se.Rule}});Object.defineProperty(E,"Terminal",{enumerable:!0,get:function(){return Se.Terminal}});var ba=ne();Object.defineProperty(E,"serializeGrammar",{enumerable:!0,get:function(){return ba.serializeGrammar}});Object.defineProperty(E,"serializeProduction",{enumerable:!0,get:function(){return ba.serializeProduction}});var xu=$e();Object.defineProperty(E,"GAstVisitor",{enumerable:!0,get:function(){return xu.GAstVisitor}});function Cu(){console.warn(`The clearCache function was 'soft' removed from the Chevrotain API. + It performs no action other than printing this message. + Please avoid using it as it will be completely removed in the future`)}E.clearCache=Cu;var Lu=La();Object.defineProperty(E,"createSyntaxDiagramsCode",{enumerable:!0,get:function(){return Lu.createSyntaxDiagramsCode}});var Mu=function(){function t(){throw new Error(`The Parser class has been deprecated, use CstParser or EmbeddedActionsParser instead. +See: https://chevrotain.io/docs/changes/BREAKING_CHANGES.html#_7-0-0`)}return t}();E.Parser=Mu});export default Fa(); diff --git a/jsm/libs/ecsy.module.js b/jsm/libs/ecsy.module.js new file mode 100644 index 0000000..6f67599 --- /dev/null +++ b/jsm/libs/ecsy.module.js @@ -0,0 +1,1792 @@ +/** + * Return the name of a component + * @param {Component} Component + * @private + */ + +/** + * Get a key from a list of components + * @param {Array(Component)} Components Array of components to generate the key + * @private + */ +function queryKey(Components) { + var ids = []; + for (var n = 0; n < Components.length; n++) { + var T = Components[n]; + + if (!componentRegistered(T)) { + throw new Error(`Tried to create a query with an unregistered component`); + } + + if (typeof T === "object") { + var operator = T.operator === "not" ? "!" : T.operator; + ids.push(operator + T.Component._typeId); + } else { + ids.push(T._typeId); + } + } + + return ids.sort().join("-"); +} + +// Detector for browser's "window" +const hasWindow = typeof window !== "undefined"; + +// performance.now() "polyfill" +const now = + hasWindow && typeof window.performance !== "undefined" + ? performance.now.bind(performance) + : Date.now.bind(Date); + +function componentRegistered(T) { + return ( + (typeof T === "object" && T.Component._typeId !== undefined) || + (T.isComponent && T._typeId !== undefined) + ); +} + +class SystemManager { + constructor(world) { + this._systems = []; + this._executeSystems = []; // Systems that have `execute` method + this.world = world; + this.lastExecutedSystem = null; + } + + registerSystem(SystemClass, attributes) { + if (!SystemClass.isSystem) { + throw new Error( + `System '${SystemClass.name}' does not extend 'System' class` + ); + } + + if (this.getSystem(SystemClass) !== undefined) { + console.warn(`System '${SystemClass.getName()}' already registered.`); + return this; + } + + var system = new SystemClass(this.world, attributes); + if (system.init) system.init(attributes); + system.order = this._systems.length; + this._systems.push(system); + if (system.execute) { + this._executeSystems.push(system); + this.sortSystems(); + } + return this; + } + + unregisterSystem(SystemClass) { + let system = this.getSystem(SystemClass); + if (system === undefined) { + console.warn( + `Can unregister system '${SystemClass.getName()}'. It doesn't exist.` + ); + return this; + } + + this._systems.splice(this._systems.indexOf(system), 1); + + if (system.execute) { + this._executeSystems.splice(this._executeSystems.indexOf(system), 1); + } + + // @todo Add system.unregister() call to free resources + return this; + } + + sortSystems() { + this._executeSystems.sort((a, b) => { + return a.priority - b.priority || a.order - b.order; + }); + } + + getSystem(SystemClass) { + return this._systems.find((s) => s instanceof SystemClass); + } + + getSystems() { + return this._systems; + } + + removeSystem(SystemClass) { + var index = this._systems.indexOf(SystemClass); + if (!~index) return; + + this._systems.splice(index, 1); + } + + executeSystem(system, delta, time) { + if (system.initialized) { + if (system.canExecute()) { + let startTime = now(); + system.execute(delta, time); + system.executeTime = now() - startTime; + this.lastExecutedSystem = system; + system.clearEvents(); + } + } + } + + stop() { + this._executeSystems.forEach((system) => system.stop()); + } + + execute(delta, time, forcePlay) { + this._executeSystems.forEach( + (system) => + (forcePlay || system.enabled) && this.executeSystem(system, delta, time) + ); + } + + stats() { + var stats = { + numSystems: this._systems.length, + systems: {}, + }; + + for (var i = 0; i < this._systems.length; i++) { + var system = this._systems[i]; + var systemStats = (stats.systems[system.getName()] = { + queries: {}, + executeTime: system.executeTime, + }); + for (var name in system.ctx) { + systemStats.queries[name] = system.ctx[name].stats(); + } + } + + return stats; + } +} + +class ObjectPool { + // @todo Add initial size + constructor(T, initialSize) { + this.freeList = []; + this.count = 0; + this.T = T; + this.isObjectPool = true; + + if (typeof initialSize !== "undefined") { + this.expand(initialSize); + } + } + + acquire() { + // Grow the list by 20%ish if we're out + if (this.freeList.length <= 0) { + this.expand(Math.round(this.count * 0.2) + 1); + } + + var item = this.freeList.pop(); + + return item; + } + + release(item) { + item.reset(); + this.freeList.push(item); + } + + expand(count) { + for (var n = 0; n < count; n++) { + var clone = new this.T(); + clone._pool = this; + this.freeList.push(clone); + } + this.count += count; + } + + totalSize() { + return this.count; + } + + totalFree() { + return this.freeList.length; + } + + totalUsed() { + return this.count - this.freeList.length; + } +} + +/** + * @private + * @class EventDispatcher + */ +class EventDispatcher { + constructor() { + this._listeners = {}; + this.stats = { + fired: 0, + handled: 0, + }; + } + + /** + * Add an event listener + * @param {String} eventName Name of the event to listen + * @param {Function} listener Callback to trigger when the event is fired + */ + addEventListener(eventName, listener) { + let listeners = this._listeners; + if (listeners[eventName] === undefined) { + listeners[eventName] = []; + } + + if (listeners[eventName].indexOf(listener) === -1) { + listeners[eventName].push(listener); + } + } + + /** + * Check if an event listener is already added to the list of listeners + * @param {String} eventName Name of the event to check + * @param {Function} listener Callback for the specified event + */ + hasEventListener(eventName, listener) { + return ( + this._listeners[eventName] !== undefined && + this._listeners[eventName].indexOf(listener) !== -1 + ); + } + + /** + * Remove an event listener + * @param {String} eventName Name of the event to remove + * @param {Function} listener Callback for the specified event + */ + removeEventListener(eventName, listener) { + var listenerArray = this._listeners[eventName]; + if (listenerArray !== undefined) { + var index = listenerArray.indexOf(listener); + if (index !== -1) { + listenerArray.splice(index, 1); + } + } + } + + /** + * Dispatch an event + * @param {String} eventName Name of the event to dispatch + * @param {Entity} entity (Optional) Entity to emit + * @param {Component} component + */ + dispatchEvent(eventName, entity, component) { + this.stats.fired++; + + var listenerArray = this._listeners[eventName]; + if (listenerArray !== undefined) { + var array = listenerArray.slice(0); + + for (var i = 0; i < array.length; i++) { + array[i].call(this, entity, component); + } + } + } + + /** + * Reset stats counters + */ + resetCounters() { + this.stats.fired = this.stats.handled = 0; + } +} + +class Query { + /** + * @param {Array(Component)} Components List of types of components to query + */ + constructor(Components, manager) { + this.Components = []; + this.NotComponents = []; + + Components.forEach((component) => { + if (typeof component === "object") { + this.NotComponents.push(component.Component); + } else { + this.Components.push(component); + } + }); + + if (this.Components.length === 0) { + throw new Error("Can't create a query without components"); + } + + this.entities = []; + + this.eventDispatcher = new EventDispatcher(); + + // This query is being used by a reactive system + this.reactive = false; + + this.key = queryKey(Components); + + // Fill the query with the existing entities + for (var i = 0; i < manager._entities.length; i++) { + var entity = manager._entities[i]; + if (this.match(entity)) { + // @todo ??? this.addEntity(entity); => preventing the event to be generated + entity.queries.push(this); + this.entities.push(entity); + } + } + } + + /** + * Add entity to this query + * @param {Entity} entity + */ + addEntity(entity) { + entity.queries.push(this); + this.entities.push(entity); + + this.eventDispatcher.dispatchEvent(Query.prototype.ENTITY_ADDED, entity); + } + + /** + * Remove entity from this query + * @param {Entity} entity + */ + removeEntity(entity) { + let index = this.entities.indexOf(entity); + if (~index) { + this.entities.splice(index, 1); + + index = entity.queries.indexOf(this); + entity.queries.splice(index, 1); + + this.eventDispatcher.dispatchEvent( + Query.prototype.ENTITY_REMOVED, + entity + ); + } + } + + match(entity) { + return ( + entity.hasAllComponents(this.Components) && + !entity.hasAnyComponents(this.NotComponents) + ); + } + + toJSON() { + return { + key: this.key, + reactive: this.reactive, + components: { + included: this.Components.map((C) => C.name), + not: this.NotComponents.map((C) => C.name), + }, + numEntities: this.entities.length, + }; + } + + /** + * Return stats for this query + */ + stats() { + return { + numComponents: this.Components.length, + numEntities: this.entities.length, + }; + } +} + +Query.prototype.ENTITY_ADDED = "Query#ENTITY_ADDED"; +Query.prototype.ENTITY_REMOVED = "Query#ENTITY_REMOVED"; +Query.prototype.COMPONENT_CHANGED = "Query#COMPONENT_CHANGED"; + +/** + * @private + * @class QueryManager + */ +class QueryManager { + constructor(world) { + this._world = world; + + // Queries indexed by a unique identifier for the components it has + this._queries = {}; + } + + onEntityRemoved(entity) { + for (var queryName in this._queries) { + var query = this._queries[queryName]; + if (entity.queries.indexOf(query) !== -1) { + query.removeEntity(entity); + } + } + } + + /** + * Callback when a component is added to an entity + * @param {Entity} entity Entity that just got the new component + * @param {Component} Component Component added to the entity + */ + onEntityComponentAdded(entity, Component) { + // @todo Use bitmask for checking components? + + // Check each indexed query to see if we need to add this entity to the list + for (var queryName in this._queries) { + var query = this._queries[queryName]; + + if ( + !!~query.NotComponents.indexOf(Component) && + ~query.entities.indexOf(entity) + ) { + query.removeEntity(entity); + continue; + } + + // Add the entity only if: + // Component is in the query + // and Entity has ALL the components of the query + // and Entity is not already in the query + if ( + !~query.Components.indexOf(Component) || + !query.match(entity) || + ~query.entities.indexOf(entity) + ) + continue; + + query.addEntity(entity); + } + } + + /** + * Callback when a component is removed from an entity + * @param {Entity} entity Entity to remove the component from + * @param {Component} Component Component to remove from the entity + */ + onEntityComponentRemoved(entity, Component) { + for (var queryName in this._queries) { + var query = this._queries[queryName]; + + if ( + !!~query.NotComponents.indexOf(Component) && + !~query.entities.indexOf(entity) && + query.match(entity) + ) { + query.addEntity(entity); + continue; + } + + if ( + !!~query.Components.indexOf(Component) && + !!~query.entities.indexOf(entity) && + !query.match(entity) + ) { + query.removeEntity(entity); + continue; + } + } + } + + /** + * Get a query for the specified components + * @param {Component} Components Components that the query should have + */ + getQuery(Components) { + var key = queryKey(Components); + var query = this._queries[key]; + if (!query) { + this._queries[key] = query = new Query(Components, this._world); + } + return query; + } + + /** + * Return some stats from this class + */ + stats() { + var stats = {}; + for (var queryName in this._queries) { + stats[queryName] = this._queries[queryName].stats(); + } + return stats; + } +} + +class Component { + constructor(props) { + if (props !== false) { + const schema = this.constructor.schema; + + for (const key in schema) { + if (props && props.hasOwnProperty(key)) { + this[key] = props[key]; + } else { + const schemaProp = schema[key]; + if (schemaProp.hasOwnProperty("default")) { + this[key] = schemaProp.type.clone(schemaProp.default); + } else { + const type = schemaProp.type; + this[key] = type.clone(type.default); + } + } + } + + if ( props !== undefined) { + this.checkUndefinedAttributes(props); + } + } + + this._pool = null; + } + + copy(source) { + const schema = this.constructor.schema; + + for (const key in schema) { + const prop = schema[key]; + + if (source.hasOwnProperty(key)) { + this[key] = prop.type.copy(source[key], this[key]); + } + } + + // @DEBUG + { + this.checkUndefinedAttributes(source); + } + + return this; + } + + clone() { + return new this.constructor().copy(this); + } + + reset() { + const schema = this.constructor.schema; + + for (const key in schema) { + const schemaProp = schema[key]; + + if (schemaProp.hasOwnProperty("default")) { + this[key] = schemaProp.type.copy(schemaProp.default, this[key]); + } else { + const type = schemaProp.type; + this[key] = type.copy(type.default, this[key]); + } + } + } + + dispose() { + if (this._pool) { + this._pool.release(this); + } + } + + getName() { + return this.constructor.getName(); + } + + checkUndefinedAttributes(src) { + const schema = this.constructor.schema; + + // Check that the attributes defined in source are also defined in the schema + Object.keys(src).forEach((srcKey) => { + if (!schema.hasOwnProperty(srcKey)) { + console.warn( + `Trying to set attribute '${srcKey}' not defined in the '${this.constructor.name}' schema. Please fix the schema, the attribute value won't be set` + ); + } + }); + } +} + +Component.schema = {}; +Component.isComponent = true; +Component.getName = function () { + return this.displayName || this.name; +}; + +class SystemStateComponent extends Component {} + +SystemStateComponent.isSystemStateComponent = true; + +class EntityPool extends ObjectPool { + constructor(entityManager, entityClass, initialSize) { + super(entityClass, undefined); + this.entityManager = entityManager; + + if (typeof initialSize !== "undefined") { + this.expand(initialSize); + } + } + + expand(count) { + for (var n = 0; n < count; n++) { + var clone = new this.T(this.entityManager); + clone._pool = this; + this.freeList.push(clone); + } + this.count += count; + } +} + +/** + * @private + * @class EntityManager + */ +class EntityManager { + constructor(world) { + this.world = world; + this.componentsManager = world.componentsManager; + + // All the entities in this instance + this._entities = []; + this._nextEntityId = 0; + + this._entitiesByNames = {}; + + this._queryManager = new QueryManager(this); + this.eventDispatcher = new EventDispatcher(); + this._entityPool = new EntityPool( + this, + this.world.options.entityClass, + this.world.options.entityPoolSize + ); + + // Deferred deletion + this.entitiesWithComponentsToRemove = []; + this.entitiesToRemove = []; + this.deferredRemovalEnabled = true; + } + + getEntityByName(name) { + return this._entitiesByNames[name]; + } + + /** + * Create a new entity + */ + createEntity(name) { + var entity = this._entityPool.acquire(); + entity.alive = true; + entity.name = name || ""; + if (name) { + if (this._entitiesByNames[name]) { + console.warn(`Entity name '${name}' already exist`); + } else { + this._entitiesByNames[name] = entity; + } + } + + this._entities.push(entity); + this.eventDispatcher.dispatchEvent(ENTITY_CREATED, entity); + return entity; + } + + // COMPONENTS + + /** + * Add a component to an entity + * @param {Entity} entity Entity where the component will be added + * @param {Component} Component Component to be added to the entity + * @param {Object} values Optional values to replace the default attributes + */ + entityAddComponent(entity, Component, values) { + // @todo Probably define Component._typeId with a default value and avoid using typeof + if ( + typeof Component._typeId === "undefined" && + !this.world.componentsManager._ComponentsMap[Component._typeId] + ) { + throw new Error( + `Attempted to add unregistered component "${Component.getName()}"` + ); + } + + if (~entity._ComponentTypes.indexOf(Component)) { + { + console.warn( + "Component type already exists on entity.", + entity, + Component.getName() + ); + } + return; + } + + entity._ComponentTypes.push(Component); + + if (Component.__proto__ === SystemStateComponent) { + entity.numStateComponents++; + } + + var componentPool = this.world.componentsManager.getComponentsPool( + Component + ); + + var component = componentPool + ? componentPool.acquire() + : new Component(values); + + if (componentPool && values) { + component.copy(values); + } + + entity._components[Component._typeId] = component; + + this._queryManager.onEntityComponentAdded(entity, Component); + this.world.componentsManager.componentAddedToEntity(Component); + + this.eventDispatcher.dispatchEvent(COMPONENT_ADDED, entity, Component); + } + + /** + * Remove a component from an entity + * @param {Entity} entity Entity which will get removed the component + * @param {*} Component Component to remove from the entity + * @param {Bool} immediately If you want to remove the component immediately instead of deferred (Default is false) + */ + entityRemoveComponent(entity, Component, immediately) { + var index = entity._ComponentTypes.indexOf(Component); + if (!~index) return; + + this.eventDispatcher.dispatchEvent(COMPONENT_REMOVE, entity, Component); + + if (immediately) { + this._entityRemoveComponentSync(entity, Component, index); + } else { + if (entity._ComponentTypesToRemove.length === 0) + this.entitiesWithComponentsToRemove.push(entity); + + entity._ComponentTypes.splice(index, 1); + entity._ComponentTypesToRemove.push(Component); + + entity._componentsToRemove[Component._typeId] = + entity._components[Component._typeId]; + delete entity._components[Component._typeId]; + } + + // Check each indexed query to see if we need to remove it + this._queryManager.onEntityComponentRemoved(entity, Component); + + if (Component.__proto__ === SystemStateComponent) { + entity.numStateComponents--; + + // Check if the entity was a ghost waiting for the last system state component to be removed + if (entity.numStateComponents === 0 && !entity.alive) { + entity.remove(); + } + } + } + + _entityRemoveComponentSync(entity, Component, index) { + // Remove T listing on entity and property ref, then free the component. + entity._ComponentTypes.splice(index, 1); + var component = entity._components[Component._typeId]; + delete entity._components[Component._typeId]; + component.dispose(); + this.world.componentsManager.componentRemovedFromEntity(Component); + } + + /** + * Remove all the components from an entity + * @param {Entity} entity Entity from which the components will be removed + */ + entityRemoveAllComponents(entity, immediately) { + let Components = entity._ComponentTypes; + + for (let j = Components.length - 1; j >= 0; j--) { + if (Components[j].__proto__ !== SystemStateComponent) + this.entityRemoveComponent(entity, Components[j], immediately); + } + } + + /** + * Remove the entity from this manager. It will clear also its components + * @param {Entity} entity Entity to remove from the manager + * @param {Bool} immediately If you want to remove the component immediately instead of deferred (Default is false) + */ + removeEntity(entity, immediately) { + var index = this._entities.indexOf(entity); + + if (!~index) throw new Error("Tried to remove entity not in list"); + + entity.alive = false; + this.entityRemoveAllComponents(entity, immediately); + + if (entity.numStateComponents === 0) { + // Remove from entity list + this.eventDispatcher.dispatchEvent(ENTITY_REMOVED, entity); + this._queryManager.onEntityRemoved(entity); + if (immediately === true) { + this._releaseEntity(entity, index); + } else { + this.entitiesToRemove.push(entity); + } + } + } + + _releaseEntity(entity, index) { + this._entities.splice(index, 1); + + if (this._entitiesByNames[entity.name]) { + delete this._entitiesByNames[entity.name]; + } + entity._pool.release(entity); + } + + /** + * Remove all entities from this manager + */ + removeAllEntities() { + for (var i = this._entities.length - 1; i >= 0; i--) { + this.removeEntity(this._entities[i]); + } + } + + processDeferredRemoval() { + if (!this.deferredRemovalEnabled) { + return; + } + + for (let i = 0; i < this.entitiesToRemove.length; i++) { + let entity = this.entitiesToRemove[i]; + let index = this._entities.indexOf(entity); + this._releaseEntity(entity, index); + } + this.entitiesToRemove.length = 0; + + for (let i = 0; i < this.entitiesWithComponentsToRemove.length; i++) { + let entity = this.entitiesWithComponentsToRemove[i]; + while (entity._ComponentTypesToRemove.length > 0) { + let Component = entity._ComponentTypesToRemove.pop(); + + var component = entity._componentsToRemove[Component._typeId]; + delete entity._componentsToRemove[Component._typeId]; + component.dispose(); + this.world.componentsManager.componentRemovedFromEntity(Component); + + //this._entityRemoveComponentSync(entity, Component, index); + } + } + + this.entitiesWithComponentsToRemove.length = 0; + } + + /** + * Get a query based on a list of components + * @param {Array(Component)} Components List of components that will form the query + */ + queryComponents(Components) { + return this._queryManager.getQuery(Components); + } + + // EXTRAS + + /** + * Return number of entities + */ + count() { + return this._entities.length; + } + + /** + * Return some stats + */ + stats() { + var stats = { + numEntities: this._entities.length, + numQueries: Object.keys(this._queryManager._queries).length, + queries: this._queryManager.stats(), + numComponentPool: Object.keys(this.componentsManager._componentPool) + .length, + componentPool: {}, + eventDispatcher: this.eventDispatcher.stats, + }; + + for (var ecsyComponentId in this.componentsManager._componentPool) { + var pool = this.componentsManager._componentPool[ecsyComponentId]; + stats.componentPool[pool.T.getName()] = { + used: pool.totalUsed(), + size: pool.count, + }; + } + + return stats; + } +} + +const ENTITY_CREATED = "EntityManager#ENTITY_CREATE"; +const ENTITY_REMOVED = "EntityManager#ENTITY_REMOVED"; +const COMPONENT_ADDED = "EntityManager#COMPONENT_ADDED"; +const COMPONENT_REMOVE = "EntityManager#COMPONENT_REMOVE"; + +class ComponentManager { + constructor() { + this.Components = []; + this._ComponentsMap = {}; + + this._componentPool = {}; + this.numComponents = {}; + this.nextComponentId = 0; + } + + hasComponent(Component) { + return this.Components.indexOf(Component) !== -1; + } + + registerComponent(Component, objectPool) { + if (this.Components.indexOf(Component) !== -1) { + console.warn( + `Component type: '${Component.getName()}' already registered.` + ); + return; + } + + const schema = Component.schema; + + if (!schema) { + throw new Error( + `Component "${Component.getName()}" has no schema property.` + ); + } + + for (const propName in schema) { + const prop = schema[propName]; + + if (!prop.type) { + throw new Error( + `Invalid schema for component "${Component.getName()}". Missing type for "${propName}" property.` + ); + } + } + + Component._typeId = this.nextComponentId++; + this.Components.push(Component); + this._ComponentsMap[Component._typeId] = Component; + this.numComponents[Component._typeId] = 0; + + if (objectPool === undefined) { + objectPool = new ObjectPool(Component); + } else if (objectPool === false) { + objectPool = undefined; + } + + this._componentPool[Component._typeId] = objectPool; + } + + componentAddedToEntity(Component) { + this.numComponents[Component._typeId]++; + } + + componentRemovedFromEntity(Component) { + this.numComponents[Component._typeId]--; + } + + getComponentsPool(Component) { + return this._componentPool[Component._typeId]; + } +} + +const Version = "0.3.1"; + +const proxyMap = new WeakMap(); + +const proxyHandler = { + set(target, prop) { + throw new Error( + `Tried to write to "${target.constructor.getName()}#${String( + prop + )}" on immutable component. Use .getMutableComponent() to modify a component.` + ); + }, +}; + +function wrapImmutableComponent(T, component) { + if (component === undefined) { + return undefined; + } + + let wrappedComponent = proxyMap.get(component); + + if (!wrappedComponent) { + wrappedComponent = new Proxy(component, proxyHandler); + proxyMap.set(component, wrappedComponent); + } + + return wrappedComponent; +} + +class Entity { + constructor(entityManager) { + this._entityManager = entityManager || null; + + // Unique ID for this entity + this.id = entityManager._nextEntityId++; + + // List of components types the entity has + this._ComponentTypes = []; + + // Instance of the components + this._components = {}; + + this._componentsToRemove = {}; + + // Queries where the entity is added + this.queries = []; + + // Used for deferred removal + this._ComponentTypesToRemove = []; + + this.alive = false; + + //if there are state components on a entity, it can't be removed completely + this.numStateComponents = 0; + } + + // COMPONENTS + + getComponent(Component, includeRemoved) { + var component = this._components[Component._typeId]; + + if (!component && includeRemoved === true) { + component = this._componentsToRemove[Component._typeId]; + } + + return wrapImmutableComponent(Component, component) + ; + } + + getRemovedComponent(Component) { + const component = this._componentsToRemove[Component._typeId]; + + return wrapImmutableComponent(Component, component) + ; + } + + getComponents() { + return this._components; + } + + getComponentsToRemove() { + return this._componentsToRemove; + } + + getComponentTypes() { + return this._ComponentTypes; + } + + getMutableComponent(Component) { + var component = this._components[Component._typeId]; + + if (!component) { + return; + } + + for (var i = 0; i < this.queries.length; i++) { + var query = this.queries[i]; + // @todo accelerate this check. Maybe having query._Components as an object + // @todo add Not components + if (query.reactive && query.Components.indexOf(Component) !== -1) { + query.eventDispatcher.dispatchEvent( + Query.prototype.COMPONENT_CHANGED, + this, + component + ); + } + } + return component; + } + + addComponent(Component, values) { + this._entityManager.entityAddComponent(this, Component, values); + return this; + } + + removeComponent(Component, forceImmediate) { + this._entityManager.entityRemoveComponent(this, Component, forceImmediate); + return this; + } + + hasComponent(Component, includeRemoved) { + return ( + !!~this._ComponentTypes.indexOf(Component) || + (includeRemoved === true && this.hasRemovedComponent(Component)) + ); + } + + hasRemovedComponent(Component) { + return !!~this._ComponentTypesToRemove.indexOf(Component); + } + + hasAllComponents(Components) { + for (var i = 0; i < Components.length; i++) { + if (!this.hasComponent(Components[i])) return false; + } + return true; + } + + hasAnyComponents(Components) { + for (var i = 0; i < Components.length; i++) { + if (this.hasComponent(Components[i])) return true; + } + return false; + } + + removeAllComponents(forceImmediate) { + return this._entityManager.entityRemoveAllComponents(this, forceImmediate); + } + + copy(src) { + // TODO: This can definitely be optimized + for (var ecsyComponentId in src._components) { + var srcComponent = src._components[ecsyComponentId]; + this.addComponent(srcComponent.constructor); + var component = this.getComponent(srcComponent.constructor); + component.copy(srcComponent); + } + + return this; + } + + clone() { + return new Entity(this._entityManager).copy(this); + } + + reset() { + this.id = this._entityManager._nextEntityId++; + this._ComponentTypes.length = 0; + this.queries.length = 0; + + for (var ecsyComponentId in this._components) { + delete this._components[ecsyComponentId]; + } + } + + remove(forceImmediate) { + return this._entityManager.removeEntity(this, forceImmediate); + } +} + +const DEFAULT_OPTIONS = { + entityPoolSize: 0, + entityClass: Entity, +}; + +class World { + constructor(options = {}) { + this.options = Object.assign({}, DEFAULT_OPTIONS, options); + + this.componentsManager = new ComponentManager(this); + this.entityManager = new EntityManager(this); + this.systemManager = new SystemManager(this); + + this.enabled = true; + + this.eventQueues = {}; + + if (hasWindow && typeof CustomEvent !== "undefined") { + var event = new CustomEvent("ecsy-world-created", { + detail: { world: this, version: Version }, + }); + window.dispatchEvent(event); + } + + this.lastTime = now() / 1000; + } + + registerComponent(Component, objectPool) { + this.componentsManager.registerComponent(Component, objectPool); + return this; + } + + registerSystem(System, attributes) { + this.systemManager.registerSystem(System, attributes); + return this; + } + + hasRegisteredComponent(Component) { + return this.componentsManager.hasComponent(Component); + } + + unregisterSystem(System) { + this.systemManager.unregisterSystem(System); + return this; + } + + getSystem(SystemClass) { + return this.systemManager.getSystem(SystemClass); + } + + getSystems() { + return this.systemManager.getSystems(); + } + + execute(delta, time) { + if (!delta) { + time = now() / 1000; + delta = time - this.lastTime; + this.lastTime = time; + } + + if (this.enabled) { + this.systemManager.execute(delta, time); + this.entityManager.processDeferredRemoval(); + } + } + + stop() { + this.enabled = false; + } + + play() { + this.enabled = true; + } + + createEntity(name) { + return this.entityManager.createEntity(name); + } + + stats() { + var stats = { + entities: this.entityManager.stats(), + system: this.systemManager.stats(), + }; + + return stats; + } +} + +class System { + canExecute() { + if (this._mandatoryQueries.length === 0) return true; + + for (let i = 0; i < this._mandatoryQueries.length; i++) { + var query = this._mandatoryQueries[i]; + if (query.entities.length === 0) { + return false; + } + } + + return true; + } + + getName() { + return this.constructor.getName(); + } + + constructor(world, attributes) { + this.world = world; + this.enabled = true; + + // @todo Better naming :) + this._queries = {}; + this.queries = {}; + + this.priority = 0; + + // Used for stats + this.executeTime = 0; + + if (attributes && attributes.priority) { + this.priority = attributes.priority; + } + + this._mandatoryQueries = []; + + this.initialized = true; + + if (this.constructor.queries) { + for (var queryName in this.constructor.queries) { + var queryConfig = this.constructor.queries[queryName]; + var Components = queryConfig.components; + if (!Components || Components.length === 0) { + throw new Error("'components' attribute can't be empty in a query"); + } + + // Detect if the components have already been registered + let unregisteredComponents = Components.filter( + (Component) => !componentRegistered(Component) + ); + + if (unregisteredComponents.length > 0) { + throw new Error( + `Tried to create a query '${ + this.constructor.name + }.${queryName}' with unregistered components: [${unregisteredComponents + .map((c) => c.getName()) + .join(", ")}]` + ); + } + + var query = this.world.entityManager.queryComponents(Components); + + this._queries[queryName] = query; + if (queryConfig.mandatory === true) { + this._mandatoryQueries.push(query); + } + this.queries[queryName] = { + results: query.entities, + }; + + // Reactive configuration added/removed/changed + var validEvents = ["added", "removed", "changed"]; + + const eventMapping = { + added: Query.prototype.ENTITY_ADDED, + removed: Query.prototype.ENTITY_REMOVED, + changed: Query.prototype.COMPONENT_CHANGED, // Query.prototype.ENTITY_CHANGED + }; + + if (queryConfig.listen) { + validEvents.forEach((eventName) => { + if (!this.execute) { + console.warn( + `System '${this.getName()}' has defined listen events (${validEvents.join( + ", " + )}) for query '${queryName}' but it does not implement the 'execute' method.` + ); + } + + // Is the event enabled on this system's query? + if (queryConfig.listen[eventName]) { + let event = queryConfig.listen[eventName]; + + if (eventName === "changed") { + query.reactive = true; + if (event === true) { + // Any change on the entity from the components in the query + let eventList = (this.queries[queryName][eventName] = []); + query.eventDispatcher.addEventListener( + Query.prototype.COMPONENT_CHANGED, + (entity) => { + // Avoid duplicates + if (eventList.indexOf(entity) === -1) { + eventList.push(entity); + } + } + ); + } else if (Array.isArray(event)) { + let eventList = (this.queries[queryName][eventName] = []); + query.eventDispatcher.addEventListener( + Query.prototype.COMPONENT_CHANGED, + (entity, changedComponent) => { + // Avoid duplicates + if ( + event.indexOf(changedComponent.constructor) !== -1 && + eventList.indexOf(entity) === -1 + ) { + eventList.push(entity); + } + } + ); + } + } else { + let eventList = (this.queries[queryName][eventName] = []); + + query.eventDispatcher.addEventListener( + eventMapping[eventName], + (entity) => { + // @fixme overhead? + if (eventList.indexOf(entity) === -1) + eventList.push(entity); + } + ); + } + } + }); + } + } + } + } + + stop() { + this.executeTime = 0; + this.enabled = false; + } + + play() { + this.enabled = true; + } + + // @question rename to clear queues? + clearEvents() { + for (let queryName in this.queries) { + var query = this.queries[queryName]; + if (query.added) { + query.added.length = 0; + } + if (query.removed) { + query.removed.length = 0; + } + if (query.changed) { + if (Array.isArray(query.changed)) { + query.changed.length = 0; + } else { + for (let name in query.changed) { + query.changed[name].length = 0; + } + } + } + } + } + + toJSON() { + var json = { + name: this.getName(), + enabled: this.enabled, + executeTime: this.executeTime, + priority: this.priority, + queries: {}, + }; + + if (this.constructor.queries) { + var queries = this.constructor.queries; + for (let queryName in queries) { + let query = this.queries[queryName]; + let queryDefinition = queries[queryName]; + let jsonQuery = (json.queries[queryName] = { + key: this._queries[queryName].key, + }); + + jsonQuery.mandatory = queryDefinition.mandatory === true; + jsonQuery.reactive = + queryDefinition.listen && + (queryDefinition.listen.added === true || + queryDefinition.listen.removed === true || + queryDefinition.listen.changed === true || + Array.isArray(queryDefinition.listen.changed)); + + if (jsonQuery.reactive) { + jsonQuery.listen = {}; + + const methods = ["added", "removed", "changed"]; + methods.forEach((method) => { + if (query[method]) { + jsonQuery.listen[method] = { + entities: query[method].length, + }; + } + }); + } + } + } + + return json; + } +} + +System.isSystem = true; +System.getName = function () { + return this.displayName || this.name; +}; + +function Not(Component) { + return { + operator: "not", + Component: Component, + }; +} + +class TagComponent extends Component { + constructor() { + super(false); + } +} + +TagComponent.isTagComponent = true; + +const copyValue = (src) => src; + +const cloneValue = (src) => src; + +const copyArray = (src, dest) => { + if (!src) { + return src; + } + + if (!dest) { + return src.slice(); + } + + dest.length = 0; + + for (let i = 0; i < src.length; i++) { + dest.push(src[i]); + } + + return dest; +}; + +const cloneArray = (src) => src && src.slice(); + +const copyJSON = (src) => JSON.parse(JSON.stringify(src)); + +const cloneJSON = (src) => JSON.parse(JSON.stringify(src)); + +const copyCopyable = (src, dest) => { + if (!src) { + return src; + } + + if (!dest) { + return src.clone(); + } + + return dest.copy(src); +}; + +const cloneClonable = (src) => src && src.clone(); + +function createType(typeDefinition) { + var mandatoryProperties = ["name", "default", "copy", "clone"]; + + var undefinedProperties = mandatoryProperties.filter((p) => { + return !typeDefinition.hasOwnProperty(p); + }); + + if (undefinedProperties.length > 0) { + throw new Error( + `createType expects a type definition with the following properties: ${undefinedProperties.join( + ", " + )}` + ); + } + + typeDefinition.isType = true; + + return typeDefinition; +} + +/** + * Standard types + */ +const Types = { + Number: createType({ + name: "Number", + default: 0, + copy: copyValue, + clone: cloneValue, + }), + + Boolean: createType({ + name: "Boolean", + default: false, + copy: copyValue, + clone: cloneValue, + }), + + String: createType({ + name: "String", + default: "", + copy: copyValue, + clone: cloneValue, + }), + + Array: createType({ + name: "Array", + default: [], + copy: copyArray, + clone: cloneArray, + }), + + Ref: createType({ + name: "Ref", + default: undefined, + copy: copyValue, + clone: cloneValue, + }), + + JSON: createType({ + name: "JSON", + default: null, + copy: copyJSON, + clone: cloneJSON, + }), +}; + +function generateId(length) { + var result = ""; + var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + var charactersLength = characters.length; + for (var i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +function injectScript(src, onLoad) { + var script = document.createElement("script"); + // @todo Use link to the ecsy-devtools repo? + script.src = src; + script.onload = onLoad; + (document.head || document.documentElement).appendChild(script); +} + +/* global Peer */ + +function hookConsoleAndErrors(connection) { + var wrapFunctions = ["error", "warning", "log"]; + wrapFunctions.forEach((key) => { + if (typeof console[key] === "function") { + var fn = console[key].bind(console); + console[key] = (...args) => { + connection.send({ + method: "console", + type: key, + args: JSON.stringify(args), + }); + return fn.apply(null, args); + }; + } + }); + + window.addEventListener("error", (error) => { + connection.send({ + method: "error", + error: JSON.stringify({ + message: error.error.message, + stack: error.error.stack, + }), + }); + }); +} + +function includeRemoteIdHTML(remoteId) { + let infoDiv = document.createElement("div"); + infoDiv.style.cssText = ` + align-items: center; + background-color: #333; + color: #aaa; + display:flex; + font-family: Arial; + font-size: 1.1em; + height: 40px; + justify-content: center; + left: 0; + opacity: 0.9; + position: absolute; + right: 0; + text-align: center; + top: 0; + `; + + infoDiv.innerHTML = `Open ECSY devtools to connect to this page using the code: ${remoteId} `; + document.body.appendChild(infoDiv); + + return infoDiv; +} + +function enableRemoteDevtools(remoteId) { + if (!hasWindow) { + console.warn("Remote devtools not available outside the browser"); + return; + } + + window.generateNewCode = () => { + window.localStorage.clear(); + remoteId = generateId(6); + window.localStorage.setItem("ecsyRemoteId", remoteId); + window.location.reload(false); + }; + + remoteId = remoteId || window.localStorage.getItem("ecsyRemoteId"); + if (!remoteId) { + remoteId = generateId(6); + window.localStorage.setItem("ecsyRemoteId", remoteId); + } + + let infoDiv = includeRemoteIdHTML(remoteId); + + window.__ECSY_REMOTE_DEVTOOLS_INJECTED = true; + window.__ECSY_REMOTE_DEVTOOLS = {}; + + let Version = ""; + + // This is used to collect the worlds created before the communication is being established + let worldsBeforeLoading = []; + let onWorldCreated = (e) => { + var world = e.detail.world; + Version = e.detail.version; + worldsBeforeLoading.push(world); + }; + window.addEventListener("ecsy-world-created", onWorldCreated); + + let onLoaded = () => { + // var peer = new Peer(remoteId); + var peer = new Peer(remoteId, { + host: "peerjs.ecsy.io", + secure: true, + port: 443, + config: { + iceServers: [ + { url: "stun:stun.l.google.com:19302" }, + { url: "stun:stun1.l.google.com:19302" }, + { url: "stun:stun2.l.google.com:19302" }, + { url: "stun:stun3.l.google.com:19302" }, + { url: "stun:stun4.l.google.com:19302" }, + ], + }, + debug: 3, + }); + + peer.on("open", (/* id */) => { + peer.on("connection", (connection) => { + window.__ECSY_REMOTE_DEVTOOLS.connection = connection; + connection.on("open", function () { + // infoDiv.style.visibility = "hidden"; + infoDiv.innerHTML = "Connected"; + + // Receive messages + connection.on("data", function (data) { + if (data.type === "init") { + var script = document.createElement("script"); + script.setAttribute("type", "text/javascript"); + script.onload = () => { + script.parentNode.removeChild(script); + + // Once the script is injected we don't need to listen + window.removeEventListener( + "ecsy-world-created", + onWorldCreated + ); + worldsBeforeLoading.forEach((world) => { + var event = new CustomEvent("ecsy-world-created", { + detail: { world: world, version: Version }, + }); + window.dispatchEvent(event); + }); + }; + script.innerHTML = data.script; + (document.head || document.documentElement).appendChild(script); + script.onload(); + + hookConsoleAndErrors(connection); + } else if (data.type === "executeScript") { + let value = eval(data.script); + if (data.returnEval) { + connection.send({ + method: "evalReturn", + value: value, + }); + } + } + }); + }); + }); + }); + }; + + // Inject PeerJS script + injectScript( + "https://cdn.jsdelivr.net/npm/peerjs@0.3.20/dist/peer.min.js", + onLoaded + ); +} + +if (hasWindow) { + const urlParams = new URLSearchParams(window.location.search); + + // @todo Provide a way to disable it if needed + if (urlParams.has("enable-remote-devtools")) { + enableRemoteDevtools(); + } +} + +export { Component, Not, ObjectPool, System, SystemStateComponent, TagComponent, Types, Version, World, Entity as _Entity, cloneArray, cloneClonable, cloneJSON, cloneValue, copyArray, copyCopyable, copyJSON, copyValue, createType, enableRemoteDevtools }; diff --git a/jsm/libs/fflate.module.js b/jsm/libs/fflate.module.js new file mode 100644 index 0000000..808000a --- /dev/null +++ b/jsm/libs/fflate.module.js @@ -0,0 +1,2474 @@ +/*! +fflate - fast JavaScript compression/decompression + +Licensed under MIT. https://github.com/101arrowz/fflate/blob/master/LICENSE +version 0.6.9 +*/ + +// DEFLATE is a complex format; to read this code, you should probably check the RFC first: +// https://tools.ietf.org/html/rfc1951 +// You may also wish to take a look at the guide I made about this program: +// https://gist.github.com/101arrowz/253f31eb5abc3d9275ab943003ffecad +// Some of the following code is similar to that of UZIP.js: +// https://github.com/photopea/UZIP.js +// However, the vast majority of the codebase has diverged from UZIP.js to increase performance and reduce bundle size. +// Sometimes 0 will appear where -1 would be more appropriate. This is because using a uint +// is better for memory in most engines (I *think*). +var ch2 = {}; +var durl = function (c) { return URL.createObjectURL(new Blob([c], { type: 'text/javascript' })); }; +var cwk = function (u) { return new Worker(u); }; +try { + URL.revokeObjectURL(durl('')); +} +catch (e) { + // We're in Deno or a very old browser + durl = function (c) { return 'data:application/javascript;charset=UTF-8,' + encodeURI(c); }; + // If Deno, this is necessary; if not, this changes nothing + cwk = function (u) { return new Worker(u, { type: 'module' }); }; +} +var wk = (function (c, id, msg, transfer, cb) { + var w = cwk(ch2[id] || (ch2[id] = durl(c))); + w.onerror = function (e) { return cb(e.error, null); }; + w.onmessage = function (e) { return cb(null, e.data); }; + w.postMessage(msg, transfer); + return w; +}); + +// aliases for shorter compressed code (most minifers don't do this) +var u8 = Uint8Array, u16 = Uint16Array, u32 = Uint32Array; +// fixed length extra bits +var fleb = new u8([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, /* unused */ 0, 0, /* impossible */ 0]); +// fixed distance extra bits +// see fleb note +var fdeb = new u8([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, /* unused */ 0, 0]); +// code length index map +var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); +// get base, reverse index map from extra bits +var freb = function (eb, start) { + var b = new u16(31); + for (var i = 0; i < 31; ++i) { + b[i] = start += 1 << eb[i - 1]; + } + // numbers here are at max 18 bits + var r = new u32(b[30]); + for (var i = 1; i < 30; ++i) { + for (var j = b[i]; j < b[i + 1]; ++j) { + r[j] = ((j - b[i]) << 5) | i; + } + } + return [b, r]; +}; +var _a = freb(fleb, 2), fl = _a[0], revfl = _a[1]; +// we can ignore the fact that the other numbers are wrong; they never happen anyway +fl[28] = 258, revfl[258] = 28; +var _b = freb(fdeb, 0), fd = _b[0], revfd = _b[1]; +// map of value to reverse (assuming 16 bits) +var rev = new u16(32768); +for (var i = 0; i < 32768; ++i) { + // reverse table algorithm from SO + var x = ((i & 0xAAAA) >>> 1) | ((i & 0x5555) << 1); + x = ((x & 0xCCCC) >>> 2) | ((x & 0x3333) << 2); + x = ((x & 0xF0F0) >>> 4) | ((x & 0x0F0F) << 4); + rev[i] = (((x & 0xFF00) >>> 8) | ((x & 0x00FF) << 8)) >>> 1; +} +// create huffman tree from u8 "map": index -> code length for code index +// mb (max bits) must be at most 15 +// TODO: optimize/split up? +var hMap = (function (cd, mb, r) { + var s = cd.length; + // index + var i = 0; + // u16 "map": index -> # of codes with bit length = index + var l = new u16(mb); + // length of cd must be 288 (total # of codes) + for (; i < s; ++i) + ++l[cd[i] - 1]; + // u16 "map": index -> minimum code for bit length = index + var le = new u16(mb); + for (i = 0; i < mb; ++i) { + le[i] = (le[i - 1] + l[i - 1]) << 1; + } + var co; + if (r) { + // u16 "map": index -> number of actual bits, symbol for code + co = new u16(1 << mb); + // bits to remove for reverser + var rvb = 15 - mb; + for (i = 0; i < s; ++i) { + // ignore 0 lengths + if (cd[i]) { + // num encoding both symbol and bits read + var sv = (i << 4) | cd[i]; + // free bits + var r_1 = mb - cd[i]; + // start value + var v = le[cd[i] - 1]++ << r_1; + // m is end value + for (var m = v | ((1 << r_1) - 1); v <= m; ++v) { + // every 16 bit value starting with the code yields the same result + co[rev[v] >>> rvb] = sv; + } + } + } + } + else { + co = new u16(s); + for (i = 0; i < s; ++i) { + if (cd[i]) { + co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]); + } + } + } + return co; +}); +// fixed length tree +var flt = new u8(288); +for (var i = 0; i < 144; ++i) + flt[i] = 8; +for (var i = 144; i < 256; ++i) + flt[i] = 9; +for (var i = 256; i < 280; ++i) + flt[i] = 7; +for (var i = 280; i < 288; ++i) + flt[i] = 8; +// fixed distance tree +var fdt = new u8(32); +for (var i = 0; i < 32; ++i) + fdt[i] = 5; +// fixed length map +var flm = /*#__PURE__*/ hMap(flt, 9, 0), flrm = /*#__PURE__*/ hMap(flt, 9, 1); +// fixed distance map +var fdm = /*#__PURE__*/ hMap(fdt, 5, 0), fdrm = /*#__PURE__*/ hMap(fdt, 5, 1); +// find max of array +var max = function (a) { + var m = a[0]; + for (var i = 1; i < a.length; ++i) { + if (a[i] > m) + m = a[i]; + } + return m; +}; +// read d, starting at bit p and mask with m +var bits = function (d, p, m) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m; +}; +// read d, starting at bit p continuing for at least 16 bits +var bits16 = function (d, p) { + var o = (p / 8) | 0; + return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7)); +}; +// get end of byte +var shft = function (p) { return ((p / 8) | 0) + (p & 7 && 1); }; +// typed array slice - allows garbage collector to free original reference, +// while being more compatible than .slice +var slc = function (v, s, e) { + if (s == null || s < 0) + s = 0; + if (e == null || e > v.length) + e = v.length; + // can't use .constructor in case user-supplied + var n = new (v instanceof u16 ? u16 : v instanceof u32 ? u32 : u8)(e - s); + n.set(v.subarray(s, e)); + return n; +}; +// expands raw DEFLATE data +var inflt = function (dat, buf, st) { + // source length + var sl = dat.length; + if (!sl || (st && !st.l && sl < 5)) + return buf || new u8(0); + // have to estimate size + var noBuf = !buf || st; + // no state + var noSt = !st || st.i; + if (!st) + st = {}; + // Assumes roughly 33% compression ratio average + if (!buf) + buf = new u8(sl * 3); + // ensure buffer can fit at least l elements + var cbuf = function (l) { + var bl = buf.length; + // need to increase size to fit + if (l > bl) { + // Double or set to necessary, whichever is greater + var nbuf = new u8(Math.max(bl * 2, l)); + nbuf.set(buf); + buf = nbuf; + } + }; + // last chunk bitpos bytes + var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; + // total bits + var tbts = sl * 8; + do { + if (!lm) { + // BFINAL - this is only 1 when last chunk is next + st.f = final = bits(dat, pos, 1); + // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman + var type = bits(dat, pos + 1, 3); + pos += 3; + if (!type) { + // go to end of byte boundary + var s = shft(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l; + if (t > sl) { + if (noSt) + throw 'unexpected EOF'; + break; + } + // ensure size + if (noBuf) + cbuf(bt + l); + // Copy over uncompressed data + buf.set(dat.subarray(s, t), bt); + // Get new bitpos, update byte count + st.b = bt += l, st.p = pos = t * 8; + continue; + } + else if (type == 1) + lm = flrm, dm = fdrm, lbt = 9, dbt = 5; + else if (type == 2) { + // literal lengths + var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; + var tl = hLit + bits(dat, pos + 5, 31) + 1; + pos += 14; + // length+distance tree + var ldt = new u8(tl); + // code length tree + var clt = new u8(19); + for (var i = 0; i < hcLen; ++i) { + // use index map to get real code + clt[clim[i]] = bits(dat, pos + i * 3, 7); + } + pos += hcLen * 3; + // code lengths bits + var clb = max(clt), clbmsk = (1 << clb) - 1; + // code lengths map + var clm = hMap(clt, clb, 1); + for (var i = 0; i < tl;) { + var r = clm[bits(dat, pos, clbmsk)]; + // bits read + pos += r & 15; + // symbol + var s = r >>> 4; + // code length to copy + if (s < 16) { + ldt[i++] = s; + } + else { + // copy count + var c = 0, n = 0; + if (s == 16) + n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; + else if (s == 17) + n = 3 + bits(dat, pos, 7), pos += 3; + else if (s == 18) + n = 11 + bits(dat, pos, 127), pos += 7; + while (n--) + ldt[i++] = c; + } + } + // length tree distance tree + var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); + // max length bits + lbt = max(lt); + // max dist bits + dbt = max(dt); + lm = hMap(lt, lbt, 1); + dm = hMap(dt, dbt, 1); + } + else + throw 'invalid block type'; + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + } + // Make sure the buffer can hold this + the largest possible addition + // Maximum chunk size (practically, theoretically infinite) is 2^17; + if (noBuf) + cbuf(bt + 131072); + var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; + var lpos = pos; + for (;; lpos = pos) { + // bits read, code + var c = lm[bits16(dat, pos) & lms], sym = c >>> 4; + pos += c & 15; + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + if (!c) + throw 'invalid length/literal'; + if (sym < 256) + buf[bt++] = sym; + else if (sym == 256) { + lpos = pos, lm = null; + break; + } + else { + var add = sym - 254; + // no extra bits needed if less + if (sym > 264) { + // index + var i = sym - 257, b = fleb[i]; + add = bits(dat, pos, (1 << b) - 1) + fl[i]; + pos += b; + } + // dist + var d = dm[bits16(dat, pos) & dms], dsym = d >>> 4; + if (!d) + throw 'invalid distance'; + pos += d & 15; + var dt = fd[dsym]; + if (dsym > 3) { + var b = fdeb[dsym]; + dt += bits16(dat, pos) & ((1 << b) - 1), pos += b; + } + if (pos > tbts) { + if (noSt) + throw 'unexpected EOF'; + break; + } + if (noBuf) + cbuf(bt + 131072); + var end = bt + add; + for (; bt < end; bt += 4) { + buf[bt] = buf[bt - dt]; + buf[bt + 1] = buf[bt + 1 - dt]; + buf[bt + 2] = buf[bt + 2 - dt]; + buf[bt + 3] = buf[bt + 3 - dt]; + } + bt = end; + } + } + st.l = lm, st.p = lpos, st.b = bt; + if (lm) + final = 1, st.m = lbt, st.d = dm, st.n = dbt; + } while (!final); + return bt == buf.length ? buf : slc(buf, 0, bt); +}; +// starting at p, write the minimum number of bits that can hold v to d +var wbits = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; +}; +// starting at p, write the minimum number of bits (>8) that can hold v to d +var wbits16 = function (d, p, v) { + v <<= p & 7; + var o = (p / 8) | 0; + d[o] |= v; + d[o + 1] |= v >>> 8; + d[o + 2] |= v >>> 16; +}; +// creates code lengths from a frequency table +var hTree = function (d, mb) { + // Need extra info to make a tree + var t = []; + for (var i = 0; i < d.length; ++i) { + if (d[i]) + t.push({ s: i, f: d[i] }); + } + var s = t.length; + var t2 = t.slice(); + if (!s) + return [et, 0]; + if (s == 1) { + var v = new u8(t[0].s + 1); + v[t[0].s] = 1; + return [v, 1]; + } + t.sort(function (a, b) { return a.f - b.f; }); + // after i2 reaches last ind, will be stopped + // freq must be greater than largest possible number of symbols + t.push({ s: -1, f: 25001 }); + var l = t[0], r = t[1], i0 = 0, i1 = 1, i2 = 2; + t[0] = { s: -1, f: l.f + r.f, l: l, r: r }; + // efficient algorithm from UZIP.js + // i0 is lookbehind, i2 is lookahead - after processing two low-freq + // symbols that combined have high freq, will start processing i2 (high-freq, + // non-composite) symbols instead + // see https://reddit.com/r/photopea/comments/ikekht/uzipjs_questions/ + while (i1 != s - 1) { + l = t[t[i0].f < t[i2].f ? i0++ : i2++]; + r = t[i0 != i1 && t[i0].f < t[i2].f ? i0++ : i2++]; + t[i1++] = { s: -1, f: l.f + r.f, l: l, r: r }; + } + var maxSym = t2[0].s; + for (var i = 1; i < s; ++i) { + if (t2[i].s > maxSym) + maxSym = t2[i].s; + } + // code lengths + var tr = new u16(maxSym + 1); + // max bits in tree + var mbt = ln(t[i1 - 1], tr, 0); + if (mbt > mb) { + // more algorithms from UZIP.js + // TODO: find out how this code works (debt) + // ind debt + var i = 0, dt = 0; + // left cost + var lft = mbt - mb, cst = 1 << lft; + t2.sort(function (a, b) { return tr[b.s] - tr[a.s] || a.f - b.f; }); + for (; i < s; ++i) { + var i2_1 = t2[i].s; + if (tr[i2_1] > mb) { + dt += cst - (1 << (mbt - tr[i2_1])); + tr[i2_1] = mb; + } + else + break; + } + dt >>>= lft; + while (dt > 0) { + var i2_2 = t2[i].s; + if (tr[i2_2] < mb) + dt -= 1 << (mb - tr[i2_2]++ - 1); + else + ++i; + } + for (; i >= 0 && dt; --i) { + var i2_3 = t2[i].s; + if (tr[i2_3] == mb) { + --tr[i2_3]; + ++dt; + } + } + mbt = mb; + } + return [new u8(tr), mbt]; +}; +// get the max length and assign length codes +var ln = function (n, l, d) { + return n.s == -1 + ? Math.max(ln(n.l, l, d + 1), ln(n.r, l, d + 1)) + : (l[n.s] = d); +}; +// length codes generation +var lc = function (c) { + var s = c.length; + // Note that the semicolon was intentional + while (s && !c[--s]) + ; + var cl = new u16(++s); + // ind num streak + var cli = 0, cln = c[0], cls = 1; + var w = function (v) { cl[cli++] = v; }; + for (var i = 1; i <= s; ++i) { + if (c[i] == cln && i != s) + ++cls; + else { + if (!cln && cls > 2) { + for (; cls > 138; cls -= 138) + w(32754); + if (cls > 2) { + w(cls > 10 ? ((cls - 11) << 5) | 28690 : ((cls - 3) << 5) | 12305); + cls = 0; + } + } + else if (cls > 3) { + w(cln), --cls; + for (; cls > 6; cls -= 6) + w(8304); + if (cls > 2) + w(((cls - 3) << 5) | 8208), cls = 0; + } + while (cls--) + w(cln); + cls = 1; + cln = c[i]; + } + } + return [cl.subarray(0, cli), s]; +}; +// calculate the length of output from tree, code lengths +var clen = function (cf, cl) { + var l = 0; + for (var i = 0; i < cl.length; ++i) + l += cf[i] * cl[i]; + return l; +}; +// writes a fixed block +// returns the new bit pos +var wfblk = function (out, pos, dat) { + // no need to write 00 as type: TypedArray defaults to 0 + var s = dat.length; + var o = shft(pos + 2); + out[o] = s & 255; + out[o + 1] = s >>> 8; + out[o + 2] = out[o] ^ 255; + out[o + 3] = out[o + 1] ^ 255; + for (var i = 0; i < s; ++i) + out[o + i + 4] = dat[i]; + return (o + 4 + s) * 8; +}; +// writes a block +var wblk = function (dat, out, final, syms, lf, df, eb, li, bs, bl, p) { + wbits(out, p++, final); + ++lf[256]; + var _a = hTree(lf, 15), dlt = _a[0], mlb = _a[1]; + var _b = hTree(df, 15), ddt = _b[0], mdb = _b[1]; + var _c = lc(dlt), lclt = _c[0], nlc = _c[1]; + var _d = lc(ddt), lcdt = _d[0], ndc = _d[1]; + var lcfreq = new u16(19); + for (var i = 0; i < lclt.length; ++i) + lcfreq[lclt[i] & 31]++; + for (var i = 0; i < lcdt.length; ++i) + lcfreq[lcdt[i] & 31]++; + var _e = hTree(lcfreq, 7), lct = _e[0], mlcb = _e[1]; + var nlcc = 19; + for (; nlcc > 4 && !lct[clim[nlcc - 1]]; --nlcc) + ; + var flen = (bl + 5) << 3; + var ftlen = clen(lf, flt) + clen(df, fdt) + eb; + var dtlen = clen(lf, dlt) + clen(df, ddt) + eb + 14 + 3 * nlcc + clen(lcfreq, lct) + (2 * lcfreq[16] + 3 * lcfreq[17] + 7 * lcfreq[18]); + if (flen <= ftlen && flen <= dtlen) + return wfblk(out, p, dat.subarray(bs, bs + bl)); + var lm, ll, dm, dl; + wbits(out, p, 1 + (dtlen < ftlen)), p += 2; + if (dtlen < ftlen) { + lm = hMap(dlt, mlb, 0), ll = dlt, dm = hMap(ddt, mdb, 0), dl = ddt; + var llm = hMap(lct, mlcb, 0); + wbits(out, p, nlc - 257); + wbits(out, p + 5, ndc - 1); + wbits(out, p + 10, nlcc - 4); + p += 14; + for (var i = 0; i < nlcc; ++i) + wbits(out, p + 3 * i, lct[clim[i]]); + p += 3 * nlcc; + var lcts = [lclt, lcdt]; + for (var it = 0; it < 2; ++it) { + var clct = lcts[it]; + for (var i = 0; i < clct.length; ++i) { + var len = clct[i] & 31; + wbits(out, p, llm[len]), p += lct[len]; + if (len > 15) + wbits(out, p, (clct[i] >>> 5) & 127), p += clct[i] >>> 12; + } + } + } + else { + lm = flm, ll = flt, dm = fdm, dl = fdt; + } + for (var i = 0; i < li; ++i) { + if (syms[i] > 255) { + var len = (syms[i] >>> 18) & 31; + wbits16(out, p, lm[len + 257]), p += ll[len + 257]; + if (len > 7) + wbits(out, p, (syms[i] >>> 23) & 31), p += fleb[len]; + var dst = syms[i] & 31; + wbits16(out, p, dm[dst]), p += dl[dst]; + if (dst > 3) + wbits16(out, p, (syms[i] >>> 5) & 8191), p += fdeb[dst]; + } + else { + wbits16(out, p, lm[syms[i]]), p += ll[syms[i]]; + } + } + wbits16(out, p, lm[256]); + return p + ll[256]; +}; +// deflate options (nice << 13) | chain +var deo = /*#__PURE__*/ new u32([65540, 131080, 131088, 131104, 262176, 1048704, 1048832, 2114560, 2117632]); +// empty +var et = /*#__PURE__*/ new u8(0); +// compresses data into a raw DEFLATE buffer +var dflt = function (dat, lvl, plvl, pre, post, lst) { + var s = dat.length; + var o = new u8(pre + s + 5 * (1 + Math.ceil(s / 7000)) + post); + // writing to this writes to the output buffer + var w = o.subarray(pre, o.length - post); + var pos = 0; + if (!lvl || s < 8) { + for (var i = 0; i <= s; i += 65535) { + // end + var e = i + 65535; + if (e < s) { + // write full block + pos = wfblk(w, pos, dat.subarray(i, e)); + } + else { + // write final block + w[i] = lst; + pos = wfblk(w, pos, dat.subarray(i, s)); + } + } + } + else { + var opt = deo[lvl - 1]; + var n = opt >>> 13, c = opt & 8191; + var msk_1 = (1 << plvl) - 1; + // prev 2-byte val map curr 2-byte val map + var prev = new u16(32768), head = new u16(msk_1 + 1); + var bs1_1 = Math.ceil(plvl / 3), bs2_1 = 2 * bs1_1; + var hsh = function (i) { return (dat[i] ^ (dat[i + 1] << bs1_1) ^ (dat[i + 2] << bs2_1)) & msk_1; }; + // 24576 is an arbitrary number of maximum symbols per block + // 424 buffer for last block + var syms = new u32(25000); + // length/literal freq distance freq + var lf = new u16(288), df = new u16(32); + // l/lcnt exbits index l/lind waitdx bitpos + var lc_1 = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0; + for (; i < s; ++i) { + // hash value + // deopt when i > s - 3 - at end, deopt acceptable + var hv = hsh(i); + // index mod 32768 previous index mod + var imod = i & 32767, pimod = head[hv]; + prev[imod] = pimod; + head[hv] = imod; + // We always should modify head and prev, but only add symbols if + // this data is not yet processed ("wait" for wait index) + if (wi <= i) { + // bytes remaining + var rem = s - i; + if ((lc_1 > 7000 || li > 24576) && rem > 423) { + pos = wblk(dat, w, 0, syms, lf, df, eb, li, bs, i - bs, pos); + li = lc_1 = eb = 0, bs = i; + for (var j = 0; j < 286; ++j) + lf[j] = 0; + for (var j = 0; j < 30; ++j) + df[j] = 0; + } + // len dist chain + var l = 2, d = 0, ch_1 = c, dif = (imod - pimod) & 32767; + if (rem > 2 && hv == hsh(i - dif)) { + var maxn = Math.min(n, rem) - 1; + var maxd = Math.min(32767, i); + // max possible length + // not capped at dif because decompressors implement "rolling" index population + var ml = Math.min(258, rem); + while (dif <= maxd && --ch_1 && imod != pimod) { + if (dat[i + l] == dat[i + l - dif]) { + var nl = 0; + for (; nl < ml && dat[i + nl] == dat[i + nl - dif]; ++nl) + ; + if (nl > l) { + l = nl, d = dif; + // break out early when we reach "nice" (we are satisfied enough) + if (nl > maxn) + break; + // now, find the rarest 2-byte sequence within this + // length of literals and search for that instead. + // Much faster than just using the start + var mmd = Math.min(dif, nl - 2); + var md = 0; + for (var j = 0; j < mmd; ++j) { + var ti = (i - dif + j + 32768) & 32767; + var pti = prev[ti]; + var cd = (ti - pti + 32768) & 32767; + if (cd > md) + md = cd, pimod = ti; + } + } + } + // check the previous match + imod = pimod, pimod = prev[imod]; + dif += (imod - pimod + 32768) & 32767; + } + } + // d will be nonzero only when a match was found + if (d) { + // store both dist and len data in one Uint32 + // Make sure this is recognized as a len/dist with 28th bit (2^28) + syms[li++] = 268435456 | (revfl[l] << 18) | revfd[d]; + var lin = revfl[l] & 31, din = revfd[d] & 31; + eb += fleb[lin] + fdeb[din]; + ++lf[257 + lin]; + ++df[din]; + wi = i + l; + ++lc_1; + } + else { + syms[li++] = dat[i]; + ++lf[dat[i]]; + } + } + } + pos = wblk(dat, w, lst, syms, lf, df, eb, li, bs, i - bs, pos); + // this is the easiest way to avoid needing to maintain state + if (!lst && pos & 7) + pos = wfblk(w, pos + 1, et); + } + return slc(o, 0, pre + shft(pos) + post); +}; +// CRC32 table +var crct = /*#__PURE__*/ (function () { + var t = new u32(256); + for (var i = 0; i < 256; ++i) { + var c = i, k = 9; + while (--k) + c = ((c & 1) && 0xEDB88320) ^ (c >>> 1); + t[i] = c; + } + return t; +})(); +// CRC32 +var crc = function () { + var c = -1; + return { + p: function (d) { + // closures have awful performance + var cr = c; + for (var i = 0; i < d.length; ++i) + cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8); + c = cr; + }, + d: function () { return ~c; } + }; +}; +// Alder32 +var adler = function () { + var a = 1, b = 0; + return { + p: function (d) { + // closures have awful performance + var n = a, m = b; + var l = d.length; + for (var i = 0; i != l;) { + var e = Math.min(i + 2655, l); + for (; i < e; ++i) + m += n += d[i]; + n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16); + } + a = n, b = m; + }, + d: function () { + a %= 65521, b %= 65521; + return (a & 255) << 24 | (a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8); + } + }; +}; +; +// deflate with opts +var dopt = function (dat, opt, pre, post, st) { + return dflt(dat, opt.level == null ? 6 : opt.level, opt.mem == null ? Math.ceil(Math.max(8, Math.min(13, Math.log(dat.length))) * 1.5) : (12 + opt.mem), pre, post, !st); +}; +// Walmart object spread +var mrg = function (a, b) { + var o = {}; + for (var k in a) + o[k] = a[k]; + for (var k in b) + o[k] = b[k]; + return o; +}; +// worker clone +// This is possibly the craziest part of the entire codebase, despite how simple it may seem. +// The only parameter to this function is a closure that returns an array of variables outside of the function scope. +// We're going to try to figure out the variable names used in the closure as strings because that is crucial for workerization. +// We will return an object mapping of true variable name to value (basically, the current scope as a JS object). +// The reason we can't just use the original variable names is minifiers mangling the toplevel scope. +// This took me three weeks to figure out how to do. +var wcln = function (fn, fnStr, td) { + var dt = fn(); + var st = fn.toString(); + var ks = st.slice(st.indexOf('[') + 1, st.lastIndexOf(']')).replace(/ /g, '').split(','); + for (var i = 0; i < dt.length; ++i) { + var v = dt[i], k = ks[i]; + if (typeof v == 'function') { + fnStr += ';' + k + '='; + var st_1 = v.toString(); + if (v.prototype) { + // for global objects + if (st_1.indexOf('[native code]') != -1) { + var spInd = st_1.indexOf(' ', 8) + 1; + fnStr += st_1.slice(spInd, st_1.indexOf('(', spInd)); + } + else { + fnStr += st_1; + for (var t in v.prototype) + fnStr += ';' + k + '.prototype.' + t + '=' + v.prototype[t].toString(); + } + } + else + fnStr += st_1; + } + else + td[k] = v; + } + return [fnStr, td]; +}; +var ch = []; +// clone bufs +var cbfs = function (v) { + var tl = []; + for (var k in v) { + if (v[k] instanceof u8 || v[k] instanceof u16 || v[k] instanceof u32) + tl.push((v[k] = new v[k].constructor(v[k])).buffer); + } + return tl; +}; +// use a worker to execute code +var wrkr = function (fns, init, id, cb) { + var _a; + if (!ch[id]) { + var fnStr = '', td_1 = {}, m = fns.length - 1; + for (var i = 0; i < m; ++i) + _a = wcln(fns[i], fnStr, td_1), fnStr = _a[0], td_1 = _a[1]; + ch[id] = wcln(fns[m], fnStr, td_1); + } + var td = mrg({}, ch[id][1]); + return wk(ch[id][0] + ';onmessage=function(e){for(var k in e.data)self[k]=e.data[k];onmessage=' + init.toString() + '}', id, td, cbfs(td), cb); +}; +// base async inflate fn +var bInflt = function () { return [u8, u16, u32, fleb, fdeb, clim, fl, fd, flrm, fdrm, rev, hMap, max, bits, bits16, shft, slc, inflt, inflateSync, pbf, gu8]; }; +var bDflt = function () { return [u8, u16, u32, fleb, fdeb, clim, revfl, revfd, flm, flt, fdm, fdt, rev, deo, et, hMap, wbits, wbits16, hTree, ln, lc, clen, wfblk, wblk, shft, slc, dflt, dopt, deflateSync, pbf]; }; +// gzip extra +var gze = function () { return [gzh, gzhl, wbytes, crc, crct]; }; +// gunzip extra +var guze = function () { return [gzs, gzl]; }; +// zlib extra +var zle = function () { return [zlh, wbytes, adler]; }; +// unzlib extra +var zule = function () { return [zlv]; }; +// post buf +var pbf = function (msg) { return postMessage(msg, [msg.buffer]); }; +// get u8 +var gu8 = function (o) { return o && o.size && new u8(o.size); }; +// async helper +var cbify = function (dat, opts, fns, init, id, cb) { + var w = wrkr(fns, init, id, function (err, dat) { + w.terminate(); + cb(err, dat); + }); + w.postMessage([dat, opts], opts.consume ? [dat.buffer] : []); + return function () { w.terminate(); }; +}; +// auto stream +var astrm = function (strm) { + strm.ondata = function (dat, final) { return postMessage([dat, final], [dat.buffer]); }; + return function (ev) { return strm.push(ev.data[0], ev.data[1]); }; +}; +// async stream attach +var astrmify = function (fns, strm, opts, init, id) { + var t; + var w = wrkr(fns, init, id, function (err, dat) { + if (err) + w.terminate(), strm.ondata.call(strm, err); + else { + if (dat[1]) + w.terminate(); + strm.ondata.call(strm, err, dat[0], dat[1]); + } + }); + w.postMessage(opts); + strm.push = function (d, f) { + if (t) + throw 'stream finished'; + if (!strm.ondata) + throw 'no stream handler'; + w.postMessage([d, t = f], [d.buffer]); + }; + strm.terminate = function () { w.terminate(); }; +}; +// read 2 bytes +var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); }; +// read 4 bytes +var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; }; +var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); }; +// write bytes +var wbytes = function (d, b, v) { + for (; v; ++b) + d[b] = v, v >>>= 8; +}; +// gzip header +var gzh = function (c, o) { + var fn = o.filename; + c[0] = 31, c[1] = 139, c[2] = 8, c[8] = o.level < 2 ? 4 : o.level == 9 ? 2 : 0, c[9] = 3; // assume Unix + if (o.mtime != 0) + wbytes(c, 4, Math.floor(new Date(o.mtime || Date.now()) / 1000)); + if (fn) { + c[3] = 8; + for (var i = 0; i <= fn.length; ++i) + c[i + 10] = fn.charCodeAt(i); + } +}; +// gzip footer: -8 to -4 = CRC, -4 to -0 is length +// gzip start +var gzs = function (d) { + if (d[0] != 31 || d[1] != 139 || d[2] != 8) + throw 'invalid gzip data'; + var flg = d[3]; + var st = 10; + if (flg & 4) + st += d[10] | (d[11] << 8) + 2; + for (var zs = (flg >> 3 & 1) + (flg >> 4 & 1); zs > 0; zs -= !d[st++]) + ; + return st + (flg & 2); +}; +// gzip length +var gzl = function (d) { + var l = d.length; + return ((d[l - 4] | d[l - 3] << 8 | d[l - 2] << 16) | (d[l - 1] << 24)) >>> 0; +}; +// gzip header length +var gzhl = function (o) { return 10 + ((o.filename && (o.filename.length + 1)) || 0); }; +// zlib header +var zlh = function (c, o) { + var lv = o.level, fl = lv == 0 ? 0 : lv < 6 ? 1 : lv == 9 ? 3 : 2; + c[0] = 120, c[1] = (fl << 6) | (fl ? (32 - 2 * fl) : 1); +}; +// zlib valid +var zlv = function (d) { + if ((d[0] & 15) != 8 || (d[0] >>> 4) > 7 || ((d[0] << 8 | d[1]) % 31)) + throw 'invalid zlib data'; + if (d[1] & 32) + throw 'invalid zlib data: preset dictionaries not supported'; +}; +function AsyncCmpStrm(opts, cb) { + if (!cb && typeof opts == 'function') + cb = opts, opts = {}; + this.ondata = cb; + return opts; +} +// zlib footer: -4 to -0 is Adler32 +/** + * Streaming DEFLATE compression + */ +var Deflate = /*#__PURE__*/ (function () { + function Deflate(opts, cb) { + if (!cb && typeof opts == 'function') + cb = opts, opts = {}; + this.ondata = cb; + this.o = opts || {}; + } + Deflate.prototype.p = function (c, f) { + this.ondata(dopt(c, this.o, 0, 0, !f), f); + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Deflate.prototype.push = function (chunk, final) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + this.d = final; + this.p(chunk, final || false); + }; + return Deflate; +}()); +export { Deflate }; +/** + * Asynchronous streaming DEFLATE compression + */ +var AsyncDeflate = /*#__PURE__*/ (function () { + function AsyncDeflate(opts, cb) { + astrmify([ + bDflt, + function () { return [astrm, Deflate]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Deflate(ev.data); + onmessage = astrm(strm); + }, 6); + } + return AsyncDeflate; +}()); +export { AsyncDeflate }; +export function deflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + ], function (ev) { return pbf(deflateSync(ev.data[0], ev.data[1])); }, 0, cb); +} +/** + * Compresses data with DEFLATE without any wrapper + * @param data The data to compress + * @param opts The compression options + * @returns The deflated version of the data + */ +export function deflateSync(data, opts) { + return dopt(data, opts || {}, 0, 0); +} +/** + * Streaming DEFLATE decompression + */ +var Inflate = /*#__PURE__*/ (function () { + /** + * Creates an inflation stream + * @param cb The callback to call whenever data is inflated + */ + function Inflate(cb) { + this.s = {}; + this.p = new u8(0); + this.ondata = cb; + } + Inflate.prototype.e = function (c) { + if (this.d) + throw 'stream finished'; + if (!this.ondata) + throw 'no stream handler'; + var l = this.p.length; + var n = new u8(l + c.length); + n.set(this.p), n.set(c, l), this.p = n; + }; + Inflate.prototype.c = function (final) { + this.d = this.s.i = final || false; + var bts = this.s.b; + var dt = inflt(this.p, this.o, this.s); + this.ondata(slc(dt, bts, this.s.b), this.d); + this.o = slc(dt, this.s.b - 32768), this.s.b = this.o.length; + this.p = slc(this.p, (this.s.p / 8) | 0), this.s.p &= 7; + }; + /** + * Pushes a chunk to be inflated + * @param chunk The chunk to push + * @param final Whether this is the final chunk + */ + Inflate.prototype.push = function (chunk, final) { + this.e(chunk), this.c(final); + }; + return Inflate; +}()); +export { Inflate }; +/** + * Asynchronous streaming DEFLATE decompression + */ +var AsyncInflate = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous inflation stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncInflate(cb) { + this.ondata = cb; + astrmify([ + bInflt, + function () { return [astrm, Inflate]; } + ], this, 0, function () { + var strm = new Inflate(); + onmessage = astrm(strm); + }, 7); + } + return AsyncInflate; +}()); +export { AsyncInflate }; +export function inflate(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt + ], function (ev) { return pbf(inflateSync(ev.data[0], gu8(ev.data[1]))); }, 1, cb); +} +/** + * Expands DEFLATE data with no wrapper + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +export function inflateSync(data, out) { + return inflt(data, out); +} +// before you yell at me for not just using extends, my reason is that TS inheritance is hard to workerize. +/** + * Streaming GZIP compression + */ +var Gzip = /*#__PURE__*/ (function () { + function Gzip(opts, cb) { + this.c = crc(); + this.l = 0; + this.v = 1; + Deflate.call(this, opts, cb); + } + /** + * Pushes a chunk to be GZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Gzip.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Gzip.prototype.p = function (c, f) { + this.c.p(c); + this.l += c.length; + var raw = dopt(c, this.o, this.v && gzhl(this.o), f && 8, !f); + if (this.v) + gzh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 8, this.c.d()), wbytes(raw, raw.length - 4, this.l); + this.ondata(raw, f); + }; + return Gzip; +}()); +export { Gzip }; +/** + * Asynchronous streaming GZIP compression + */ +var AsyncGzip = /*#__PURE__*/ (function () { + function AsyncGzip(opts, cb) { + astrmify([ + bDflt, + gze, + function () { return [astrm, Deflate, Gzip]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Gzip(ev.data); + onmessage = astrm(strm); + }, 8); + } + return AsyncGzip; +}()); +export { AsyncGzip }; +export function gzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + gze, + function () { return [gzipSync]; } + ], function (ev) { return pbf(gzipSync(ev.data[0], ev.data[1])); }, 2, cb); +} +/** + * Compresses data with GZIP + * @param data The data to compress + * @param opts The compression options + * @returns The gzipped version of the data + */ +export function gzipSync(data, opts) { + if (!opts) + opts = {}; + var c = crc(), l = data.length; + c.p(data); + var d = dopt(data, opts, gzhl(opts), 8), s = d.length; + return gzh(d, opts), wbytes(d, s - 8, c.d()), wbytes(d, s - 4, l), d; +} +/** + * Streaming GZIP decompression + */ +var Gunzip = /*#__PURE__*/ (function () { + /** + * Creates a GUNZIP stream + * @param cb The callback to call whenever data is inflated + */ + function Gunzip(cb) { + this.v = 1; + Inflate.call(this, cb); + } + /** + * Pushes a chunk to be GUNZIPped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Gunzip.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + var s = this.p.length > 3 ? gzs(this.p) : 4; + if (s >= this.p.length && !final) + return; + this.p = this.p.subarray(s), this.v = 0; + } + if (final) { + if (this.p.length < 8) + throw 'invalid gzip stream'; + this.p = this.p.subarray(0, -8); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Gunzip; +}()); +export { Gunzip }; +/** + * Asynchronous streaming GZIP decompression + */ +var AsyncGunzip = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous GUNZIP stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncGunzip(cb) { + this.ondata = cb; + astrmify([ + bInflt, + guze, + function () { return [astrm, Inflate, Gunzip]; } + ], this, 0, function () { + var strm = new Gunzip(); + onmessage = astrm(strm); + }, 9); + } + return AsyncGunzip; +}()); +export { AsyncGunzip }; +export function gunzip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt, + guze, + function () { return [gunzipSync]; } + ], function (ev) { return pbf(gunzipSync(ev.data[0])); }, 3, cb); +} +/** + * Expands GZIP data + * @param data The data to decompress + * @param out Where to write the data. GZIP already encodes the output size, so providing this doesn't save memory. + * @returns The decompressed version of the data + */ +export function gunzipSync(data, out) { + return inflt(data.subarray(gzs(data), -8), out || new u8(gzl(data))); +} +/** + * Streaming Zlib compression + */ +var Zlib = /*#__PURE__*/ (function () { + function Zlib(opts, cb) { + this.c = adler(); + this.v = 1; + Deflate.call(this, opts, cb); + } + /** + * Pushes a chunk to be zlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Zlib.prototype.push = function (chunk, final) { + Deflate.prototype.push.call(this, chunk, final); + }; + Zlib.prototype.p = function (c, f) { + this.c.p(c); + var raw = dopt(c, this.o, this.v && 2, f && 4, !f); + if (this.v) + zlh(raw, this.o), this.v = 0; + if (f) + wbytes(raw, raw.length - 4, this.c.d()); + this.ondata(raw, f); + }; + return Zlib; +}()); +export { Zlib }; +/** + * Asynchronous streaming Zlib compression + */ +var AsyncZlib = /*#__PURE__*/ (function () { + function AsyncZlib(opts, cb) { + astrmify([ + bDflt, + zle, + function () { return [astrm, Deflate, Zlib]; } + ], this, AsyncCmpStrm.call(this, opts, cb), function (ev) { + var strm = new Zlib(ev.data); + onmessage = astrm(strm); + }, 10); + } + return AsyncZlib; +}()); +export { AsyncZlib }; +export function zlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bDflt, + zle, + function () { return [zlibSync]; } + ], function (ev) { return pbf(zlibSync(ev.data[0], ev.data[1])); }, 4, cb); +} +/** + * Compress data with Zlib + * @param data The data to compress + * @param opts The compression options + * @returns The zlib-compressed version of the data + */ +export function zlibSync(data, opts) { + if (!opts) + opts = {}; + var a = adler(); + a.p(data); + var d = dopt(data, opts, 2, 4); + return zlh(d, opts), wbytes(d, d.length - 4, a.d()), d; +} +/** + * Streaming Zlib decompression + */ +var Unzlib = /*#__PURE__*/ (function () { + /** + * Creates a Zlib decompression stream + * @param cb The callback to call whenever data is inflated + */ + function Unzlib(cb) { + this.v = 1; + Inflate.call(this, cb); + } + /** + * Pushes a chunk to be unzlibbed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Unzlib.prototype.push = function (chunk, final) { + Inflate.prototype.e.call(this, chunk); + if (this.v) { + if (this.p.length < 2 && !final) + return; + this.p = this.p.subarray(2), this.v = 0; + } + if (final) { + if (this.p.length < 4) + throw 'invalid zlib stream'; + this.p = this.p.subarray(0, -4); + } + // necessary to prevent TS from using the closure value + // This allows for workerization to function correctly + Inflate.prototype.c.call(this, final); + }; + return Unzlib; +}()); +export { Unzlib }; +/** + * Asynchronous streaming Zlib decompression + */ +var AsyncUnzlib = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous Zlib decompression stream + * @param cb The callback to call whenever data is deflated + */ + function AsyncUnzlib(cb) { + this.ondata = cb; + astrmify([ + bInflt, + zule, + function () { return [astrm, Inflate, Unzlib]; } + ], this, 0, function () { + var strm = new Unzlib(); + onmessage = astrm(strm); + }, 11); + } + return AsyncUnzlib; +}()); +export { AsyncUnzlib }; +export function unzlib(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return cbify(data, opts, [ + bInflt, + zule, + function () { return [unzlibSync]; } + ], function (ev) { return pbf(unzlibSync(ev.data[0], gu8(ev.data[1]))); }, 5, cb); +} +/** + * Expands Zlib data + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +export function unzlibSync(data, out) { + return inflt((zlv(data), data.subarray(2, -4)), out); +} +// Default algorithm for compression (used because having a known output size allows faster decompression) +export { gzip as compress, AsyncGzip as AsyncCompress }; +// Default algorithm for compression (used because having a known output size allows faster decompression) +export { gzipSync as compressSync, Gzip as Compress }; +/** + * Streaming GZIP, Zlib, or raw DEFLATE decompression + */ +var Decompress = /*#__PURE__*/ (function () { + /** + * Creates a decompression stream + * @param cb The callback to call whenever data is decompressed + */ + function Decompress(cb) { + this.G = Gunzip; + this.I = Inflate; + this.Z = Unzlib; + this.ondata = cb; + } + /** + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Decompress.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no stream handler'; + if (!this.s) { + if (this.p && this.p.length) { + var n = new u8(this.p.length + chunk.length); + n.set(this.p), n.set(chunk, this.p.length); + } + else + this.p = chunk; + if (this.p.length > 2) { + var _this_1 = this; + var cb = function () { _this_1.ondata.apply(_this_1, arguments); }; + this.s = (this.p[0] == 31 && this.p[1] == 139 && this.p[2] == 8) + ? new this.G(cb) + : ((this.p[0] & 15) != 8 || (this.p[0] >> 4) > 7 || ((this.p[0] << 8 | this.p[1]) % 31)) + ? new this.I(cb) + : new this.Z(cb); + this.s.push(this.p, final); + this.p = null; + } + } + else + this.s.push(chunk, final); + }; + return Decompress; +}()); +export { Decompress }; +/** + * Asynchronous streaming GZIP, Zlib, or raw DEFLATE decompression + */ +var AsyncDecompress = /*#__PURE__*/ (function () { + /** + * Creates an asynchronous decompression stream + * @param cb The callback to call whenever data is decompressed + */ + function AsyncDecompress(cb) { + this.G = AsyncGunzip; + this.I = AsyncInflate; + this.Z = AsyncUnzlib; + this.ondata = cb; + } + /** + * Pushes a chunk to be decompressed + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + AsyncDecompress.prototype.push = function (chunk, final) { + Decompress.prototype.push.call(this, chunk, final); + }; + return AsyncDecompress; +}()); +export { AsyncDecompress }; +export function decompress(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzip(data, opts, cb) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflate(data, opts, cb) + : unzlib(data, opts, cb); +} +/** + * Expands compressed GZIP, Zlib, or raw DEFLATE data, automatically detecting the format + * @param data The data to decompress + * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length. + * @returns The decompressed version of the data + */ +export function decompressSync(data, out) { + return (data[0] == 31 && data[1] == 139 && data[2] == 8) + ? gunzipSync(data, out) + : ((data[0] & 15) != 8 || (data[0] >> 4) > 7 || ((data[0] << 8 | data[1]) % 31)) + ? inflateSync(data, out) + : unzlibSync(data, out); +} +// flatten a directory structure +var fltn = function (d, p, t, o) { + for (var k in d) { + var val = d[k], n = p + k; + if (val instanceof u8) + t[n] = [val, o]; + else if (Array.isArray(val)) + t[n] = [val[0], mrg(o, val[1])]; + else + fltn(val, n + '/', t, o); + } +}; +// text encoder +var te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder(); +// text decoder +var td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder(); +// text decoder stream +var tds = 0; +try { + td.decode(et, { stream: true }); + tds = 1; +} +catch (e) { } +// decode UTF8 +var dutf8 = function (d) { + for (var r = '', i = 0;;) { + var c = d[i++]; + var eb = (c > 127) + (c > 223) + (c > 239); + if (i + eb > d.length) + return [r, slc(d, i - 1)]; + if (!eb) + r += String.fromCharCode(c); + else if (eb == 3) { + c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536, + r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023)); + } + else if (eb & 1) + r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63)); + else + r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)); + } +}; +/** + * Streaming UTF-8 decoding + */ +var DecodeUTF8 = /*#__PURE__*/ (function () { + /** + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is decoded + */ + function DecodeUTF8(cb) { + this.ondata = cb; + if (tds) + this.t = new TextDecoder(); + else + this.p = et; + } + /** + * Pushes a chunk to be decoded from UTF-8 binary + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + DecodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + final = !!final; + if (this.t) { + this.ondata(this.t.decode(chunk, { stream: true }), final); + if (final) { + if (this.t.decode().length) + throw 'invalid utf-8 data'; + this.t = null; + } + return; + } + if (!this.p) + throw 'stream finished'; + var dat = new u8(this.p.length + chunk.length); + dat.set(this.p); + dat.set(chunk, this.p.length); + var _a = dutf8(dat), ch = _a[0], np = _a[1]; + if (final) { + if (np.length) + throw 'invalid utf-8 data'; + this.p = null; + } + else + this.p = np; + this.ondata(ch, final); + }; + return DecodeUTF8; +}()); +export { DecodeUTF8 }; +/** + * Streaming UTF-8 encoding + */ +var EncodeUTF8 = /*#__PURE__*/ (function () { + /** + * Creates a UTF-8 decoding stream + * @param cb The callback to call whenever data is encoded + */ + function EncodeUTF8(cb) { + this.ondata = cb; + } + /** + * Pushes a chunk to be encoded to UTF-8 + * @param chunk The string data to push + * @param final Whether this is the last chunk + */ + EncodeUTF8.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback'; + if (this.d) + throw 'stream finished'; + this.ondata(strToU8(chunk), this.d = final || false); + }; + return EncodeUTF8; +}()); +export { EncodeUTF8 }; +/** + * Converts a string into a Uint8Array for use with compression/decompression methods + * @param str The string to encode + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless decoding a binary string. + * @returns The string encoded in UTF-8/Latin-1 binary + */ +export function strToU8(str, latin1) { + if (latin1) { + var ar_1 = new u8(str.length); + for (var i = 0; i < str.length; ++i) + ar_1[i] = str.charCodeAt(i); + return ar_1; + } + if (te) + return te.encode(str); + var l = str.length; + var ar = new u8(str.length + (str.length >> 1)); + var ai = 0; + var w = function (v) { ar[ai++] = v; }; + for (var i = 0; i < l; ++i) { + if (ai + 5 > ar.length) { + var n = new u8(ai + 8 + ((l - i) << 1)); + n.set(ar); + ar = n; + } + var c = str.charCodeAt(i); + if (c < 128 || latin1) + w(c); + else if (c < 2048) + w(192 | (c >> 6)), w(128 | (c & 63)); + else if (c > 55295 && c < 57344) + c = 65536 + (c & 1023 << 10) | (str.charCodeAt(++i) & 1023), + w(240 | (c >> 18)), w(128 | ((c >> 12) & 63)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + else + w(224 | (c >> 12)), w(128 | ((c >> 6) & 63)), w(128 | (c & 63)); + } + return slc(ar, 0, ai); +} +/** + * Converts a Uint8Array to a string + * @param dat The data to decode to string + * @param latin1 Whether or not to interpret the data as Latin-1. This should + * not need to be true unless encoding to binary string. + * @returns The original UTF-8/Latin-1 string + */ +export function strFromU8(dat, latin1) { + if (latin1) { + var r = ''; + for (var i = 0; i < dat.length; i += 16384) + r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); + return r; + } + else if (td) + return td.decode(dat); + else { + var _a = dutf8(dat), out = _a[0], ext = _a[1]; + if (ext.length) + throw 'invalid utf-8 data'; + return out; + } +} +; +// deflate bit flag +var dbf = function (l) { return l == 1 ? 3 : l < 6 ? 2 : l == 9 ? 1 : 0; }; +// skip local zip header +var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; +// read zip header +var zh = function (d, b, z) { + var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20); + var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2]; + return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off]; +}; +// read zip64 extra field +var z64e = function (d, b) { + for (; b2(d, b) != 1; b += 4 + b2(d, b + 2)) + ; + return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)]; +}; +// extra field length +var exfl = function (ex) { + var le = 0; + if (ex) { + for (var k in ex) { + var l = ex[k].length; + if (l > 65535) + throw 'extra field too long'; + le += l + 4; + } + } + return le; +}; +// write zip header +var wzh = function (d, b, f, fn, u, c, ce, co) { + var fl = fn.length, ex = f.extra, col = co && co.length; + var exl = exfl(ex); + wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4; + if (ce != null) + d[b++] = 20, d[b++] = f.os; + d[b] = 20, b += 2; // spec compliance? what's that? + d[b++] = (f.flag << 1) | (c == null && 8), d[b++] = u && 8; + d[b++] = f.compression & 255, d[b++] = f.compression >> 8; + var dt = new Date(f.mtime == null ? Date.now() : f.mtime), y = dt.getFullYear() - 1980; + if (y < 0 || y > 119) + throw 'date not in range 1980-2099'; + wbytes(d, b, (y << 25) | ((dt.getMonth() + 1) << 21) | (dt.getDate() << 16) | (dt.getHours() << 11) | (dt.getMinutes() << 5) | (dt.getSeconds() >>> 1)), b += 4; + if (c != null) { + wbytes(d, b, f.crc); + wbytes(d, b + 4, c); + wbytes(d, b + 8, f.size); + } + wbytes(d, b + 12, fl); + wbytes(d, b + 14, exl), b += 16; + if (ce != null) { + wbytes(d, b, col); + wbytes(d, b + 6, f.attrs); + wbytes(d, b + 10, ce), b += 14; + } + d.set(fn, b); + b += fl; + if (exl) { + for (var k in ex) { + var exf = ex[k], l = exf.length; + wbytes(d, b, +k); + wbytes(d, b + 2, l); + d.set(exf, b + 4), b += 4 + l; + } + } + if (col) + d.set(co, b), b += col; + return b; +}; +// write zip footer (end of central directory) +var wzf = function (o, b, c, d, e) { + wbytes(o, b, 0x6054B50); // skip disk + wbytes(o, b + 8, c); + wbytes(o, b + 10, c); + wbytes(o, b + 12, d); + wbytes(o, b + 16, e); +}; +/** + * A pass-through stream to keep data uncompressed in a ZIP archive. + */ +var ZipPassThrough = /*#__PURE__*/ (function () { + /** + * Creates a pass-through stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + */ + function ZipPassThrough(filename) { + this.filename = filename; + this.c = crc(); + this.size = 0; + this.compression = 0; + } + /** + * Processes a chunk and pushes to the output stream. You can override this + * method in a subclass for custom behavior, but by default this passes + * the data through. You must call this.ondata(err, chunk, final) at some + * point in this method. + * @param chunk The chunk to process + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.process = function (chunk, final) { + this.ondata(null, chunk, final); + }; + /** + * Pushes a chunk to be added. If you are subclassing this with a custom + * compression algorithm, note that you must push data from the source + * file only, pre-compression. + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + ZipPassThrough.prototype.push = function (chunk, final) { + if (!this.ondata) + throw 'no callback - add to ZIP archive before pushing'; + this.c.p(chunk); + this.size += chunk.length; + if (final) + this.crc = this.c.d(); + this.process(chunk, final || false); + }; + return ZipPassThrough; +}()); +export { ZipPassThrough }; +// I don't extend because TypeScript extension adds 1kB of runtime bloat +/** + * Streaming DEFLATE compression for ZIP archives. Prefer using AsyncZipDeflate + * for better performance + */ +var ZipDeflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options + */ + function ZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new Deflate(opts, function (dat, final) { + _this_1.ondata(null, dat, final); + }); + this.compression = 8; + this.flag = dbf(opts.level); + } + ZipDeflate.prototype.process = function (chunk, final) { + try { + this.d.push(chunk, final); + } + catch (e) { + this.ondata(e, null, final); + } + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + ZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return ZipDeflate; +}()); +export { ZipDeflate }; +/** + * Asynchronous streaming DEFLATE compression for ZIP archives + */ +var AsyncZipDeflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE stream that can be added to ZIP archives + * @param filename The filename to associate with this data stream + * @param opts The compression options + */ + function AsyncZipDeflate(filename, opts) { + var _this_1 = this; + if (!opts) + opts = {}; + ZipPassThrough.call(this, filename); + this.d = new AsyncDeflate(opts, function (err, dat, final) { + _this_1.ondata(err, dat, final); + }); + this.compression = 8; + this.flag = dbf(opts.level); + this.terminate = this.d.terminate; + } + AsyncZipDeflate.prototype.process = function (chunk, final) { + this.d.push(chunk, final); + }; + /** + * Pushes a chunk to be deflated + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + AsyncZipDeflate.prototype.push = function (chunk, final) { + ZipPassThrough.prototype.push.call(this, chunk, final); + }; + return AsyncZipDeflate; +}()); +export { AsyncZipDeflate }; +// TODO: Better tree shaking +/** + * A zippable archive to which files can incrementally be added + */ +var Zip = /*#__PURE__*/ (function () { + /** + * Creates an empty ZIP archive to which files can be added + * @param cb The callback to call whenever data for the generated ZIP archive + * is available + */ + function Zip(cb) { + this.ondata = cb; + this.u = []; + this.d = 1; + } + /** + * Adds a file to the ZIP archive + * @param file The file stream to add + */ + Zip.prototype.add = function (file) { + var _this_1 = this; + if (this.d & 2) + throw 'stream finished'; + var f = strToU8(file.filename), fl = f.length; + var com = file.comment, o = com && strToU8(com); + var u = fl != file.filename.length || (o && (com.length != o.length)); + var hl = fl + exfl(file.extra) + 30; + if (fl > 65535) + throw 'filename too long'; + var header = new u8(hl); + wzh(header, 0, file, f, u); + var chks = [header]; + var pAll = function () { + for (var _i = 0, chks_1 = chks; _i < chks_1.length; _i++) { + var chk = chks_1[_i]; + _this_1.ondata(null, chk, false); + } + chks = []; + }; + var tr = this.d; + this.d = 0; + var ind = this.u.length; + var uf = mrg(file, { + f: f, + u: u, + o: o, + t: function () { + if (file.terminate) + file.terminate(); + }, + r: function () { + pAll(); + if (tr) { + var nxt = _this_1.u[ind + 1]; + if (nxt) + nxt.r(); + else + _this_1.d = 1; + } + tr = 1; + } + }); + var cl = 0; + file.ondata = function (err, dat, final) { + if (err) { + _this_1.ondata(err, dat, final); + _this_1.terminate(); + } + else { + cl += dat.length; + chks.push(dat); + if (final) { + var dd = new u8(16); + wbytes(dd, 0, 0x8074B50); + wbytes(dd, 4, file.crc); + wbytes(dd, 8, cl); + wbytes(dd, 12, file.size); + chks.push(dd); + uf.c = cl, uf.b = hl + cl + 16, uf.crc = file.crc, uf.size = file.size; + if (tr) + uf.r(); + tr = 1; + } + else if (tr) + pAll(); + } + }; + this.u.push(uf); + }; + /** + * Ends the process of adding files and prepares to emit the final chunks. + * This *must* be called after adding all desired files for the resulting + * ZIP file to work properly. + */ + Zip.prototype.end = function () { + var _this_1 = this; + if (this.d & 2) { + if (this.d & 1) + throw 'stream finishing'; + throw 'stream finished'; + } + if (this.d) + this.e(); + else + this.u.push({ + r: function () { + if (!(_this_1.d & 1)) + return; + _this_1.u.splice(-1, 1); + _this_1.e(); + }, + t: function () { } + }); + this.d = 3; + }; + Zip.prototype.e = function () { + var bt = 0, l = 0, tl = 0; + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0); + } + var out = new u8(tl + 22); + for (var _b = 0, _c = this.u; _b < _c.length; _b++) { + var f = _c[_b]; + wzh(out, bt, f, f.f, f.u, f.c, l, f.o); + bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b; + } + wzf(out, bt, this.u.length, tl, l); + this.ondata(null, out, true); + this.d = 2; + }; + /** + * A method to terminate any internal workers used by the stream. Subsequent + * calls to add() will fail. + */ + Zip.prototype.terminate = function () { + for (var _i = 0, _a = this.u; _i < _a.length; _i++) { + var f = _a[_i]; + f.t(); + } + this.d = 2; + }; + return Zip; +}()); +export { Zip }; +export function zip(data, opts, cb) { + if (!cb) + cb = opts, opts = {}; + if (typeof cb != 'function') + throw 'no callback'; + var r = {}; + fltn(data, '', r, opts); + var k = Object.keys(r); + var lft = k.length, o = 0, tot = 0; + var slft = lft, files = new Array(lft); + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var cbf = function () { + var out = new u8(tot + 22), oe = o, cdl = tot - o; + tot = 0; + for (var i = 0; i < slft; ++i) { + var f = files[i]; + try { + var l = f.c.length; + wzh(out, tot, f, f.f, f.u, l); + var badd = 30 + f.f.length + exfl(f.extra); + var loc = tot + badd; + out.set(f.c, loc); + wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l; + } + catch (e) { + return cb(e, null); + } + } + wzf(out, o, files.length, cdl, oe); + cb(null, out); + }; + if (!lft) + cbf(); + var _loop_1 = function (i) { + var fn = k[i]; + var _a = r[fn], file = _a[0], p = _a[1]; + var c = crc(), size = file.length; + c.p(file); + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + var compression = p.level == 0 ? 0 : 8; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); + } + else { + var l = d.length; + files[i] = mrg(p, { + size: size, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + compression: compression + }); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + if (!--lft) + cbf(); + } + }; + if (s > 65535) + cbl('filename too long', null); + if (!compression) + cbl(null, file); + else if (size < 160000) { + try { + cbl(null, deflateSync(file, p)); + } + catch (e) { + cbl(e, null); + } + } + else + term.push(deflate(file, p, cbl)); + }; + // Cannot use lft because it can decrease + for (var i = 0; i < slft; ++i) { + _loop_1(i); + } + return tAll; +} +/** + * Synchronously creates a ZIP file. Prefer using `zip` for better performance + * with more than one file. + * @param data The directory structure for the ZIP archive + * @param opts The main options, merged with per-file options + * @returns The generated ZIP archive + */ +export function zipSync(data, opts) { + if (!opts) + opts = {}; + var r = {}; + var files = []; + fltn(data, '', r, opts); + var o = 0; + var tot = 0; + for (var fn in r) { + var _a = r[fn], file = _a[0], p = _a[1]; + var compression = p.level == 0 ? 0 : 8; + var f = strToU8(fn), s = f.length; + var com = p.comment, m = com && strToU8(com), ms = m && m.length; + var exl = exfl(p.extra); + if (s > 65535) + throw 'filename too long'; + var d = compression ? deflateSync(file, p) : file, l = d.length; + var c = crc(); + c.p(file); + files.push(mrg(p, { + size: file.length, + crc: c.d(), + c: d, + f: f, + m: m, + u: s != fn.length || (m && (com.length != ms)), + o: o, + compression: compression + })); + o += 30 + s + exl + l; + tot += 76 + 2 * (s + exl) + (ms || 0) + l; + } + var out = new u8(tot + 22), oe = o, cdl = tot - o; + for (var i = 0; i < files.length; ++i) { + var f = files[i]; + wzh(out, f.o, f, f.f, f.u, f.c.length); + var badd = 30 + f.f.length + exfl(f.extra); + out.set(f.c, f.o + badd); + wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0); + } + wzf(out, o, files.length, cdl, oe); + return out; +} +/** + * Streaming pass-through decompression for ZIP archives + */ +var UnzipPassThrough = /*#__PURE__*/ (function () { + function UnzipPassThrough() { + } + UnzipPassThrough.prototype.push = function (data, final) { + this.ondata(null, data, final); + }; + UnzipPassThrough.compression = 0; + return UnzipPassThrough; +}()); +export { UnzipPassThrough }; +/** + * Streaming DEFLATE decompression for ZIP archives. Prefer AsyncZipInflate for + * better performance. + */ +var UnzipInflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE decompression that can be used in ZIP archives + */ + function UnzipInflate() { + var _this_1 = this; + this.i = new Inflate(function (dat, final) { + _this_1.ondata(null, dat, final); + }); + } + UnzipInflate.prototype.push = function (data, final) { + try { + this.i.push(data, final); + } + catch (e) { + this.ondata(e, data, final); + } + }; + UnzipInflate.compression = 8; + return UnzipInflate; +}()); +export { UnzipInflate }; +/** + * Asynchronous streaming DEFLATE decompression for ZIP archives + */ +var AsyncUnzipInflate = /*#__PURE__*/ (function () { + /** + * Creates a DEFLATE decompression that can be used in ZIP archives + */ + function AsyncUnzipInflate(_, sz) { + var _this_1 = this; + if (sz < 320000) { + this.i = new Inflate(function (dat, final) { + _this_1.ondata(null, dat, final); + }); + } + else { + this.i = new AsyncInflate(function (err, dat, final) { + _this_1.ondata(err, dat, final); + }); + this.terminate = this.i.terminate; + } + } + AsyncUnzipInflate.prototype.push = function (data, final) { + if (this.i.terminate) + data = slc(data, 0); + this.i.push(data, final); + }; + AsyncUnzipInflate.compression = 8; + return AsyncUnzipInflate; +}()); +export { AsyncUnzipInflate }; +/** + * A ZIP archive decompression stream that emits files as they are discovered + */ +var Unzip = /*#__PURE__*/ (function () { + /** + * Creates a ZIP decompression stream + * @param cb The callback to call whenever a file in the ZIP archive is found + */ + function Unzip(cb) { + this.onfile = cb; + this.k = []; + this.o = { + 0: UnzipPassThrough + }; + this.p = et; + } + /** + * Pushes a chunk to be unzipped + * @param chunk The chunk to push + * @param final Whether this is the last chunk + */ + Unzip.prototype.push = function (chunk, final) { + var _this_1 = this; + if (!this.onfile) + throw 'no callback'; + if (!this.p) + throw 'stream finished'; + if (this.c > 0) { + var len = Math.min(this.c, chunk.length); + var toAdd = chunk.subarray(0, len); + this.c -= len; + if (this.d) + this.d.push(toAdd, !this.c); + else + this.k[0].push(toAdd); + chunk = chunk.subarray(len); + if (chunk.length) + return this.push(chunk, final); + } + else { + var f = 0, i = 0, is = void 0, buf = void 0; + if (!this.p.length) + buf = chunk; + else if (!chunk.length) + buf = this.p; + else { + buf = new u8(this.p.length + chunk.length); + buf.set(this.p), buf.set(chunk, this.p.length); + } + var l = buf.length, oc = this.c, add = oc && this.d; + var _loop_2 = function () { + var _a; + var sig = b4(buf, i); + if (sig == 0x4034B50) { + f = 1, is = i; + this_1.d = null; + this_1.c = 0; + var bf = b2(buf, i + 6), cmp_1 = b2(buf, i + 8), u = bf & 2048, dd = bf & 8, fnl = b2(buf, i + 26), es = b2(buf, i + 28); + if (l > i + 30 + fnl + es) { + var chks_2 = []; + this_1.k.unshift(chks_2); + f = 2; + var sc_1 = b4(buf, i + 18), su_1 = b4(buf, i + 22); + var fn_1 = strFromU8(buf.subarray(i + 30, i += 30 + fnl), !u); + if (sc_1 == 4294967295) { + _a = dd ? [-2] : z64e(buf, i), sc_1 = _a[0], su_1 = _a[1]; + } + else if (dd) + sc_1 = -1; + i += es; + this_1.c = sc_1; + var d_1; + var file_1 = { + name: fn_1, + compression: cmp_1, + start: function () { + if (!file_1.ondata) + throw 'no callback'; + if (!sc_1) + file_1.ondata(null, et, true); + else { + var ctr = _this_1.o[cmp_1]; + if (!ctr) + throw 'unknown compression type ' + cmp_1; + d_1 = sc_1 < 0 ? new ctr(fn_1) : new ctr(fn_1, sc_1, su_1); + d_1.ondata = function (err, dat, final) { file_1.ondata(err, dat, final); }; + for (var _i = 0, chks_3 = chks_2; _i < chks_3.length; _i++) { + var dat = chks_3[_i]; + d_1.push(dat, false); + } + if (_this_1.k[0] == chks_2 && _this_1.c) + _this_1.d = d_1; + else + d_1.push(et, true); + } + }, + terminate: function () { + if (d_1 && d_1.terminate) + d_1.terminate(); + } + }; + if (sc_1 >= 0) + file_1.size = sc_1, file_1.originalSize = su_1; + this_1.onfile(file_1); + } + return "break"; + } + else if (oc) { + if (sig == 0x8074B50) { + is = i += 12 + (oc == -2 && 8), f = 3, this_1.c = 0; + return "break"; + } + else if (sig == 0x2014B50) { + is = i -= 4, f = 3, this_1.c = 0; + return "break"; + } + } + }; + var this_1 = this; + for (; i < l - 4; ++i) { + var state_1 = _loop_2(); + if (state_1 === "break") + break; + } + this.p = et; + if (oc < 0) { + var dat = f ? buf.subarray(0, is - 12 - (oc == -2 && 8) - (b4(buf, is - 16) == 0x8074B50 && 4)) : buf.subarray(0, i); + if (add) + add.push(dat, !!f); + else + this.k[+(f == 2)].push(dat); + } + if (f & 2) + return this.push(buf.subarray(i), final); + this.p = buf.subarray(i); + } + if (final) { + if (this.c) + throw 'invalid zip file'; + this.p = null; + } + }; + /** + * Registers a decoder with the stream, allowing for files compressed with + * the compression type provided to be expanded correctly + * @param decoder The decoder constructor + */ + Unzip.prototype.register = function (decoder) { + this.o[decoder.compression] = decoder; + }; + return Unzip; +}()); +export { Unzip }; +/** + * Asynchronously decompresses a ZIP archive + * @param data The raw compressed ZIP file + * @param cb The callback to call with the decompressed files + * @returns A function that can be used to immediately terminate the unzipping + */ +export function unzip(data, cb) { + if (typeof cb != 'function') + throw 'no callback'; + var term = []; + var tAll = function () { + for (var i = 0; i < term.length; ++i) + term[i](); + }; + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) { + cb('invalid zip file', null); + return; + } + } + ; + var lft = b2(data, e + 8); + if (!lft) + cb(null, {}); + var c = lft; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) { + cb('invalid zip file', null); + return; + } + c = lft = b4(data, e + 32); + o = b4(data, e + 48); + } + var _loop_3 = function (i) { + var _a = zh(data, o, z), c_1 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + var cbl = function (e, d) { + if (e) { + tAll(); + cb(e, null); + } + else { + files[fn] = d; + if (!--lft) + cb(null, files); + } + }; + if (!c_1) + cbl(null, slc(data, b, b + sc)); + else if (c_1 == 8) { + var infl = data.subarray(b, b + sc); + if (sc < 320000) { + try { + cbl(null, inflateSync(infl, new u8(su))); + } + catch (e) { + cbl(e, null); + } + } + else + term.push(inflate(infl, { size: su }, cbl)); + } + else + cbl('unknown compression type ' + c_1, null); + }; + for (var i = 0; i < c; ++i) { + _loop_3(i); + } + return tAll; +} +/** + * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better + * performance with more than one file. + * @param data The raw compressed ZIP file + * @returns The decompressed files + */ +export function unzipSync(data) { + var files = {}; + var e = data.length - 22; + for (; b4(data, e) != 0x6054B50; --e) { + if (!e || data.length - e > 65558) + throw 'invalid zip file'; + } + ; + var c = b2(data, e + 8); + if (!c) + return {}; + var o = b4(data, e + 16); + var z = o == 4294967295; + if (z) { + e = b4(data, e - 12); + if (b4(data, e) != 0x6064B50) + throw 'invalid zip file'; + c = b4(data, e + 32); + o = b4(data, e + 48); + } + for (var i = 0; i < c; ++i) { + var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off); + o = no; + if (!c_2) + files[fn] = slc(data, b, b + sc); + else if (c_2 == 8) + files[fn] = inflateSync(data.subarray(b, b + sc), new u8(su)); + else + throw 'unknown compression type ' + c_2; + } + return files; +} diff --git a/jsm/libs/flow.module.js b/jsm/libs/flow.module.js new file mode 100644 index 0000000..2469b71 --- /dev/null +++ b/jsm/libs/flow.module.js @@ -0,0 +1,4102 @@ +/** + * https://github.com/sunag/flow + */ + +function __flow__addCSS( css ) { + + try { + + const style = document.createElement( 'style' ); + + style.setAttribute( 'type', 'text/css' ); + style.innerHTML = css; + document.head.appendChild( style ); + + } catch ( e ) {} + +} + +__flow__addCSS( `@keyframes f-animation-open { 0% { transform: scale(.5); opacity: 0; } 100% { transform: scale(1); opacity: 1; }}f-canvas,f-canvas canvas { position: absolute; top: 0; left: 0; margin: 0; padding: 0; width: 100%; height: 100%; -webkit-touch-callout: none; }f-canvas { overflow: auto; cursor: grab;}f-canvas canvas.front { z-index: 10;}body.dragging *:not(.drag) { pointer-events: none !important;}f-canvas.grabbing * { cursor: grabbing; user-select: none;}f-canvas canvas { position: fixed; overflow: hidden; pointer-events: none;}f-canvas::-webkit-scrollbar { width: 8px; height: 8px;}f-canvas::-webkit-scrollbar-thumb:hover{ background: #014fc5;}f-canvas::-webkit-scrollbar-track { background: #363636;}f-canvas::-webkit-scrollbar-thumb { background-color: #666666; border-radius: 10px; border: 0;}f-canvas f-content,f-canvas f-area { position: absolute; display: block;}f-node { position: absolute; margin: 0; padding: 0; user-select: none; width: 320px; z-index: 1; cursor: auto; filter: drop-shadow(0 0 10px #00000061); backdrop-filter: blur(4px);}f-node.selected { z-index: 2;}f-node.selected,f-canvas.dragging-rio f-node:hover,f-canvas.dragging-lio f-node:hover { filter: drop-shadow(0 0 10px #00000061) drop-shadow(0 0 8px #4444dd);}f-node.closed f-element:not(:first-child) { display: none;}f-node.center { top: 50%; left: 50%; transform: translate( -50%, -50% );}f-node.top-right { top: 0; right: 0;}f-node.top-center { top: 0; left: 50%; transform: translateX( -50% );}f-node.top-left { top: 0; left: 0;}f-node { transition: filter 0.2s ease;}f-node { animation: .2s f-animation-open 1 alternate ease-out;}f-tips,f-drop,f-menu,f-menu input,f-menu button,f-element,f-element input,f-element select,f-element button,f-element textarea { font-family: 'Open Sans', sans-serif; font-size: 13px; text-transform: capitalize; color: #eeeeee; outline: solid 0px #000; margin: 0; padding: 0; border: 0; user-select: none; -webkit-tap-highlight-color: transparent; transition: background 0.2s ease, filter 0.2s ease;}f-element input:read-only { color: #666;}f-element input,f-element textarea { text-transform: initial;}f-element input { transition: background 0.1s ease;}f-element input,f-element select,f-element button,f-element textarea { background-color: #232324d1;}f-element { position: relative; width: calc( 100% - 14px ); background: rgba(45, 45, 48, 0.95); pointer-events: auto; border-bottom: 2px solid #232323; display: flex; padding-left: 7px; padding-right: 7px; padding-top: 2px; padding-bottom: 2px;}f-element:after,f-element:before { transition: opacity .17s; opacity: 0; content: '';}f-element[tooltip]:hover:after,f-element[tooltip]:focus-within:after { font-size: 14px !important; display: flex; justify-content: center; position: fixed; margin-left: -7px; width: calc( 100% ); background: #1d1d1de8; border: 1px solid #444444a1; border-radius: 6px; color: #dadada; content: attr( tooltip ); margin-top: -41px; font-size: 16px; padding-top: 3px; padding-bottom: 3px; z-index: 10; opacity: 1; backdrop-filter: blur(4px); white-space: nowrap; overflow: hidden; text-shadow: 1px 1px 0px #0007;}f-element[tooltip]:hover:before,f-element[tooltip]:focus-within:before { border: solid; border-color: #1d1d1de8 transparent; border-width: 12px 6px 0 6px; left: calc( 50% - 6px ); bottom: 30px; position: absolute; opacity: 1; z-index: 11;}f-element[error] { background-color: #ff0000;}f-element[error]:hover:after,f-element[error]:focus-within:after { border: none; background-color: #ff0000bb; filter: drop-shadow( 2px 2px 5px #000 ); color: #fff;}f-element[error]:hover:before,f-element[error]:focus-within:before { border-color: #ff0000bb transparent;}f-element { height: 24px;}f-element input { margin-top: 2px; margin-bottom: 2px; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%); margin-left: 2px; margin-right: 2px; width: 100%; padding-left: 4px; padding-right: 4px;}f-element input.number { cursor: col-resize;}f-element input:focus[type='text'], f-element input:focus[type='range'], f-element input:focus[type='color'] { background: rgba( 0, 0, 0, 0.6 ); outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-element input[type='color'] { appearance: none; padding: 0; margin-left: 2px; margin-right: 2px; height: calc( 100% - 4px ); margin-top: 2px; border: none;}f-element input[type='color']::-webkit-color-swatch-wrapper { padding: 2px;}f-element input[type='color']::-webkit-color-swatch { border: none; cursor: alias;}f-element input[type='range'] { appearance: none; width: 100%; overflow: hidden; padding: 0; cursor: ew-resize;}f-element input[type='range']::-webkit-slider-runnable-track { appearance: none; height: 10px; color: #13bba4; margin: 0;}f-element input[type='range']::-webkit-slider-thumb { appearance: none; width: 0; background: #434343; box-shadow: -500px 0 0 500px rgba( 0, 120, 255, 0.98 ); border-radius: 50%; border: 0 !important;}f-element input[type='range']::-webkit-slider-runnable-track { margin-left: -4px; margin-right: -5px;}f-element input[type='checkbox'] { appearance: none; cursor: pointer;}f-element input[type='checkbox'].toggle { height: 20px; width: 45px; border-radius: 16px; display: inline-block; position: relative; margin: 0; margin-top: 2px; background: linear-gradient( 0deg, #292929 0%, #0a0a0ac2 100% ); transition: all 0.2s ease;}f-element input[type='checkbox'].toggle:after { content: ""; position: absolute; top: 2px; left: 2px; width: 16px; height: 16px; border-radius: 50%; background: white; box-shadow: 0 1px 2px rgba(44, 44, 44, 0.2); transition: all 0.2s cubic-bezier(0.5, 0.1, 0.75, 1.35);}f-element input[type='checkbox'].toggle:checked { background: linear-gradient( 0deg, #0177fb 0%, #0177fb 100% );}f-element input[type='checkbox'].toggle:checked:after { transform: translatex(25px);}f-element.auto-height { display: table;}f-element textarea { width: calc( 100% - 18px ); padding-top: 1px; padding-bottom: 3px; padding-left: 3px; padding-right: 8px; margin-top: 2px; margin-left: 2px; height: calc( 100% - 8px ); max-height: 300px; border-radius: 2px; resize: none; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element.auto-height textarea { resize: auto;}f-element select { width: 100%; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px; cursor: pointer; box-shadow: inset 0px 1px 1px rgb(0 0 0 / 20%), 0px 1px 0px rgb(255 255 255 / 5%);}f-element f-toolbar { position: absolute; display: flex; top: 0; width: 100%; height: 100%; align-content: space-around;}f-element.input-right f-toolbar { right: 7px; float: right; justify-content: end;}f-element f-toolbar { margin-top: auto; margin-bottom: auto; margin-left: 3px; margin-right: 3px; font-size: 18px; line-height: 18px;}f-element f-toolbar button { opacity: .7; cursor: pointer; font-size: 14px; width: unset; height: unset; border-radius: unset; border: unset; outline: 0; background-color: unset; box-shadow: unset;}f-element f-toolbar button:hover,f-element f-toolbar button:active { opacity: 1; border: 0; background-color: unset;}f-element input.range-value { width: 60px; text-align: center;}f-menu.context button,f-element button { width: 100%; height: calc( 100% - 4px ); margin-left: 2px; margin-right: 2px; margin-top: 2px; border-radius: 3px; cursor: pointer;}f-element button { box-shadow: inset 1px 1px 1px 0 rgb(255 255 255 / 17%), inset -2px -2px 2px 0 rgb(0 0 0 / 26%);}f-element button:hover { color: #fff; background-color: #2a2a2a;}f-element button:active { border: 1px solid rgba( 0, 120, 255, 0.98 );}f-element f-inputs,f-element f-subinputs { display: flex; justify-content: flex-end; width: 100%;}f-element f-inputs { left: 100px; top: 50%; transform: translateY(-50%); position: absolute; width: calc( 100% - 106px ); height: calc( 100% - 4px ); z-index: 1;}f-element.inputs-disable f-inputs { filter: grayscale(100%); opacity: .5;}f-element.inputs-disable f-inputs input { pointer-events: none;}f-element f-label,f-element span { margin: auto; text-shadow: 1px 1px 0px #0007;}f-element f-label { padding-left: 4px; white-space: nowrap; position: absolute; top: 50%; transform: translateY(-50%); width: calc( 100% - 20px );}f-element.right f-label { text-align: right;}f-element.center f-label { text-align: center;}f-element f-label i { float: left; font-size: 18px; margin-right: 6px;}f-element f-label.center { width: 100%; text-align: center; display: block;}f-element.title { height: 29px; background-color: #3a3a3ab0; background-color: #3b3b43ed; cursor: all-scroll; border-top-left-radius: 6px; border-top-right-radius: 6px;}f-element.blue { background-color: #014fc5;}f-element.red { background-color: #bd0b0b;}f-element.green { background-color: #148d05;}f-element.yellow { background-color: #d6b100;}f-element.title.left { text-align: left; display: inline-grid; justify-content: start;}f-element.title span { text-align: center; font-size: 15px; padding-top: 2px;}f-element.title i { font-size: 18px; position: absolute; right: 10px; top: 50%; transform: translateY(-50%); opacity: .5;}f-element.title f-toolbar i { font-size: 20px; right: unset; left: 0px;}f-element.input-right.title i { left: 10px; right: unset;}f-element.title.left span { text-align: left;}f-element f-io { border: 2px solid #dadada; width: 7px; height: 7px; position: absolute; background: #242427; border-radius: 8px; float: left; left: -7px; top: calc( 50% - 5px ); cursor: alias; box-shadow: 0 0 3px 2px #0000005e; z-index: 1;}f-element f-io.connect,f-canvas.dragging-rio f-element:hover f-io.lio,f-canvas.dragging-lio f-element:hover f-io.rio { zoom: 1.4;}f-node.io-connect f-io:not(.connect) { border: 2px solid #dadada !important; zoom: 1 !important;}f-element f-io.rio { float: right; right: -7px; left: unset;}f-element f-disconnect { position: absolute; left: -35px; top: 50%; font-size: 22px; transform: translateY( -50% ); filter: drop-shadow(0 0 5px #000); text-shadow: 0px 0px 5px black; cursor: pointer; transition: all .2s;}f-element.input-right f-disconnect { right: -35px; left: unset;}f-element f-disconnect:hover { color: #ff3300;}f-element textarea::-webkit-scrollbar { width: 6px;}f-element textarea::-webkit-scrollbar-track { background: #111; } f-element textarea::-webkit-scrollbar-thumb { background: #0177fb; }f-element textarea::-webkit-scrollbar-thumb:hover { background: #1187ff; }f-element.small { height: 18px;}f-element.large { height: 36px;}body.connecting f-node:not(.io-connect) f-element:hover,f-element.select { background-color: rgba(61, 70, 82, 0.98);}f-element.invalid > f-io { zoom: 1 !important;}f-element.invalid::after { font-size: 14px !important; display: flex; justify-content: center; align-items:center; margin: auto; position: absolute; width: 100%; height: 100%; background: #bd0b0b77; vertical-align: middle; color: #fff; content: 'Not Compatible'; opacity: .95; backdrop-filter: grayscale(100%); white-space: nowrap; overflow: hidden; left: 0; top: 0; text-transform: initial;}f-element.invalid > f-inputs,f-element.invalid > f-label { opacity: .1;}f-drop { width: 100%; height: 100%; position: sticky; left: 0; top: 0; background: #02358417; text-align: center; justify-content: center; align-items: center; display: flex; box-shadow: inset 0 0 20px 10px #464ace17; pointer-events: none; transition: all .07s; opacity: 0; visibility: hidden;}f-drop.visible { visibility: unset; opacity: unset; transition: all .23s;}f-drop span { opacity: .5; font-size: 40px; text-shadow: 0px 0px 5px #000; font-weight: bold;}f-tooltip { pointer-events: none;}f-tooltip { position: absolute; left: 0; top: 0; background: rgba(0,0,0,.8); backdrop-filter: blur(4px); font-size: 14px; padding: 7px; left: 50%; border-radius: 10px; transform: translateX(-50%); visibility: hidden; pointer-events: none; opacity: 0; transition: all 0.3s ease; z-index: 150; white-space: nowrap;}f-menu.context,f-menu.search { position: absolute;}f-menu.context { width: 170px; z-index: 110;}f-menu.search { bottom: 85px; left: 50%; transform: translateX(-50%); z-index: 10; width: 300px;}f-menu.context f-list { display: block; margin: 0; background: #171717e6; font-size: 12px; border-radius: 6px; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; box-shadow: 3px 3px 6px rgba(0,0,0,.2); transition: opacity 0.2s ease, transform 0.1s ease;}f-menu.search f-list { margin: 0 6px 0 6px; display: flex; flex-direction: column-reverse; margin-bottom: 5px;}f-menu.context.hidden { visibility: hidden; opacity: 0;}f-menu.context f-item,f-menu.search f-item { display: block; position: relative; margin: 0; padding: 0; white-space: nowrap;}f-menu.search f-item { opacity: 0;}f-menu.context f-item.submenu::after { content: ""; position: absolute; right: 6px; top: 50%; -webkit-transform: translateY(-50%); transform: translateY(-50%); border: 5px solid transparent; border-left-color: #808080;}f-menu.context f-item:hover > f-menu,f-menu.context f-item.active > f-menu { visibility: unset; transform: unset; opacity: unset;}f-menu.context f-menu { top: 0px; left: calc( 100% - 4px );}f-menu.context f-item button,f-menu.search f-item button { overflow: visible; display: block; width: calc( 100% - 6px ); text-align: left; cursor: pointer; white-space: nowrap; padding: 6px 8px; border-radius: 3px; background: rgba(45, 45, 48, 0.95); border: 0; color: #ddd; margin: 3px; text-shadow: 1px 1px 0px #0007;}f-menu.context f-item button i,f-menu.search f-item button i { float: left; font-size: 16px;}f-menu.context f-item button span,f-menu.search f-item button span { margin-left: 6px;}f-menu.context f-item:hover > button,f-menu.search f-item:hover > button,f-menu.search f-item.active > button { color: #fff; background-color: rgba(61, 70, 82, 0.98);}f-menu.search f-item:hover,f-menu.search f-item.active { opacity: 1 !important;}f-menu.context f-item button:active { outline: solid 1px rgba( 0, 80, 200, 0.98 );}f-menu.context f-item f-tooltip { margin-left: 85px; top: -50px;}f-menu.search f-item { display: none;}f-menu.search f-item:nth-child(1) { opacity: 1; display: unset;}f-menu.search f-item:nth-child(2) { opacity: .8; display: unset;}f-menu.search f-item:nth-child(3) { opacity: .6; display: unset;}f-menu.search f-item:nth-child(4) { opacity: .4; display: unset;}f-menu.search f-item button { border-radius: 14px;}f-tips { right: 10px; top: 10px; position: absolute; z-index: 100; pointer-events: none; display: flex; flex-direction: column;}f-tips f-tip { width: 450px; font-size: 13px; border-radius: 6px; text-align: center; display: block; height: auto; color: #ffffffe0; margin: 4px; padding: 4px; background: #17171794; border: 1px solid #7e7e7e38; line-height: 100%; backdrop-filter: blur(6px); transition: all 0.2s ease; text-transform: initial; opacity: 0;}f-tips f-tip:nth-child(1) { opacity: 1;}f-tips f-tip:nth-child(2) { opacity: .75;}f-tips f-tip:nth-child(3) { opacity: .25;}f-tips f-tip:nth-child(4) { opacity: .1;}f-tips f-tip.error { background: #b900005e;}f-menu.search input { width: calc( 100% - 28px ); height: 41px; position: absolute; z-index: 10; border-radius: 20px; padding-left: 14px; padding-right: 14px; font-size: 15px; background-color: #17171794; border: 1px solid #7e7e7e45; backdrop-filter: blur(6px); box-shadow: 3px 3px 6px rgb(0 0 0 / 20%); text-transform: initial;}f-menu.circle { position: absolute; left: 40px; bottom: 40px; z-index: 100;}f-menu.circle f-item { align-content: space-around; margin-right: 20px;}f-menu.circle f-item button { width: 47px; height: 47px; font-size: 22px; background: #17171794; border-radius: 50%; backdrop-filter: blur(6px); border: 1px solid #7e7e7e45; line-height: 100%; cursor: pointer; box-shadow: 3px 3px 6px rgba(0,0,0,.2);}f-menu.circle f-item f-tooltip { margin-top: -60px;}.f-rounded f-node f-element,.f-rounded f-node f-element.title.left { border-radius: 10px 5px 10px 5px;}.f-rounded f-node f-element input, .f-rounded f-node f-element select,.f-rounded f-node f-element button,.f-rounded f-node f-element textarea,.f-rounded f-node f-element input[type='checkbox'].toggle,.f-rounded f-node f-element input[type='checkbox'].toggle:after { border-radius: 20px 10px;}.f-rounded f-node f-element input { padding-left: 7px; padding-right: 7px;}.f-rounded f-menu.context,.f-rounded f-menu.context f-item button { border-radius: 20px 10px;}@media (hover: hover) and (pointer: fine) { f-node:not(.selected):hover { filter: drop-shadow(0 0 6px #66666630); } f-element f-toolbar { visibility: hidden; opacity: 0; transition: opacity 0.2s ease; } f-node:hover > f-element f-toolbar { visibility: visible; opacity: 1; } f-element f-io:hover { zoom: 1.4; } f-menu.circle f-item button:hover { background-color: #2a2a2a; } f-menu.search input:hover, f-menu.search input:focus { background-color: #1a1a1a; filter: drop-shadow(0 0 6px #66666630); } f-menu.search input:focus { filter: drop-shadow(0 0 8px #4444dd); } f-menu.circle f-item button:hover > f-tooltip, f-menu.context f-item button:hover > f-tooltip { visibility: visible; opacity: 1; } f-menu.circle f-item button:hover > f-tooltip { margin-top: -50px; } f-menu.context f-item button:hover > f-tooltip { top: -30px; } f-menu.circle f-item button:focus > f-tooltip, f-menu.context f-item button:focus > f-tooltip { visibility: hidden; opacity: 0; }}@media (hover: none) and (pointer: coarse) { body.dragging f-canvas, body.connecting f-canvas { overflow: hidden !important; }}f-canvas { will-change: top, left;}f-node { will-change: transform !important;}` ); + +const REVISION = '1'; + +const Styles = { + icons: { + close: '', + unlink: '' + } +}; + +let _id = 0; + +class Serializer extends EventTarget { + + constructor() { + + super(); + + this._id = _id ++; + + this._serializable = true; + + } + + get id() { + + return this._id; + + } + + setSerializable( value ) { + + this._serializable = value; + + return this; + + } + + getSerializable() { + + return this._serializable; + + } + + serialize( /*data*/ ) { + + console.warn( 'Serializer: Abstract function.' ); + + } + + deserialize( /*data*/ ) { + + console.warn( 'Serializer: Abstract function.' ); + + } + + toJSON( data = null ) { + + let object = null; + + const id = this.id; + + if ( data !== null ) { + + const objects = data.objects; + + object = objects[ id ]; + + if ( object === undefined ) { + + object = { objects }; + + this.serialize( object ); + + delete object.objects; + + objects[ id ] = object; + + } + + } else { + + object = { objects: {} }; + + this.serialize( object ); + + } + + object.id = id; + object.type = this.constructor.name; + + return object; + + } + +} + +class PointerMonitor { + + started = false; + + constructor() { + + this.x = 0; + this.y = 0; + + this._onMoveEvent = ( e ) => { + + const event = e.touches ? e.touches[ 0 ] : e; + + this.x = event.x; + this.y = event.y; + + }; + + } + + start() { + + if ( this.started ) return; + + this.started = true; + + window.addEventListener( 'wheel', this._onMoveEvent, true ); + + window.addEventListener( 'mousedown', this._onMoveEvent, true ); + window.addEventListener( 'touchstart', this._onMoveEvent, true ); + + window.addEventListener( 'mousemove', this._onMoveEvent, true ); + window.addEventListener( 'touchmove', this._onMoveEvent, true ); + + window.addEventListener( 'dragover', this._onMoveEvent, true ); + + return this; + + } + +} + +const pointer = new PointerMonitor().start(); + +const draggableDOM = ( dom, callback = null, className = 'dragging' ) => { + + let dragData = null; + + const getZoom = () => { + + let zoomDOM = dom; + + while ( zoomDOM && zoomDOM !== document ) { + + const zoom = zoomDOM.style.zoom; + + if ( zoom ) { + + return Number( zoom ); + + } + + zoomDOM = zoomDOM.parentNode; + + } + + return 1; + + }; + + const onMouseDown = ( e ) => { + + const event = e.touches ? e.touches[ 0 ] : e; + + e.stopImmediatePropagation(); + + dragData = { + client: { x: event.clientX, y: event.clientY }, + delta: { x: 0, y: 0 }, + start: { x: dom.offsetLeft, y: dom.offsetTop }, + dragging: false, + isTouch: !! e.touches + }; + + window.addEventListener( 'mousemove', onGlobalMouseMove ); + window.addEventListener( 'mouseup', onGlobalMouseUp ); + + window.addEventListener( 'touchmove', onGlobalMouseMove ); + window.addEventListener( 'touchend', onGlobalMouseUp ); + + }; + + const onGlobalMouseMove = ( e ) => { + + const { start, delta, client } = dragData; + + const event = e.touches ? e.touches[ 0 ] : e; + + const zoom = getZoom(); + + delta.x = ( event.clientX - client.x ) / zoom; + delta.y = ( event.clientY - client.y ) / zoom; + + dragData.x = start.x + delta.x; + dragData.y = start.y + delta.y; + + if ( dragData.dragging === true ) { + + if ( callback !== null ) { + + callback( dragData ); + + } else { + + dom.style.cssText += `; left: ${ dragData.x }px; top: ${ dragData.y }px;`; + + } + + e.stopImmediatePropagation(); + + } else { + + if ( Math.abs( delta.x ) > 2 || Math.abs( delta.y ) > 2 ) { + + dragData.dragging = true; + + dom.classList.add( 'drag' ); + + if ( className ) document.body.classList.add( className ); + + e.stopImmediatePropagation(); + + } + + } + + }; + + const onGlobalMouseUp = ( e ) => { + + e.stopImmediatePropagation(); + + dom.classList.remove( 'drag' ); + + if ( className ) document.body.classList.remove( className ); + + window.removeEventListener( 'mousemove', onGlobalMouseMove ); + window.removeEventListener( 'mouseup', onGlobalMouseUp ); + + window.removeEventListener( 'touchmove', onGlobalMouseMove ); + window.removeEventListener( 'touchend', onGlobalMouseUp ); + + if ( callback === null ) { + + dom.removeEventListener( 'mousedown', onMouseDown ); + dom.removeEventListener( 'touchstart', onMouseDown ); + + } + + dragData.dragging = false; + + if ( callback !== null ) { + + callback( dragData ); + + } + + }; + + if ( dom instanceof Event ) { + + const e = dom; + dom = e.target; + + onMouseDown( e ); + + } else { + + dom.addEventListener( 'mousedown', onMouseDown ); + dom.addEventListener( 'touchstart', onMouseDown ); + + } + +}; + +const dispatchEventList = ( list, ...params ) => { + + for ( const callback of list ) { + + if ( callback( ...params ) === false ) { + + return false; + + } + + } + + return true; + +}; + +const toPX = ( val ) => { + + if ( isNaN( val ) === false ) { + + val = `${ val }px`; + + } + + return val; + +}; + +const toHex = ( val ) => { + + if ( isNaN( val ) === false ) { + + val = `#${ val.toString( 16 ).padStart( 6, '0' ) }`; + + } + + return val; + +}; + +var Utils = /*#__PURE__*/Object.freeze( { + __proto__: null, + pointer: pointer, + draggableDOM: draggableDOM, + dispatchEventList: dispatchEventList, + toPX: toPX, + toHex: toHex +} ); + +class Link { + + constructor( inputElement = null, outputElement = null ) { + + this.inputElement = inputElement; + this.outputElement = outputElement; + + } + + get lioElement() { + + if ( Link.InputDirection === 'left' ) { + + return this.outputElement; + + } else { + + return this.inputElement; + + } + + } + + get rioElement() { + + if ( Link.InputDirection === 'left' ) { + + return this.inputElement; + + } else { + + return this.outputElement; + + } + + } + +} + +//Link.InputDirection = 'right'; +Link.InputDirection = 'left'; + +let selected = null; + +class Element extends Serializer { + + constructor( draggable = false ) { + + super(); + + const dom = document.createElement( 'f-element' ); + dom.element = this; + + const onSelect = ( e ) => { + + let element = this; + + if ( e.changedTouches && e.changedTouches.length > 0 ) { + + const touch = e.changedTouches[ 0 ]; + + let overDOM = document.elementFromPoint( touch.clientX, touch.clientY ); + + while ( overDOM && ( ! overDOM.element || ! overDOM.element.isElement ) ) { + + overDOM = overDOM.parentNode; + + } + + element = overDOM ? overDOM.element : null; + + } + + const type = e.type; + + if ( ( type === 'mouseout' ) && selected === element ) { + + selected = null; + + } else { + + selected = element; + + } + + }; + + if ( draggable === false ) { + + dom.ontouchstart = dom.onmousedown = ( e ) => { + + e.stopPropagation(); + + }; + + } + + dom.addEventListener( 'mouseup', onSelect, true ); + dom.addEventListener( 'mouseover', onSelect ); + dom.addEventListener( 'mouseout', onSelect ); + dom.addEventListener( 'touchmove', onSelect ); + dom.addEventListener( 'touchend', onSelect ); + + this.inputs = []; + + this.links = []; + + this.dom = dom; + + this.lioLength = 0; + this.rioLength = 0; + + this.events = { + 'connect': [], + 'connectChildren': [], + 'valid': [] + }; + + this.node = null; + + this.style = ''; + + this.objectCallback = null; + + this.enabledInputs = true; + + this.visible = true; + + this.inputsDOM = dom; + + this.disconnectDOM = null; + + this.lioDOM = this._createIO( 'lio' ); + this.rioDOM = this._createIO( 'rio' ); + + this.dom.classList.add( `input-${ Link.InputDirection }` ); + + this.dom.append( this.lioDOM ); + this.dom.append( this.rioDOM ); + + this.addEventListener( 'connect', ( ) => { + + dispatchEventList( this.events.connect, this ); + + } ); + + this.addEventListener( 'connectChildren', ( ) => { + + dispatchEventList( this.events.connectChildren, this ); + + } ); + + } + + setAttribute( name, value ) { + + this.dom.setAttribute( name, value ); + + return this; + + } + + onValid( callback ) { + + this.events.valid.push( callback ); + + return this; + + } + + onConnect( callback, childrens = false ) { + + this.events.connect.push( callback ); + + if ( childrens ) { + + this.events.connectChildren.push( callback ); + + } + + return this; + + } + + setObjectCallback( callback ) { + + this.objectCallback = callback; + + return this; + + } + + getObject( output = null ) { + + return this.objectCallback ? this.objectCallback( output ) : null; + + } + + setVisible( value ) { + + this.visible = value; + + this.dom.style.display = value ? '' : 'none'; + + return this; + + } + + getVisible() { + + return this.visible; + + } + + setEnabledInputs( value ) { + + const dom = this.dom; + + if ( ! this.enabledInputs ) dom.classList.remove( 'inputs-disable' ); + + if ( ! value ) dom.classList.add( 'inputs-disable' ); + + this.enabledInputs = value; + + return this; + + } + + getEnabledInputs() { + + return this.enabledInputs; + + } + + setColor( color ) { + + this.dom.style[ 'background-color' ] = toHex( color ); + + return this; + + } + + setStyle( style ) { + + const dom = this.dom; + + if ( this.style ) dom.classList.remove( this.style ); + + if ( style ) dom.classList.add( style ); + + this.style = style; + + return this; + + } + + setInput( length ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setLIO( length ); + + } else { + + return this.setRIO( length ); + + } + + } + + setInputColor( color ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setLIOColor( color ); + + } else { + + return this.setRIOColor( color ); + + } + + } + + setOutput( length ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setRIO( length ); + + } else { + + return this.setLIO( length ); + + } + + } + + setOutputColor( color ) { + + if ( Link.InputDirection === 'left' ) { + + return this.setRIOColor( color ); + + } else { + + return this.setLIOColor( color ); + + } + + } + + get inputLength() { + + if ( Link.InputDirection === 'left' ) { + + return this.lioLength; + + } else { + + return this.rioLength; + + } + + } + + get outputLength() { + + if ( Link.InputDirection === 'left' ) { + + return this.rioLength; + + } else { + + return this.lioLength; + + } + + } + + setLIOColor( color ) { + + this.lioDOM.style[ 'border-color' ] = toHex( color ); + + return this; + + } + + setLIO( length ) { + + this.lioLength = length; + + this.lioDOM.style.visibility = length > 0 ? '' : 'hidden'; + + return this; + + } + + getLIOColor() { + + return this.lioDOM.style[ 'border-color' ]; + + } + + setRIOColor( color ) { + + this.rioDOM.style[ 'border-color' ] = toHex( color ); + + return this; + + } + + getRIOColor() { + + return this.rioDOM.style[ 'border-color' ]; + + } + + setRIO( length ) { + + this.rioLength = length; + + this.rioDOM.style.visibility = length > 0 ? '' : 'hidden'; + + return this; + + } + + add( input ) { + + this.inputs.push( input ); + + input.element = this; + + this.inputsDOM.append( input.dom ); + + return this; + + } + + setHeight( val ) { + + this.dom.style.height = toPX( val ); + + return this; + + } + + getHeight() { + + return this.dom.style.height; + + } + + connect( element = null ) { + + if ( this.disconnectDOM !== null ) { + + // remove the current input + + this.disconnectDOM.dispatchEvent( new Event( 'disconnect' ) ); + + } + + if ( element !== null ) { + + if ( dispatchEventList( this.events.valid, this, element, 'connect' ) === false ) { + + return false; + + } + + const link = new Link( this, element ); + + this.links.push( link ); + + if ( this.disconnectDOM === null ) { + + this.disconnectDOM = document.createElement( 'f-disconnect' ); + this.disconnectDOM.innerHTML = Styles.icons.unlink ? `` : '✖'; + + this.dom.append( this.disconnectDOM ); + + const onDisconnect = () => { + + this.links = []; + this.dom.removeChild( this.disconnectDOM ); + + this.disconnectDOM.removeEventListener( 'mousedown', onClick, true ); + this.disconnectDOM.removeEventListener( 'touchstart', onClick, true ); + this.disconnectDOM.removeEventListener( 'disconnect', onDisconnect, true ); + + element.removeEventListener( 'connect', onConnect ); + element.removeEventListener( 'connectChildren', onConnect ); + element.removeEventListener( 'nodeConnect', onConnect ); + element.removeEventListener( 'nodeConnectChildren', onConnect ); + element.removeEventListener( 'dispose', onDispose ); + + this.disconnectDOM = null; + + }; + + const onConnect = () => { + + this.dispatchEvent( new Event( 'connectChildren' ) ); + + }; + + const onDispose = () => { + + this.connect(); + + }; + + const onClick = ( e ) => { + + e.stopPropagation(); + + this.connect(); + + }; + + this.disconnectDOM.addEventListener( 'mousedown', onClick, true ); + this.disconnectDOM.addEventListener( 'touchstart', onClick, true ); + this.disconnectDOM.addEventListener( 'disconnect', onDisconnect, true ); + + element.addEventListener( 'connect', onConnect ); + element.addEventListener( 'connectChildren', onConnect ); + element.addEventListener( 'nodeConnect', onConnect ); + element.addEventListener( 'nodeConnectChildren', onConnect ); + element.addEventListener( 'dispose', onDispose ); + + } + + } + + this.dispatchEvent( new Event( 'connect' ) ); + + return true; + + } + + dispose() { + + this.dispatchEvent( new Event( 'dispose' ) ); + + } + + serialize( data ) { + + const height = this.getHeight(); + + const inputs = []; + const links = []; + + for ( const input of this.inputs ) { + + inputs.push( input.toJSON( data ).id ); + + } + + for ( const link of this.links ) { + + if ( link.inputElement !== null && link.outputElement !== null ) { + + links.push( link.outputElement.toJSON( data ).id ); + + } + + } + + if ( this.inputLength > 0 ) data.inputLength = this.inputLength; + if ( this.outputLength > 0 ) data.outputLength = this.outputLength; + + if ( inputs.length > 0 ) data.inputs = inputs; + if ( links.length > 0 ) data.links = links; + + if ( this.style !== '' ) { + + data.style = this.style; + + } + + if ( height !== '' ) { + + data.height = height; + + } + + } + + deserialize( data ) { + + if ( data.inputLength !== undefined ) this.setInput( data.inputLength ); + if ( data.outputLength !== undefined ) this.setOutput( data.outputLength ); + + if ( data.inputs !== undefined ) { + + const inputs = this.inputs; + + if ( inputs.length > 0 ) { + + let index = 0; + + for ( const id of data.inputs ) { + + data.objects[ id ] = inputs[ index ++ ]; + + } + + } else { + + for ( const id of data.inputs ) { + + this.add( data.objects[ id ] ); + + } + + } + + } + + if ( data.links !== undefined ) { + + for ( const id of data.links ) { + + this.connect( data.objects[ id ] ); + + } + + } + + if ( data.style !== undefined ) { + + this.setStyle( data.style ); + + } + + if ( data.height !== undefined ) { + + this.setHeight( data.height ); + + } + + } + + getLinkedObject( output = null ) { + + const linkedElement = this.getLinkedElement(); + + return linkedElement ? linkedElement.getObject( output ) : null; + + } + + getLinkedElement() { + + const link = this.getLink(); + + return link ? link.outputElement : null; + + } + + getLink() { + + return this.links[ 0 ]; + + } + + _createIO( type ) { + + const { dom } = this; + + const ioDOM = document.createElement( 'f-io' ); + ioDOM.style.visibility = 'hidden'; + ioDOM.className = type; + + const onConnectEvent = ( e ) => { + + e.preventDefault(); + + e.stopPropagation(); + + selected = null; + + const nodeDOM = this.node.dom; + + nodeDOM.classList.add( 'io-connect' ); + + ioDOM.classList.add( 'connect' ); + dom.classList.add( 'select' ); + + const defaultOutput = Link.InputDirection === 'left' ? 'lio' : 'rio'; + + const link = type === defaultOutput ? new Link( this ) : new Link( null, this ); + const previewLink = new Link( link.inputElement, link.outputElement ); + + this.links.push( link ); + + draggableDOM( e, ( data ) => { + + if ( previewLink.outputElement ) + previewLink.outputElement.dom.classList.remove( 'invalid' ); + + if ( previewLink.inputElement ) + previewLink.inputElement.dom.classList.remove( 'invalid' ); + + previewLink.inputElement = link.inputElement; + previewLink.outputElement = link.outputElement; + + if ( type === defaultOutput ) { + + previewLink.outputElement = selected; + + } else { + + previewLink.inputElement = selected; + + } + + const isInvalid = previewLink.inputElement !== null && previewLink.outputElement !== null && + previewLink.inputElement.inputLength > 0 && previewLink.outputElement.outputLength > 0 && + dispatchEventList( previewLink.inputElement.events.valid, previewLink.inputElement, previewLink.outputElement, data.dragging ? 'dragging' : 'dragged' ) === false; + + if ( data.dragging && isInvalid ) { + + if ( type === defaultOutput ) { + + if ( previewLink.outputElement ) + previewLink.outputElement.dom.classList.add( 'invalid' ); + + } else { + + if ( previewLink.inputElement ) + previewLink.inputElement.dom.classList.add( 'invalid' ); + + } + + return; + + } + + if ( ! data.dragging ) { + + nodeDOM.classList.remove( 'io-connect' ); + + ioDOM.classList.remove( 'connect' ); + dom.classList.remove( 'select' ); + + this.links.splice( this.links.indexOf( link ), 1 ); + + if ( selected !== null && ! isInvalid ) { + + link.inputElement = previewLink.inputElement; + link.outputElement = previewLink.outputElement; + + // check if is an is circular link + + if ( link.outputElement.node.isCircular( link.inputElement.node ) ) { + + return; + + } + + // + + if ( link.inputElement.inputLength > 0 && link.outputElement.outputLength > 0 ) { + + link.inputElement.connect( link.outputElement ); + + } + + } + + } + + }, 'connecting' ); + + }; + + ioDOM.addEventListener( 'mousedown', onConnectEvent, true ); + ioDOM.addEventListener( 'touchstart', onConnectEvent, true ); + + return ioDOM; + + } + +} + +Element.prototype.isElement = true; + +class Input extends Serializer { + + constructor( dom ) { + + super(); + + this.dom = dom; + + this.element = null; + + this.extra = null; + + this.tagColor = null; + + this.events = { + 'change': [], + 'click': [] + }; + + this.addEventListener( 'change', ( ) => { + + dispatchEventList( this.events.change, this ); + + } ); + + this.addEventListener( 'click', ( ) => { + + dispatchEventList( this.events.click, this ); + + } ); + + } + + setExtra( value ) { + + this.extra = value; + + return this; + + } + + getExtra() { + + return this.extra; + + } + + setTagColor( color ) { + + this.tagColor = color; + + this.dom.style[ 'border-left' ] = `2px solid ${color}`; + + return this; + + } + + getTagColor() { + + return this.tagColor; + + } + + setToolTip( text ) { + + const div = document.createElement( 'f-tooltip' ); + div.innerText = text; + + this.dom.append( div ); + + return this; + + } + + onChange( callback ) { + + this.events.change.push( callback ); + + return this; + + } + + onClick( callback ) { + + this.events.click.push( callback ); + + return this; + + } + + setReadOnly( value ) { + + this.dom.readOnly = value; + + return this; + + } + + getReadOnly() { + + return this.dom.readOnly; + + } + + setValue( value, dispatch = true ) { + + this.dom.value = value; + + if ( dispatch ) this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + getValue() { + + return this.dom.value; + + } + + serialize( data ) { + + data.value = this.getValue(); + + } + + deserialize( data ) { + + this.setValue( data.value ); + + } + +} + +Input.prototype.isInput = true; + +class Node extends Serializer { + + constructor() { + + super(); + + const dom = document.createElement( 'f-node' ); + + const onDown = () => { + + const canvas = this.canvas; + + if ( canvas !== null ) { + + canvas.select( this ); + + } + + }; + + dom.addEventListener( 'mousedown', onDown, true ); + dom.addEventListener( 'touchstart', onDown, true ); + + this._onConnect = ( e ) => { + + const { target } = e; + + for ( const element of this.elements ) { + + if ( element !== target ) { + + element.dispatchEvent( new Event( 'nodeConnect' ) ); + + } + + } + + }; + + this._onConnectChildren = ( e ) => { + + const { target } = e; + + for ( const element of this.elements ) { + + if ( element !== target ) { + + element.dispatchEvent( new Event( 'nodeConnectChildren' ) ); + + } + + } + + }; + + this.dom = dom; + + this.style = ''; + + this.canvas = null; + + this.elements = []; + + this.events = { + 'focus': [], + 'blur': [] + }; + + this.setWidth( 300 ).setPosition( 0, 0 ); + + } + + onFocus( callback ) { + + this.events.focus.push( callback ); + + return this; + + } + + onBlur( callback ) { + + this.events.blur.push( callback ); + + return this; + + } + + setStyle( style ) { + + const dom = this.dom; + + if ( this.style ) dom.classList.remove( this.style ); + + if ( style ) dom.classList.add( style ); + + this.style = style; + + return this; + + } + + setPosition( x, y ) { + + const dom = this.dom; + + dom.style.left = toPX( x ); + dom.style.top = toPX( y ); + + return this; + + } + + getPosition() { + + const dom = this.dom; + + return { + x: parseInt( dom.style.left ), + y: parseInt( dom.style.top ) + }; + + } + + setWidth( val ) { + + this.dom.style.width = toPX( val ); + + return this; + + } + + getWidth() { + + return parseInt( this.dom.style.width ); + + } + + add( element ) { + + this.elements.push( element ); + + element.node = this; + element.addEventListener( 'connect', this._onConnect ); + element.addEventListener( 'connectChildren', this._onConnectChildren ); + + this.dom.append( element.dom ); + + return this; + + } + + remove( element ) { + + this.elements.splice( this.elements.indexOf( element ), 1 ); + + element.node = null; + element.removeEventListener( 'connect', this._onConnect ); + element.removeEventListener( 'connectChildren', this._onConnectChildren ); + + this.dom.removeChild( element.dom ); + + return this; + + } + + dispose() { + + const canvas = this.canvas; + + if ( canvas !== null ) canvas.remove( this ); + + for ( const element of this.elements ) { + + element.dispose(); + + } + + this.dispatchEvent( new Event( 'dispose' ) ); + + } + + isCircular( node ) { + + if ( node === this ) return true; + + const links = this.getLinks(); + + for ( const link of links ) { + + if ( link.outputElement.node.isCircular( node ) ) { + + return true; + + } + + } + + return false; + + } + + getLinks() { + + const links = []; + + for ( const element of this.elements ) { + + links.push( ...element.links ); + + } + + return links; + + } + + serialize( data ) { + + const { x, y } = this.getPosition(); + + const elements = []; + + for ( const element of this.elements ) { + + elements.push( element.toJSON( data ).id ); + + } + + data.x = x; + data.y = y; + data.width = this.getWidth(); + data.elements = elements; + + if ( this.style !== '' ) { + + data.style = this.style; + + } + + } + + deserialize( data ) { + + this.setPosition( data.x, data.y ); + this.setWidth( data.width ); + + if ( data.style !== undefined ) { + + this.setStyle( data.style ); + + } + + const elements = this.elements; + + if ( elements.length > 0 ) { + + let index = 0; + + for ( const id of data.elements ) { + + data.objects[ id ] = elements[ index ++ ]; + + } + + } else { + + for ( const id of data.elements ) { + + this.add( data.objects[ id ] ); + + } + + } + + } + +} + +Node.prototype.isNode = true; + +class DraggableElement extends Element { + + constructor( draggable = true ) { + + super( true ); + + this.draggable = draggable; + + const onDrag = ( e ) => { + + e.preventDefault(); + + if ( this.draggable === true ) { + + draggableDOM( this.node.dom ); + + } + + }; + + const { dom } = this; + + dom.addEventListener( 'mousedown', onDrag, true ); + dom.addEventListener( 'touchstart', onDrag, true ); + + } + +} + +class TitleElement extends DraggableElement { + + constructor( title, draggable = true ) { + + super( draggable ); + + const { dom } = this; + + dom.className = 'title'; + + const spanDOM = document.createElement( 'span' ); + spanDOM.innerText = title; + + const iconDOM = document.createElement( 'i' ); + + const toolbarDOM = document.createElement( 'f-toolbar' ); + + this.buttons = []; + + this.spanDOM = spanDOM; + this.iconDOM = iconDOM; + this.toolbarDOM = toolbarDOM; + + dom.append( spanDOM ); + dom.append( iconDOM ); + dom.append( toolbarDOM ); + + } + + setIcon( value ) { + + this.iconDOM.className = value; + + return this; + + } + + getIcon() { + + return this.iconDOM.className; + + } + + setTitle( value ) { + + this.spanDOM.innerText = value; + + return this; + + } + + getTitle() { + + return this.spanDOM.innerText; + + } + + addButton( button ) { + + this.buttons.push( button ); + + this.toolbarDOM.append( button.dom ); + + return this; + + } + + serialize( data ) { + + super.serialize( data ); + + const title = this.getTitle(); + const icon = this.getIcon(); + + data.title = title; + + if ( icon !== '' ) { + + data.icon = icon; + + } + + } + + deserialize( data ) { + + super.deserialize( data ); + + this.setTitle( data.title ); + + if ( data.icon !== undefined ) { + + this.setIcon( data.icon ); + + } + + } + +} + +const drawLine = ( p1x, p1y, p2x, p2y, invert, size, colorA, ctx, colorB = null ) => { + + const dx = p2x - p1x; + const dy = p2y - p1y; + const offset = Math.sqrt( ( dx * dx ) + ( dy * dy ) ) * ( invert ? - .3 : .3 ); + + ctx.beginPath(); + + ctx.moveTo( p1x, p1y ); + + ctx.bezierCurveTo( + p1x + offset, p1y, + p2x - offset, p2y, + p2x, p2y + ); + + if ( colorB !== null && colorA !== colorB ) { + + const gradient = ctx.createLinearGradient( p1x, p1y, p2x, p2y ); + gradient.addColorStop( 0, colorA ); + gradient.addColorStop( 1, colorB ); + + ctx.strokeStyle = gradient; + + } else { + + ctx.strokeStyle = colorA; + + } + + ctx.lineWidth = size; + ctx.stroke(); + +}; + +const colors = [ + '#ff4444', + '#44ff44', + '#4444ff' +]; + +const dropNode = new Node().add( new TitleElement( 'File' ) ).setWidth( 250 ); + +class Canvas extends Serializer { + + constructor() { + + super(); + + const dom = document.createElement( 'f-canvas' ); + const contentDOM = document.createElement( 'f-content' ); + const areaDOM = document.createElement( 'f-area' ); + const dropDOM = document.createElement( 'f-drop' ); + + const canvas = document.createElement( 'canvas' ); + const frontCanvas = document.createElement( 'canvas' ); + + const context = canvas.getContext( '2d' ); + const frontContext = frontCanvas.getContext( '2d' ); + + this.dom = dom; + + this.contentDOM = contentDOM; + this.areaDOM = areaDOM; + this.dropDOM = dropDOM; + + this.canvas = canvas; + this.frontCanvas = frontCanvas; + + this.context = context; + this.frontContext = frontContext; + + this.width = 10000; + this.height = 10000; + + this.clientX = 0; + this.clientY = 0; + + this.relativeClientX = 0; + this.relativeClientY = 0; + + this.zoom = 1; + + this.nodes = []; + + this.selected = null; + + this.updating = false; + + this.droppedItems = []; + + this.events = { + 'drop': [] + }; + + frontCanvas.className = 'front'; + + contentDOM.style.left = toPX( this.centerX ); + contentDOM.style.top = toPX( this.centerY ); + + areaDOM.style.width = `calc( 100% + ${ this.width }px )`; + areaDOM.style.height = `calc( 100% + ${ this.height }px )`; + + dropDOM.innerHTML = 'drop your file'; + + dom.append( dropDOM ); + dom.append( canvas ); + dom.append( frontCanvas ); + dom.append( contentDOM ); + dom.append( areaDOM ); + /* + let zoomTouchData = null; + + const onZoomStart = () => { + + zoomTouchData = null; + + }; +*/ + const onZoom = ( e ) => { + + if ( e.touches ) { + + if ( e.touches.length === 2 ) { + + e.preventDefault(); + + e.stopImmediatePropagation(); + /* + const clientX = ( e.touches[ 0 ].clientX + e.touches[ 1 ].clientX ) / 2; + const clientY = ( e.touches[ 0 ].clientY + e.touches[ 1 ].clientY ) / 2; + + const distance = Math.hypot( + e.touches[ 0 ].clientX - e.touches[ 1 ].clientX, + e.touches[ 0 ].clientY - e.touches[ 1 ].clientY + ); + + if ( zoomTouchData === null ) { + + zoomTouchData = { + distance + }; + + } + + const delta = ( zoomTouchData.distance - distance ); + zoomTouchData.distance = distance; + + let zoom = Math.min( Math.max( this.zoom - delta * .01, .5 ), 1.2 ); + + if ( zoom < .52 ) zoom = .5; + else if ( zoom > .98 ) zoom = 1; + + contentDOM.style.left = toPX( this.centerX / zoom ); + contentDOM.style.top = toPX( this.centerY / zoom ); + contentDOM.style.zoom = this.zoom = zoom; +*/ + + } + + } else { + + e.preventDefault(); + + e.stopImmediatePropagation(); + /* + const delta = e.deltaY / 100; + const zoom = Math.min( Math.max( this.zoom - delta * .1, .5 ), 1 ); + + contentDOM.style.left = toPX( this.centerX / zoom ); + contentDOM.style.top = toPX( this.centerY / zoom ); + contentDOM.style.zoom = this.zoom = zoom; +*/ + + } + + }; + + dom.addEventListener( 'wheel', onZoom ); + dom.addEventListener( 'touchmove', onZoom ); + //dom.addEventListener( 'touchstart', onZoomStart ); + + let dropEnterCount = 0; + + const dragState = ( enter ) => { + + if ( enter ) { + + if ( dropEnterCount ++ === 0 ) { + + this.droppedItems = []; + + dropDOM.classList.add( 'visible' ); + + this.add( dropNode ); + + } + + } else if ( -- dropEnterCount === 0 ) { + + dropDOM.classList.remove( 'visible' ); + + this.remove( dropNode ); + + } + + }; + + dom.addEventListener( 'dragenter', () => { + + dragState( true ); + + } ); + + dom.addEventListener( 'dragleave', () => { + + dragState( false ); + + } ); + + dom.addEventListener( 'dragover', ( e ) => { + + e.preventDefault(); + + const { relativeClientX, relativeClientY } = this; + + const centerNodeX = dropNode.getWidth() / 2; + + dropNode.setPosition( relativeClientX - centerNodeX, relativeClientY - 20 ); + + } ); + + dom.addEventListener( 'drop', ( e ) => { + + e.preventDefault(); + + dragState( false ); + + this.droppedItems = e.dataTransfer.items; + + dispatchEventList( this.events.drop, this ); + + } ); + + draggableDOM( dom, ( data ) => { + + const { delta, isTouch } = data; + + if ( ! isTouch ) { + + if ( data.scrollTop === undefined ) { + + data.scrollLeft = dom.scrollLeft; + data.scrollTop = dom.scrollTop; + + } + + dom.scrollLeft = data.scrollLeft - delta.x; + dom.scrollTop = data.scrollTop - delta.y; + + } + + if ( data.dragging ) { + + dom.classList.add( 'grabbing' ); + + } else { + + dom.classList.remove( 'grabbing' ); + + } + + }, 'dragging-canvas' ); + + this._onMoveEvent = ( e ) => { + + const event = e.touches ? e.touches[ 0 ] : e; + const { zoom, rect } = this; + + this.clientX = event.clientX; + this.clientY = event.clientY; + + this.relativeClientX = ( ( ( dom.scrollLeft - this.centerX ) + event.clientX ) - rect.left ) / zoom; + this.relativeClientY = ( ( ( dom.scrollTop - this.centerY ) + event.clientY ) - rect.top ) / zoom; + + }; + + this._onContentLoaded = () => { + + this.centralize(); + + }; + + this._onUpdate = () => { + + this.update(); + + }; + + this.start(); + + } + + get rect() { + + return this.dom.getBoundingClientRect(); + + } + + get relativeX() { + + return this.dom.scrollLeft - this.centerX; + + } + + get relativeY() { + + return this.dom.scrollTop - this.centerY; + + } + + get centerX() { + + return this.width / 2; + + } + + get centerY() { + + return this.height / 2; + + } + + onDrop( callback ) { + + this.events.drop.push( callback ); + + return this; + + } + + start() { + + this.updating = true; + + document.addEventListener( 'wheel', this._onMoveEvent, true ); + + document.addEventListener( 'mousedown', this._onMoveEvent, true ); + document.addEventListener( 'touchstart', this._onMoveEvent, true ); + + document.addEventListener( 'mousemove', this._onMoveEvent, true ); + document.addEventListener( 'touchmove', this._onMoveEvent, true ); + + document.addEventListener( 'dragover', this._onMoveEvent, true ); + + document.addEventListener( 'DOMContentLoaded', this._onContentLoaded ); + + requestAnimationFrame( this._onUpdate ); + + } + + stop() { + + this.updating = false; + + document.removeEventListener( 'wheel', this._onMoveEvent, true ); + + document.removeEventListener( 'mousedown', this._onMoveEvent, true ); + document.removeEventListener( 'touchstart', this._onMoveEvent, true ); + + document.removeEventListener( 'mousemove', this._onMoveEvent, true ); + document.removeEventListener( 'touchmove', this._onMoveEvent, true ); + + document.removeEventListener( 'dragover', this._onMoveEvent, true ); + + document.removeEventListener( 'DOMContentLoaded', this._onContentLoaded ); + + } + + add( node ) { + + if ( node.canvas === this ) return; + + this.nodes.push( node ); + + node.canvas = this; + + this.contentDOM.append( node.dom ); + + return this; + + } + + remove( node ) { + + if ( node === this.selected ) { + + this.select(); + + } + + this.unlink( node ); + + const nodes = this.nodes; + + nodes.splice( nodes.indexOf( node ), 1 ); + + node.canvas = null; + + this.contentDOM.removeChild( node.dom ); + + node.dispatchEvent( new Event( 'remove' ) ); + + return this; + + } + + clear() { + + const nodes = this.nodes; + + while ( nodes.length > 0 ) { + + this.remove( nodes[ 0 ] ); + + } + + return this; + + } + + unlink( node ) { + + const links = this.getLinks(); + + for ( const link of links ) { + + if ( link.inputElement && link.outputElement ) { + + if ( link.inputElement.node === node ) { + + link.inputElement.connect(); + + } else if ( link.outputElement.node === node ) { + + link.inputElement.connect(); + + } + + } + + } + + } + + getLinks() { + + const links = []; + + for ( const node of this.nodes ) { + + links.push( ...node.getLinks() ); + + } + + return links; + + } + + centralize() { + + this.dom.scroll( this.centerX, this.centerY ); + + return this; + + } + + select( node = null ) { + + if ( node === this.selected ) return; + + const previousNode = this.selected; + + if ( previousNode !== null ) { + + previousNode.dom.classList.remove( 'selected' ); + + this.selected = null; + + dispatchEventList( previousNode.events.blur, previousNode ); + + } + + if ( node !== null ) { + + node.dom.classList.add( 'selected' ); + + this.selected = node; + + dispatchEventList( node.events.focus, node ); + + } + + } + + update() { + + if ( this.updating === false ) return; + + requestAnimationFrame( this._onUpdate ); + + const { dom, zoom, canvas, frontCanvas, frontContext, context } = this; + + const width = window.innerWidth; + const height = window.innerHeight; + + const domRect = this.rect; + + if ( canvas.width !== width || canvas.height !== height ) { + + canvas.width = width; + canvas.height = height; + + frontCanvas.width = width; + frontCanvas.height = height; + + } + + context.clearRect( 0, 0, width, height ); + frontContext.clearRect( 0, 0, width, height ); + + context.globalCompositeOperation = 'lighter'; + frontContext.globalCompositeOperation = 'source-over'; + + const links = this.getLinks(); + + const aPos = { x: 0, y: 0 }; + const bPos = { x: 0, y: 0 }; + + const offsetIORadius = 10; + + let dragging = ''; + + for ( const link of links ) { + + const { lioElement, rioElement } = link; + + let draggingLink = ''; + let length = 0; + + if ( lioElement !== null ) { + + const rect = lioElement.dom.getBoundingClientRect(); + + length = Math.max( length, lioElement.rioLength ); + + aPos.x = rect.x + rect.width; + aPos.y = rect.y + ( rect.height / 2 ); + + } else { + + aPos.x = this.clientX; + aPos.y = this.clientY; + + draggingLink = 'lio'; + + } + + if ( rioElement !== null ) { + + const rect = rioElement.dom.getBoundingClientRect(); + + length = Math.max( length, rioElement.lioLength ); + + bPos.x = rect.x; + bPos.y = rect.y + ( rect.height / 2 ); + + } else { + + bPos.x = this.clientX; + bPos.y = this.clientY; + + draggingLink = 'rio'; + + } + + dragging = dragging || draggingLink; + + const drawContext = draggingLink ? frontContext : context; + + if ( draggingLink || length === 1 ) { + + let colorA = null, + colorB = null; + + if ( draggingLink === 'rio' ) { + + colorA = colorB = lioElement.getRIOColor(); + + aPos.x += offsetIORadius; + bPos.x /= zoom; + bPos.y /= zoom; + + } else if ( draggingLink === 'lio' ) { + + colorA = colorB = rioElement.getLIOColor(); + + bPos.x -= offsetIORadius; + aPos.x /= zoom; + aPos.y /= zoom; + + } else { + + colorA = lioElement.getRIOColor(); + colorB = rioElement.getLIOColor(); + + } + + drawLine( + aPos.x * zoom, aPos.y * zoom, + bPos.x * zoom, bPos.y * zoom, + false, 2, colorA || '#ffffff', drawContext, colorB || '#ffffff' + ); + + } else { + + length = Math.min( length, 4 ); + + for ( let i = 0; i < length; i ++ ) { + + const color = colors[ i ] || '#ffffff'; + + const marginY = 4; + + const rioLength = Math.min( lioElement.rioLength, length ); + const lioLength = Math.min( rioElement.lioLength, length ); + + const colorA = lioElement.getRIOColor() || color; + const colorB = rioElement.getLIOColor() || color; + + const aCenterY = ( ( rioLength * marginY ) * .5 ) - ( marginY / 2 ); + const bCenterY = ( ( lioLength * marginY ) * .5 ) - ( marginY / 2 ); + + const aIndex = Math.min( i, rioLength - 1 ); + const bIndex = Math.min( i, lioLength - 1 ); + + const aPosY = ( aIndex * marginY ) - 1; + const bPosY = ( bIndex * marginY ) - 1; + + drawLine( + aPos.x * zoom, ( ( aPos.y + aPosY ) - aCenterY ) * zoom, + bPos.x * zoom, ( ( bPos.y + bPosY ) - bCenterY ) * zoom, + false, 2, colorA, drawContext, colorB + ); + + } + + } + + } + + context.globalCompositeOperation = 'destination-in'; + + context.fillRect( domRect.x, domRect.y, domRect.width, domRect.height ); + + if ( dragging !== '' ) { + + dom.classList.add( 'dragging-' + dragging ); + + } else { + + dom.classList.remove( 'dragging-lio' ); + dom.classList.remove( 'dragging-rio' ); + + } + + } + + serialize( data ) { + + const nodes = []; + + for ( const node of this.nodes ) { + + nodes.push( node.toJSON( data ).id ); + + } + + data.nodes = nodes; + + } + + deserialize( data ) { + + for ( const id of data.nodes ) { + + this.add( data.objects[ id ] ); + + } + + } + +} + +class ButtonInput extends Input { + + constructor( innterText = '' ) { + + const dom = document.createElement( 'button' ); + + const spanDOM = document.createElement( 'span' ); + dom.append( spanDOM ); + + const iconDOM = document.createElement( 'i' ); + dom.append( iconDOM ); + + super( dom ); + + this.spanDOM = spanDOM; + this.iconDOM = iconDOM; + + spanDOM.innerText = innterText; + + dom.onmouseover = () => { + + this.dispatchEvent( new Event( 'mouseover' ) ); + + }; + + dom.onclick = dom.ontouchstart = ( e ) => { + + e.preventDefault(); + + e.stopPropagation(); + + this.dispatchEvent( new Event( 'click' ) ); + + }; + + } + + setIcon( className ) { + + this.iconDOM.className = className; + + return this; + + } + + setValue( val ) { + + this.spanDOM.innerText = val; + + return this; + + } + + getValue() { + + return this.spanDOM.innerText; + + } + +} + +class ObjectNode extends Node { + + constructor( name, inputLength, callback = null, width = 300 ) { + + super(); + + this.setWidth( width ); + + const title = new TitleElement( name ) + .setObjectCallback( callback ) + .setSerializable( false ) + .setOutput( inputLength ); + + const closeButton = new ButtonInput( Styles.icons.close || '✕' ).onClick( () => { + + this.dispose(); + + } ).setIcon( Styles.icons.close ); + + title.addButton( closeButton ); + + this.add( title ); + + this.title = title; + this.closeButton = closeButton; + + } + + setName( value ) { + + this.title.setTitle( value ); + + return this; + + } + + getName() { + + return this.title.getTitle(); + + } + + setObjectCallback( callback ) { + + this.title.setObjectCallback( callback ); + + return this; + + } + + getObject( callback ) { + + return this.title.getObject( callback ); + + } + + setColor( color ) { + + return this.title.setColor( color ); + + } + + setOutputColor( color ) { + + return this.title.setOutputColor( color ); + + } + + invalidate() { + + this.title.dispatchEvent( new Event( 'connect' ) ); + + } + +} + +const ENTER_KEY$1 = 13; + +class StringInput extends Input { + + constructor( value = '' ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + dom.type = 'text'; + dom.value = value; + dom.spellcheck = false; + dom.autocomplete = 'off'; + + dom.onblur = () => { + + this.dispatchEvent( new Event( 'blur' ) ); + + }; + + dom.onchange = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + dom.onkeyup = ( e ) => { + + if ( e.keyCode === ENTER_KEY$1 ) { + + e.target.blur(); + + } + + e.stopPropagation(); + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + } + +} + +const ENTER_KEY = 13; + +class NumberInput extends Input { + + constructor( value = 0, min = - Infinity, max = Infinity, step = .01 ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + this.min = min; + this.max = max; + this.step = step; + + this.integer = false; + + dom.type = 'text'; + dom.className = 'number'; + dom.value = this._getString( value ); + dom.spellcheck = false; + dom.autocomplete = 'off'; + + dom.ondragstart = dom.oncontextmenu = ( e ) => { + + e.preventDefault(); + + e.stopPropagation(); + + }; + + dom.onfocus = dom.onclick = () => { + + dom.select(); + + }; + + dom.onblur = () => { + + this.dispatchEvent( new Event( 'blur' ) ); + + }; + + dom.onchange = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + dom.onkeydown = ( e ) => { + + if ( e.key.length === 1 && /\d|\./.test( e.key ) !== true ) { + + return false; + + } + + if ( e.keyCode === ENTER_KEY ) { + + e.target.blur(); + + } + + e.stopPropagation(); + + }; + + draggableDOM( dom, ( data ) => { + + const { delta } = data; + + if ( data.value === undefined ) { + + data.value = this.getValue(); + + } + + const diff = delta.x - delta.y; + + const value = data.value + ( diff * this.step ); + + this.dom.value = this._getString( value.toFixed( this.precision ) ); + + this.dispatchEvent( new Event( 'change' ) ); + + } ); + + } + + setStep( step ) { + + this.step = step; + + return this; + + } + + setRange( min, max, step ) { + + this.min = min; + this.max = max; + this.step = step; + + this.dispatchEvent( new Event( 'range' ) ); + + return this.setValue( this.getValue() ); + + } + + get precision() { + + if ( this.integer === true ) return 0; + + const fract = this.step % 1; + + return fract !== 0 ? fract.toString().split( '.' )[ 1 ].length : 1; + + } + + setValue( val, dispatch = true ) { + + return super.setValue( this._getString( val ), dispatch ); + + } + + getValue() { + + return Number( this.dom.value ); + + } + + serialize( data ) { + + const { min, max } = this; + + if ( min !== - Infinity && max !== Infinity ) { + + data.min = this.min; + data.max = this.max; + data.step = this.step; + + } + + super.serialize( data ); + + } + + deserialize( data ) { + + if ( data.min !== undefined ) { + + const { min, max, step } = this; + + this.setRange( min, max, step ); + + } + + super.deserialize( data ); + + } + + _getString( value ) { + + const num = Math.min( Math.max( Number( value ), this.min ), this.max ); + + if ( this.integer === true ) { + + return Math.floor( num ); + + } else { + + return num + ( num % 1 ? '' : '.0' ); + + } + + } + +} + +const getStep = ( min, max ) => { + + const sensibility = .001; + + return ( max - min ) * sensibility; + +}; + +class SliderInput extends Input { + + constructor( value = 0, min = 0, max = 100 ) { + + const dom = document.createElement( 'f-subinputs' ); + super( dom ); + + value = Math.min( Math.max( value, min ), max ); + + const step = getStep( min, max ); + + const rangeDOM = document.createElement( 'input' ); + rangeDOM.type = 'range'; + rangeDOM.min = min; + rangeDOM.max = max; + rangeDOM.step = step; + rangeDOM.value = value; + + const field = new NumberInput( value, min, max, step ); + field.dom.className = 'range-value'; + field.onChange( () => { + + rangeDOM.value = field.getValue(); + + } ); + + field.addEventListener( 'range', () => { + + rangeDOM.min = field.min; + rangeDOM.max = field.max; + rangeDOM.step = field.step; + rangeDOM.value = field.getValue(); + + } ); + + dom.append( rangeDOM ); + dom.append( field.dom ); + + this.rangeDOM = rangeDOM; + this.field = field; + + const updateRangeValue = () => { + + let value = Number( rangeDOM.value ); + + if ( value !== this.max && value + this.step >= this.max ) { + + // fix not end range fraction + + rangeDOM.value = value = this.max; + + } + + this.field.setValue( value ); + + }; + + draggableDOM( rangeDOM, () => { + + updateRangeValue(); + + this.dispatchEvent( new Event( 'change' ) ); + + }, '' ); + + } + + get min() { + + return this.field.min; + + } + + get max() { + + return this.field.max; + + } + + get step() { + + return this.field.step; + + } + + setRange( min, max ) { + + this.field.setRange( min, max, getStep( min, max ) ); + + this.dispatchEvent( new Event( 'range' ) ); + this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + setValue( val, dispatch = true ) { + + this.field.setValue( val ); + this.rangeDOM.value = val; + + if ( dispatch ) this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + getValue() { + + return this.field.getValue(); + + } + + serialize( data ) { + + data.min = this.min; + data.max = this.max; + + super.serialize( data ); + + } + + deserialize( data ) { + + const { min, max } = data; + + this.setRange( min, max ); + + super.deserialize( data ); + + } + +} + +class ColorInput extends Input { + + constructor( value = 0x0099ff ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + dom.type = 'color'; + dom.value = toHex( value ); + + dom.oninput = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + } + + setValue( value, dispatch = true ) { + + return super.setValue( toHex( value ), dispatch ); + + } + + getValue() { + + return parseInt( super.getValue().slice( 1 ), 16 ); + + } + +} + +class TextInput extends Input { + + constructor( innerText = '' ) { + + const dom = document.createElement( 'textarea' ); + super( dom ); + + dom.innerText = innerText; + + } + + setValue( val ) { + + this.dom.innerText = val; + + return this; + + } + + getValue() { + + return this.dom.innerText; + + } + +} + +class LabelElement extends Element { + + constructor( label = '', align = '' ) { + + super(); + + this.labelDOM = document.createElement( 'f-label' ); + this.inputsDOM = document.createElement( 'f-inputs' ); + + const spanDOM = document.createElement( 'span' ); + const iconDOM = document.createElement( 'i' ); + + this.spanDOM = spanDOM; + this.iconDOM = iconDOM; + + this.labelDOM.append( this.spanDOM ); + this.labelDOM.append( this.iconDOM ); + + this.dom.append( this.labelDOM ); + this.dom.append( this.inputsDOM ); + + this.serializeLabel = false; + + this.setLabel( label ); + this.setAlign( align ); + + } + + setIcon( value ) { + + this.iconDOM.className = value; + + return this; + + } + + getIcon() { + + return this.iconDOM.className; + + } + + setAlign( align ) { + + this.labelDOM.className = align; + + } + + setLabel( val ) { + + this.spanDOM.innerText = val; + + } + + getLabel() { + + return this.spanDOM.innerText; + + } + + serialize( data ) { + + super.serialize( data ); + + if ( this.serializeLabel ) { + + const label = this.getLabel(); + const icon = this.getIcon(); + + data.label = label; + + if ( icon !== '' ) { + + data.icon = icon; + + } + + } + + } + + deserialize( data ) { + + super.deserialize( data ); + + if ( this.serializeLabel ) { + + this.setLabel( data.label ); + + if ( data.icon !== undefined ) { + + this.setIcon( data.icon ); + + } + + } + + } + +} + +class PanelNode extends Node { + + constructor( title = 'Panel', align = 'top-right' ) { + + super(); + + const titleElement = new TitleElement( title ); + this.add( titleElement ); + + const collapseButton = new ButtonInput( '🗕' ); + collapseButton.onClick( () => { + + this.setCollapse( ! this.collapsed ); + + } ); + + titleElement.addButton( collapseButton ); + + this.collapseButton = collapseButton; + this.titleElement = titleElement; + this.align = align; + this.collapsed = false; + + this.setAlign( align ); + this.setStyle( 'rouded' ); + + } + + setCollapse( value ) { + + const cssClass = 'closed'; + + this.dom.classList.remove( cssClass ); + + this.collapsed = value; + + this.collapseButton.value = value ? '🗖' : '🗕'; + + if ( value === true ) { + + this.dom.classList.add( cssClass ); + + } + + return this; + + } + + setAlign( align ) { + + if ( this.align ) this.dom.classList.remove( this.align ); + this.dom.classList.add( align ); + + this.align = align; + + return this; + + } + + addInput( inputClass, object, property, ...params ) { + + const value = object[ property ]; + + const input = new inputClass( value, ...params ); + input.onChange( () => { + + object[ property ] = input.value; + + } ); + + this.add( new LabelElement( property ).add( input ) ); + + return input; + + } + + addSlider( object, property, min, max ) { + + return this.addInput( SliderInput, object, property, min, max ); + + } + + addNumber( object, property ) { + + return this.addInput( NumberInput, object, property ); + + } + + addColor( object, property ) { + + return this.addInput( ColorInput, object, property ); + + } + + addString( object, property ) { + + return this.addInput( StringInput, object, property ); + + } + + addText( object, property ) { + + const input = this.addInput( TextInput, object, property ); + input.element.setHeight( 70 ); + + return input; + + } + + addButton( name ) { + + const input = new ButtonInput( name ); + + this.add( new Element().setHeight( 34 ).add( input ) ); + + return input; + + } + +} + +class Menu extends EventTarget { + + constructor( className ) { + + super(); + + const dom = document.createElement( 'f-menu' ); + dom.className = className + ' hidden'; + + const listDOM = document.createElement( 'f-list' ); + + dom.append( listDOM ); + + this.dom = dom; + this.listDOM = listDOM; + + this.visible = false; + + this.subMenus = new WeakMap(); + this.domButtons = new WeakMap(); + + this.buttons = []; + + this.events = {}; + + } + + onContext( callback ) { + + this.events.context.push( callback ); + + return this; + + } + + show() { + + this.dom.classList.remove( 'hidden' ); + + this.visible = true; + + this.dispatchEvent( new Event( 'show' ) ); + + return this; + + } + + hide() { + + this.dom.classList.add( 'hidden' ); + + this.dispatchEvent( new Event( 'hide' ) ); + + this.visible = false; + + } + + add( button, submenu = null ) { + + const liDOM = document.createElement( 'f-item' ); + + if ( submenu !== null ) { + + liDOM.classList.add( 'submenu' ); + + liDOM.append( submenu.dom ); + + this.subMenus.set( button, submenu ); + + button.dom.addEventListener( 'mouseover', () => submenu.show() ); + button.dom.addEventListener( 'mouseout', () => submenu.hide() ); + + } + + liDOM.append( button.dom ); + + this.buttons.push( button ); + + this.listDOM.append( liDOM ); + + this.domButtons.set( button, liDOM ); + + return this; + + } + + clear() { + + this.buttons = []; + + this.subMenus = new WeakMap(); + this.domButtons = new WeakMap(); + + while ( this.listDOM.firstChild ) { + + this.listDOM.firstChild.remove(); + + } + + } + +} + +let lastContext = null; + +const onCloseLastContext = ( e ) => { + + if ( lastContext && lastContext.visible === true && e.target.closest( 'f-menu.context' ) === null ) { + + lastContext.hide(); + + } + +}; + +document.body.addEventListener( 'mousedown', onCloseLastContext, true ); +document.body.addEventListener( 'touchstart', onCloseLastContext, true ); + +class ContextMenu extends Menu { + + constructor( target = null ) { + + super( 'context', target ); + + this.events.context = []; + + this._lastButtonClick = null; + + this._onButtonClick = ( e = null ) => { + + const button = e ? e.target : null; + + if ( this._lastButtonClick ) { + + this._lastButtonClick.dom.parentElement.classList.remove( 'active' ); + + } + + this._lastButtonClick = button; + + if ( button ) { + + if ( this.subMenus.has( button ) ) { + + this.subMenus.get( button )._onButtonClick(); + + } + + button.dom.parentElement.classList.add( 'active' ); + + } + + }; + + this._onButtonMouseOver = ( e ) => { + + const button = e.target; + + if ( this.subMenus.has( button ) && this._lastButtonClick !== button ) { + + this._onButtonClick(); + + } + + }; + + this.addEventListener( 'context', ( ) => { + + dispatchEventList( this.events.context, this ); + + } ); + + this.setTarget( target ); + + } + + openFrom( dom ) { + + const rect = dom.getBoundingClientRect(); + + return this.open( rect.x + ( rect.width / 2 ), rect.y + ( rect.height / 2 ) ); + + } + + open( x = pointer.x, y = pointer.y ) { + + if ( lastContext !== null ) { + + lastContext.hide(); + + } + + lastContext = this; + + this.setPosition( x, y ); + + document.body.append( this.dom ); + + return this.show(); + + } + + setPosition( x, y ) { + + const dom = this.dom; + + dom.style.left = toPX( x ); + dom.style.top = toPX( y ); + + return this; + + } + + setTarget( target = null ) { + + if ( target !== null ) { + + const onContextMenu = ( e ) => { + + e.preventDefault(); + + if ( e.pointerType !== 'mouse' || ( e.pageX === 0 && e.pageY === 0 ) ) return; + + this.dispatchEvent( new Event( 'context' ) ); + + this.open(); + + }; + + this.target = target; + + target.addEventListener( 'contextmenu', onContextMenu, false ); + + } + + return this; + + } + + show() { + + if ( ! this.opened ) { + + this.dom.style.left = ''; + this.dom.style.transform = ''; + + } + + const domRect = this.dom.getBoundingClientRect(); + + let offsetX = Math.min( window.innerWidth - ( domRect.x + domRect.width + 10 ), 0 ); + let offsetY = Math.min( window.innerHeight - ( domRect.y + domRect.height + 10 ), 0 ); + + if ( this.opened ) { + + if ( offsetX < 0 ) offsetX = - domRect.width; + if ( offsetY < 0 ) offsetY = - domRect.height; + + this.setPosition( domRect.x + offsetX, domRect.y + offsetY ); + + } else { + + // flip submenus + + if ( offsetX < 0 ) this.dom.style.left = '-100%'; + if ( offsetY < 0 ) this.dom.style.transform = 'translateY( calc( 32px - 100% ) )'; + + } + + return super.show(); + + } + + hide() { + + if ( this.opened ) { + + lastContext = null; + + } + + return super.hide(); + + } + + add( button, submenu = null ) { + + button.addEventListener( 'click', this._onButtonClick ); + button.addEventListener( 'mouseover', this._onButtonMouseOver ); + + return super.add( button, submenu ); + + } + + get opened() { + + return lastContext === this; + + } + +} + +class CircleMenu extends Menu { + + constructor( target = null ) { + + super( 'circle', target ); + + } + +} + +class Tips extends EventTarget { + + constructor() { + + super(); + + const dom = document.createElement( 'f-tips' ); + + this.dom = dom; + + this.time = 0; + this.duration = 3000; + + } + + message( str ) { + + return this.tip( str ); + + } + + error( str ) { + + return this.tip( str, 'error' ); + + } + + tip( html, className = '' ) { + + const dom = document.createElement( 'f-tip' ); + dom.className = className; + dom.innerHTML = html; + + this.dom.prepend( dom ); + + //requestAnimationFrame( () => dom.style.opacity = 1 ); + + this.time = Math.min( this.time + this.duration, this.duration ); + + setTimeout( () => { + + this.time -= this.duration; + + dom.style.opacity = 0; + + setTimeout( () => dom.remove(), 250 ); + + }, this.time ); + + return this; + + } + +} + +const filterString = ( str ) => { + + return str.trim().toLowerCase().replace( /\s\s+/g, ' ' ); + +}; + +class Search extends Menu { + + constructor() { + + super( 'search' ); + + this.events.submit = []; + this.events.filter = []; + + const inputDOM = document.createElement( 'input' ); + inputDOM.placeholder = 'Type here'; + + let filter = true; + let filterNeedUpdate = true; + + inputDOM.addEventListener( 'focusout', () => { + + filterNeedUpdate = true; + + this.setValue( '' ); + + } ); + + inputDOM.onkeydown = ( e ) => { + + const keyCode = e.keyCode; + + if ( keyCode === 38 ) { + + const index = this.filteredIndex; + + if ( this.forceAutoComplete ) { + + this.filteredIndex = index !== null ? ( index + 1 ) % ( this.filtered.length || 1 ) : 0; + + } else { + + this.filteredIndex = index !== null ? Math.min( index + 1, this.filtered.length - 1 ) : 0; + + } + + e.preventDefault(); + + filter = false; + + } else if ( keyCode === 40 ) { + + const index = this.filteredIndex; + + if ( this.forceAutoComplete ) { + + this.filteredIndex = index - 1; + + if ( this.filteredIndex === null ) this.filteredIndex = this.filtered.length - 1; + + } else { + + this.filteredIndex = index !== null ? index - 1 : null; + + } + + e.preventDefault(); + + filter = false; + + } else if ( keyCode === 13 ) { + + this.value = this.currentFiltered ? this.currentFiltered.button.getValue() : inputDOM.value; + + this.submit(); + + e.preventDefault(); + + filter = false; + + } else { + + filter = true; + + } + + }; + + inputDOM.onkeyup = () => { + + if ( filter ) { + + if ( filterNeedUpdate ) { + + this.dispatchEvent( new Event( 'filter' ) ); + + filterNeedUpdate = false; + + } + + this.filter( inputDOM.value ); + + } + + }; + + this.filtered = []; + this.currentFiltered = null; + + this.value = ''; + + this.forceAutoComplete = false; + + this.dom.append( inputDOM ); + + this.inputDOM = inputDOM; + + this.addEventListener( 'filter', ( ) => { + + dispatchEventList( this.events.filter, this ); + + } ); + + this.addEventListener( 'submit', ( ) => { + + dispatchEventList( this.events.submit, this ); + + } ); + + } + + submit() { + + this.dispatchEvent( new Event( 'submit' ) ); + + return this.setValue( '' ); + + } + + setValue( value ) { + + this.inputDOM.value = value; + + this.filter( value ); + + return this; + + } + + getValue() { + + return this.value; + + } + + onFilter( callback ) { + + this.events.filter.push( callback ); + + return this; + + } + + onSubmit( callback ) { + + this.events.submit.push( callback ); + + return this; + + } + + getFilterByButton( button ) { + + for ( const filter of this.filtered ) { + + if ( filter.button === button ) { + + return filter; + + } + + } + + return null; + + } + + add( button ) { + + super.add( button ); + + const onDown = () => { + + const filter = this.getFilterByButton( button ); + + this.filteredIndex = this.filtered.indexOf( filter ); + this.value = button.getValue(); + + this.submit(); + + }; + + button.dom.addEventListener( 'mousedown', onDown ); + button.dom.addEventListener( 'touchstart', onDown ); + + this.domButtons.get( button ).remove(); + + return this; + + } + + set filteredIndex( index ) { + + if ( this.currentFiltered ) { + + const buttonDOM = this.domButtons.get( this.currentFiltered.button ); + + buttonDOM.classList.remove( 'active' ); + + this.currentFiltered = null; + + } + + const filteredItem = this.filtered[ index ]; + + if ( filteredItem ) { + + const buttonDOM = this.domButtons.get( filteredItem.button ); + + buttonDOM.classList.add( 'active' ); + + this.currentFiltered = filteredItem; + + } + + this.updateFilter(); + + } + + get filteredIndex() { + + return this.currentFiltered ? this.filtered.indexOf( this.currentFiltered ) : null; + + } + + filter( text ) { + + text = filterString( text ); + + const filtered = []; + + for ( const button of this.buttons ) { + + const buttonDOM = this.domButtons.get( button ); + + buttonDOM.remove(); + + const label = filterString( button.getValue() ); + + if ( text && label.includes( text ) === true ) { + + const score = text.length / label.length; + + filtered.push( { + button, + score + } ); + + } + + } + + filtered.sort( ( a, b ) => b.score - a.score ); + + this.filtered = filtered; + this.filteredIndex = this.forceAutoComplete ? 0 : null; + + } + + updateFilter() { + + const filteredIndex = Math.min( this.filteredIndex, this.filteredIndex - 3 ); + + for ( let i = 0; i < this.filtered.length; i ++ ) { + + const button = this.filtered[ i ].button; + const buttonDOM = this.domButtons.get( button ); + + buttonDOM.remove(); + + if ( i >= filteredIndex ) { + + this.listDOM.append( buttonDOM ); + + } + + } + + } + +} + +class SelectInput extends Input { + + constructor( options = [], value = null ) { + + const dom = document.createElement( 'select' ); + super( dom ); + + dom.onchange = () => { + + this.dispatchEvent( new Event( 'change' ) ); + + }; + + dom.onmousedown = dom.ontouchstart = () => { + + this.dispatchEvent( new Event( 'click' ) ); + + }; + + this.setOptions( options, value ); + + } + + setOptions( options, value = null ) { + + const dom = this.dom; + const defaultValue = dom.value; + + let containsDefaultValue = false; + + this.options = options; + dom.innerHTML = ''; + + for ( let index = 0; index < options.length; index ++ ) { + + let opt = options[ index ]; + + if ( typeof opt === 'string' ) { + + opt = { name: opt, value: index }; + + } + + const option = document.createElement( 'option' ); + option.innerText = opt.name; + option.value = opt.value; + + if ( containsDefaultValue === false && defaultValue === opt.value ) { + + containsDefaultValue = true; + + } + + dom.append( option ); + + } + + dom.value = value !== null ? value : containsDefaultValue ? defaultValue : ''; + + return this; + + } + + getOptions() { + + return this._options; + + } + + serialize( data ) { + + data.options = [ ...this.options ]; + + super.serialize( data ); + + } + + deserialize( data ) { + + const currentOptions = this.options; + + if ( currentOptions.length === 0 ) { + + this.setOptions( data.options ); + + } + + super.deserialize( data ); + + } + +} + +class ToggleInput extends Input { + + constructor( value = false ) { + + const dom = document.createElement( 'input' ); + super( dom ); + + dom.type = 'checkbox'; + dom.className = 'toggle'; + dom.checked = value; + + dom.onclick = () => this.dispatchEvent( new Event( 'click' ) ); + dom.onchange = () => this.dispatchEvent( new Event( 'change' ) ); + + } + + setValue( val ) { + + this.dom.checked = val; + + this.dispatchEvent( new Event( 'change' ) ); + + return this; + + } + + getValue() { + + return this.dom.checked; + + } + +} + +var Flow = /*#__PURE__*/Object.freeze( { + __proto__: null, + Element: Element, + Input: Input, + Node: Node, + Canvas: Canvas, + Serializer: Serializer, + Styles: Styles, + ObjectNode: ObjectNode, + PanelNode: PanelNode, + Menu: Menu, + ContextMenu: ContextMenu, + CircleMenu: CircleMenu, + Tips: Tips, + Search: Search, + DraggableElement: DraggableElement, + LabelElement: LabelElement, + TitleElement: TitleElement, + ButtonInput: ButtonInput, + ColorInput: ColorInput, + NumberInput: NumberInput, + SelectInput: SelectInput, + SliderInput: SliderInput, + StringInput: StringInput, + TextInput: TextInput, + ToggleInput: ToggleInput +} ); + +class Loader extends EventTarget { + + constructor( parseType = Loader.DEFAULT ) { + + super(); + + this.parseType = parseType; + + this.events = { + 'load': [] + }; + + } + + setParseType( type ) { + + this.parseType = type; + + return this; + + } + + getParseType() { + + return this.parseType; + + } + + onLoad( callback ) { + + this.events.load.push( callback ); + + return this; + + } + + async load( url, lib = null ) { + + return await fetch( url ) + .then( response => response.json() ) + .then( result => { + + this.data = this.parse( result, lib ); + + dispatchEventList( this.events.load, this ); + + return this.data; + + } ) + .catch( err => { + + console.error( 'Loader:', err ); + + } ); + + } + + parse( json, lib = null ) { + + json = this._parseObjects( json, lib ); + + const parseType = this.parseType; + + if ( parseType === Loader.DEFAULT ) { + + const flowObj = new Flow[ json.type ](); + + if ( flowObj.getSerializable() ) { + + flowObj.deserialize( json ); + + } + + return flowObj; + + } else if ( parseType === Loader.OBJECTS ) { + + return json; + + } + + } + + _parseObjects( json, lib = null ) { + + json = { ...json }; + + const objects = {}; + + for ( const id in json.objects ) { + + const obj = json.objects[ id ]; + obj.objects = objects; + + const Class = lib && lib[ obj.type ] ? lib[ obj.type ] : Flow[ obj.type ]; + + if ( ! Class ) { + + console.error( `Class "${ obj.type }" not found!` ); + + } + + objects[ id ] = new Class(); + + } + + const ref = new WeakMap(); + + const deserializePass = ( prop = null ) => { + + for ( const id in json.objects ) { + + const newObject = objects[ id ]; + + if ( ref.has( newObject ) === false && ( prop === null || newObject[ prop ] === true ) ) { + + ref.set( newObject, true ); + + if ( newObject.getSerializable() ) { + + newObject.deserialize( json.objects[ id ] ); + + } + + } + + } + + }; + + deserializePass( 'isNode' ); + deserializePass( 'isElement' ); + deserializePass( 'isInput' ); + deserializePass(); + + json.objects = objects; + + return json; + + } + +} + +Loader.DEFAULT = 'default'; +Loader.OBJECTS = 'objects'; + +export { ButtonInput, Canvas, CircleMenu, ColorInput, ContextMenu, DraggableElement, Element, Input, LabelElement, Loader, Menu, Node, NumberInput, ObjectNode, PanelNode, REVISION, Search, SelectInput, Serializer, SliderInput, StringInput, Styles, TextInput, Tips, TitleElement, ToggleInput, Utils }; diff --git a/jsm/libs/ktx-parse.module.js b/jsm/libs/ktx-parse.module.js new file mode 100644 index 0000000..c644054 --- /dev/null +++ b/jsm/libs/ktx-parse.module.js @@ -0,0 +1 @@ +const t=new Uint8Array([0]),e=[171,75,84,88,32,50,48,187,13,10,26,10];var n,i,s,a,r,o,l,f;!function(t){t[t.NONE=0]="NONE",t[t.BASISLZ=1]="BASISLZ",t[t.ZSTD=2]="ZSTD",t[t.ZLIB=3]="ZLIB"}(n||(n={})),function(t){t[t.BASICFORMAT=0]="BASICFORMAT"}(i||(i={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.ETC1S=163]="ETC1S",t[t.UASTC=166]="UASTC"}(s||(s={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.SRGB=1]="SRGB"}(a||(a={})),function(t){t[t.UNSPECIFIED=0]="UNSPECIFIED",t[t.LINEAR=1]="LINEAR",t[t.SRGB=2]="SRGB",t[t.ITU=3]="ITU",t[t.NTSC=4]="NTSC",t[t.SLOG=5]="SLOG",t[t.SLOG2=6]="SLOG2"}(r||(r={})),function(t){t[t.ALPHA_STRAIGHT=0]="ALPHA_STRAIGHT",t[t.ALPHA_PREMULTIPLIED=1]="ALPHA_PREMULTIPLIED"}(o||(o={})),function(t){t[t.RGB=0]="RGB",t[t.RRR=3]="RRR",t[t.GGG=4]="GGG",t[t.AAA=15]="AAA"}(l||(l={})),function(t){t[t.RGB=0]="RGB",t[t.RGBA=3]="RGBA",t[t.RRR=4]="RRR",t[t.RRRG=5]="RRRG"}(f||(f={}));class U{constructor(){this.vkFormat=0,this.typeSize=1,this.pixelWidth=0,this.pixelHeight=0,this.pixelDepth=0,this.layerCount=0,this.faceCount=1,this.supercompressionScheme=n.NONE,this.levels=[],this.dataFormatDescriptor=[{vendorId:0,descriptorType:i.BASICFORMAT,versionNumber:2,descriptorBlockSize:40,colorModel:s.UNSPECIFIED,colorPrimaries:a.SRGB,transferFunction:a.SRGB,flags:o.ALPHA_STRAIGHT,texelBlockDimension:{x:4,y:4,z:1,w:1},bytesPlane:[],samples:[]}],this.keyValue={},this.globalData=null}}class c{constructor(t,e,n,i){this._dataView=new DataView(t.buffer,t.byteOffset+e,n),this._littleEndian=i,this._offset=0}_nextUint8(){const t=this._dataView.getUint8(this._offset);return this._offset+=1,t}_nextUint16(){const t=this._dataView.getUint16(this._offset,this._littleEndian);return this._offset+=2,t}_nextUint32(){const t=this._dataView.getUint32(this._offset,this._littleEndian);return this._offset+=4,t}_nextUint64(){const t=this._dataView.getUint32(this._offset,this._littleEndian)+2**32*this._dataView.getUint32(this._offset+4,this._littleEndian);return this._offset+=8,t}_skip(t){return this._offset+=t,this}_scan(t,e=0){const n=this._offset;let i=0;for(;this._dataView.getUint8(this._offset)!==e&&i{this.setValue(this.$input.checked),this._callOnFinishChange()}),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.checked=this.getValue(),this}}function e(t){let i,e;return(i=t.match(/(#|0x)?([a-f0-9]{6})/i))?e=i[2]:(i=t.match(/rgb\(\s*(\d*)\s*,\s*(\d*)\s*,\s*(\d*)\s*\)/))?e=parseInt(i[1]).toString(16).padStart(2,0)+parseInt(i[2]).toString(16).padStart(2,0)+parseInt(i[3]).toString(16).padStart(2,0):(i=t.match(/^#?([a-f0-9])([a-f0-9])([a-f0-9])$/i))&&(e=i[1]+i[1]+i[2]+i[2]+i[3]+i[3]),!!e&&"#"+e}const s={isPrimitive:!0,match:t=>"string"==typeof t,fromHexString:e,toHexString:e},n={isPrimitive:!0,match:t=>"number"==typeof t,fromHexString:t=>parseInt(t.substring(1),16),toHexString:t=>"#"+t.toString(16).padStart(6,0)},r={isPrimitive:!1,match:Array.isArray,fromHexString(t,i,e=1){const s=n.fromHexString(t);i[0]=(s>>16&255)/255*e,i[1]=(s>>8&255)/255*e,i[2]=(255&s)/255*e},toHexString:([t,i,e],s=1)=>n.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},l={isPrimitive:!1,match:t=>Object(t)===t,fromHexString(t,i,e=1){const s=n.fromHexString(t);i.r=(s>>16&255)/255*e,i.g=(s>>8&255)/255*e,i.b=(255&s)/255*e},toHexString:({r:t,g:i,b:e},s=1)=>n.toHexString(t*(s=255/s)<<16^i*s<<8^e*s<<0)},o=[s,n,r,l];class a extends t{constructor(t,i,s,n){var r;super(t,i,s,"color"),this.$input=document.createElement("input"),this.$input.setAttribute("type","color"),this.$input.setAttribute("tabindex",-1),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$text=document.createElement("input"),this.$text.setAttribute("type","text"),this.$text.setAttribute("spellcheck","false"),this.$text.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this.$display.appendChild(this.$input),this.$widget.appendChild(this.$display),this.$widget.appendChild(this.$text),this._format=(r=this.initialValue,o.find(t=>t.match(r))),this._rgbScale=n,this._initialValueHexString=this.save(),this._textFocused=!1,this.$input.addEventListener("input",()=>{this._setValueFromHexString(this.$input.value)}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$text.addEventListener("input",()=>{const t=e(this.$text.value);t&&this._setValueFromHexString(t)}),this.$text.addEventListener("focus",()=>{this._textFocused=!0,this.$text.select()}),this.$text.addEventListener("blur",()=>{this._textFocused=!1,this.updateDisplay(),this._callOnFinishChange()}),this.$disable=this.$text,this.updateDisplay()}reset(){return this._setValueFromHexString(this._initialValueHexString),this}_setValueFromHexString(t){if(this._format.isPrimitive){const i=this._format.fromHexString(t);this.setValue(i)}else this._format.fromHexString(t,this.getValue(),this._rgbScale),this._callOnChange(),this.updateDisplay()}save(){return this._format.toHexString(this.getValue(),this._rgbScale)}load(t){return this._setValueFromHexString(t),this._callOnFinishChange(),this}updateDisplay(){return this.$input.value=this._format.toHexString(this.getValue(),this._rgbScale),this._textFocused||(this.$text.value=this.$input.value.substring(1)),this.$display.style.backgroundColor=this.$input.value,this}}class h extends t{constructor(t,i,e){super(t,i,e,"function"),this.$button=document.createElement("button"),this.$button.appendChild(this.$name),this.$widget.appendChild(this.$button),this.$button.addEventListener("click",t=>{t.preventDefault(),this.getValue().call(this.object)}),this.$button.addEventListener("touchstart",()=>{}),this.$disable=this.$button}}class d extends t{constructor(t,i,e,s,n,r){super(t,i,e,"number"),this._initInput(),this.min(s),this.max(n);const l=void 0!==r;this.step(l?r:this._getImplicitStep(),l),this.updateDisplay()}min(t){return this._min=t,this._onUpdateMinMax(),this}max(t){return this._max=t,this._onUpdateMinMax(),this}step(t,i=!0){return this._step=t,this._stepExplicit=i,this}updateDisplay(){const t=this.getValue();if(this._hasSlider){let i=(t-this._min)/(this._max-this._min);i=Math.max(0,Math.min(i,1)),this.$fill.style.width=100*i+"%"}return this._inputFocused||(this.$input.value=t),this}_initInput(){this.$input=document.createElement("input"),this.$input.setAttribute("type","number"),this.$input.setAttribute("step","any"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$widget.appendChild(this.$input),this.$disable=this.$input;const t=t=>{const i=parseFloat(this.$input.value);isNaN(i)||(this._snapClampSetValue(i+t),this.$input.value=this.getValue())};let i,e,s,n,r,l=!1;const o=t=>{if(l){const s=t.clientX-i,n=t.clientY-e;Math.abs(n)>5?(t.preventDefault(),this.$input.blur(),l=!1,this._setDraggingStyle(!0,"vertical")):Math.abs(s)>5&&a()}if(!l){const i=t.clientY-s;r-=i*this._step*this._arrowKeyMultiplier(t),n+r>this._max?r=this._max-n:n+r{this._setDraggingStyle(!1,"vertical"),this._callOnFinishChange(),window.removeEventListener("mousemove",o),window.removeEventListener("mouseup",a)};this.$input.addEventListener("input",()=>{const t=parseFloat(this.$input.value);isNaN(t)||this.setValue(this._clamp(t))}),this.$input.addEventListener("keydown",i=>{"Enter"===i.code&&this.$input.blur(),"ArrowUp"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i))),"ArrowDown"===i.code&&(i.preventDefault(),t(this._step*this._arrowKeyMultiplier(i)*-1))}),this.$input.addEventListener("wheel",i=>{this._inputFocused&&(i.preventDefault(),t(this._step*this._normalizeMouseWheel(i)))}),this.$input.addEventListener("mousedown",t=>{i=t.clientX,e=s=t.clientY,l=!0,n=this.getValue(),r=0,window.addEventListener("mousemove",o),window.addEventListener("mouseup",a)}),this.$input.addEventListener("focus",()=>{this._inputFocused=!0}),this.$input.addEventListener("blur",()=>{this._inputFocused=!1,this.updateDisplay(),this._callOnFinishChange()})}_initSlider(){this._hasSlider=!0,this.$slider=document.createElement("div"),this.$slider.classList.add("slider"),this.$fill=document.createElement("div"),this.$fill.classList.add("fill"),this.$slider.appendChild(this.$fill),this.$widget.insertBefore(this.$slider,this.$input),this.domElement.classList.add("hasSlider");const t=t=>{const i=this.$slider.getBoundingClientRect();let e=(s=t,n=i.left,r=i.right,l=this._min,o=this._max,(s-n)/(r-n)*(o-l)+l);var s,n,r,l,o;this._snapClampSetValue(e)},i=i=>{t(i.clientX)},e=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("mousemove",i),window.removeEventListener("mouseup",e)};let s,n,r=!1;const l=i=>{i.preventDefault(),this._setDraggingStyle(!0),t(i.touches[0].clientX),r=!1},o=i=>{if(r){const t=i.touches[0].clientX-s,e=i.touches[0].clientY-n;Math.abs(t)>Math.abs(e)?l(i):(window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a))}else i.preventDefault(),t(i.touches[0].clientX)},a=()=>{this._callOnFinishChange(),this._setDraggingStyle(!1),window.removeEventListener("touchmove",o),window.removeEventListener("touchend",a)},h=this._callOnFinishChange.bind(this);let d;this.$slider.addEventListener("mousedown",s=>{this._setDraggingStyle(!0),t(s.clientX),window.addEventListener("mousemove",i),window.addEventListener("mouseup",e)}),this.$slider.addEventListener("touchstart",t=>{t.touches.length>1||(this._hasScrollBar?(s=t.touches[0].clientX,n=t.touches[0].clientY,r=!0):l(t),window.addEventListener("touchmove",o),window.addEventListener("touchend",a))}),this.$slider.addEventListener("wheel",t=>{if(Math.abs(t.deltaX)this._max&&(t=this._max),t}_snapClampSetValue(t){this.setValue(this._clamp(this._snap(t)))}get _hasScrollBar(){const t=this.parent.root.$children;return t.scrollHeight>t.clientHeight}get _hasMin(){return void 0!==this._min}get _hasMax(){return void 0!==this._max}}class c extends t{constructor(t,i,e,s){super(t,i,e,"option"),this.$select=document.createElement("select"),this.$select.setAttribute("aria-labelledby",this.$name.id),this.$display=document.createElement("div"),this.$display.classList.add("display"),this._values=Array.isArray(s)?s:Object.values(s),this._names=Array.isArray(s)?s:Object.keys(s),this._names.forEach(t=>{const i=document.createElement("option");i.innerHTML=t,this.$select.appendChild(i)}),this.$select.addEventListener("change",()=>{this.setValue(this._values[this.$select.selectedIndex]),this._callOnFinishChange()}),this.$select.addEventListener("focus",()=>{this.$display.classList.add("focus")}),this.$select.addEventListener("blur",()=>{this.$display.classList.remove("focus")}),this.$widget.appendChild(this.$select),this.$widget.appendChild(this.$display),this.$disable=this.$select,this.updateDisplay()}updateDisplay(){const t=this.getValue(),i=this._values.indexOf(t);return this.$select.selectedIndex=i,this.$display.innerHTML=-1===i?t:this._names[i],this}}class u extends t{constructor(t,i,e){super(t,i,e,"string"),this.$input=document.createElement("input"),this.$input.setAttribute("type","text"),this.$input.setAttribute("aria-labelledby",this.$name.id),this.$input.addEventListener("input",()=>{this.setValue(this.$input.value)}),this.$input.addEventListener("keydown",t=>{"Enter"===t.code&&this.$input.blur()}),this.$input.addEventListener("blur",()=>{this._callOnFinishChange()}),this.$widget.appendChild(this.$input),this.$disable=this.$input,this.updateDisplay()}updateDisplay(){return this.$input.value=this.getValue(),this}}let p=!1;class g{constructor({parent:t,autoPlace:i=void 0===t,container:e,width:s,title:n="Controls",injectStyles:r=!0,touchStyles:l=!0}={}){if(this.parent=t,this.root=t?t.root:this,this.children=[],this.controllers=[],this.folders=[],this._closed=!1,this._hidden=!1,this.domElement=document.createElement("div"),this.domElement.classList.add("lil-gui"),this.$title=document.createElement("div"),this.$title.classList.add("title"),this.$title.setAttribute("role","button"),this.$title.setAttribute("aria-expanded",!0),this.$title.setAttribute("tabindex",0),this.$title.addEventListener("click",()=>this.openAnimated(this._closed)),this.$title.addEventListener("keydown",t=>{"Enter"!==t.code&&"Space"!==t.code||(t.preventDefault(),this.$title.click())}),this.$title.addEventListener("touchstart",()=>{}),this.$children=document.createElement("div"),this.$children.classList.add("children"),this.domElement.appendChild(this.$title),this.domElement.appendChild(this.$children),this.title(n),l&&this.domElement.classList.add("allow-touch-styles"),this.parent)return this.parent.children.push(this),this.parent.folders.push(this),void this.parent.$children.appendChild(this.domElement);this.domElement.classList.add("root"),!p&&r&&(!function(t){const i=document.createElement("style");i.innerHTML=t;const e=document.querySelector("head link[rel=stylesheet], head style");e?document.head.insertBefore(i,e):document.head.appendChild(i)}('.lil-gui{--background-color:#1f1f1f;--text-color:#ebebeb;--title-background-color:#111;--title-text-color:#ebebeb;--widget-color:#424242;--hover-color:#4f4f4f;--focus-color:#595959;--number-color:#2cc9ff;--string-color:#a2db3c;--font-size:11px;--input-font-size:11px;--font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;--font-family-mono:Menlo,Monaco,Consolas,"Droid Sans Mono",monospace;--padding:4px;--spacing:4px;--widget-height:20px;--name-width:45%;--slider-knob-width:2px;--slider-input-width:27%;--color-input-width:27%;--slider-input-min-width:45px;--color-input-min-width:45px;--folder-indent:7px;--widget-padding:0 0 0 3px;--widget-border-radius:2px;--checkbox-size:calc(var(--widget-height)*0.75);--scrollbar-width:5px;background-color:var(--background-color);color:var(--text-color);font-family:var(--font-family);font-size:var(--font-size);font-style:normal;font-weight:400;line-height:1;text-align:left;touch-action:manipulation;user-select:none;-webkit-user-select:none}.lil-gui,.lil-gui *{box-sizing:border-box;margin:0;padding:0}.lil-gui.root{display:flex;flex-direction:column;width:var(--width,245px)}.lil-gui.root>.title{background:var(--title-background-color);color:var(--title-text-color)}.lil-gui.root>.children{overflow-x:hidden;overflow-y:auto}.lil-gui.root>.children::-webkit-scrollbar{background:var(--background-color);height:var(--scrollbar-width);width:var(--scrollbar-width)}.lil-gui.root>.children::-webkit-scrollbar-thumb{background:var(--focus-color);border-radius:var(--scrollbar-width)}.lil-gui.force-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}.lil-gui.autoPlace{max-height:100%;position:fixed;right:15px;top:0;z-index:1001}.lil-gui .controller{align-items:center;display:flex;margin:var(--spacing) 0;padding:0 var(--padding)}.lil-gui .controller.disabled{opacity:.5}.lil-gui .controller.disabled,.lil-gui .controller.disabled *{pointer-events:none!important}.lil-gui .controller>.name{flex-shrink:0;line-height:var(--widget-height);min-width:var(--name-width);padding-right:var(--spacing);white-space:pre}.lil-gui .controller .widget{align-items:center;display:flex;min-height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.string input{color:var(--string-color)}.lil-gui .controller.boolean .widget{cursor:pointer}.lil-gui .controller.color .display{border-radius:var(--widget-border-radius);height:var(--widget-height);position:relative;width:100%}.lil-gui .controller.color input[type=color]{cursor:pointer;height:100%;opacity:0;width:100%}.lil-gui .controller.color input[type=text]{flex-shrink:0;font-family:var(--font-family-mono);margin-left:var(--spacing);min-width:var(--color-input-min-width);width:var(--color-input-width)}.lil-gui .controller.option select{max-width:100%;opacity:0;position:absolute;width:100%}.lil-gui .controller.option .display{background:var(--widget-color);border-radius:var(--widget-border-radius);height:var(--widget-height);line-height:var(--widget-height);max-width:100%;overflow:hidden;padding-left:.55em;padding-right:1.75em;pointer-events:none;position:relative;word-break:break-all}.lil-gui .controller.option .display.active{background:var(--focus-color)}.lil-gui .controller.option .display:after{bottom:0;content:"↕";font-family:lil-gui;padding-right:.375em;position:absolute;right:0;top:0}.lil-gui .controller.option .widget,.lil-gui .controller.option select{cursor:pointer}.lil-gui .controller.number input{color:var(--number-color)}.lil-gui .controller.number.hasSlider input{flex-shrink:0;margin-left:var(--spacing);min-width:var(--slider-input-min-width);width:var(--slider-input-width)}.lil-gui .controller.number .slider{background-color:var(--widget-color);border-radius:var(--widget-border-radius);cursor:ew-resize;height:var(--widget-height);overflow:hidden;padding-right:var(--slider-knob-width);touch-action:pan-y;width:100%}.lil-gui .controller.number .slider.active{background-color:var(--focus-color)}.lil-gui .controller.number .slider.active .fill{opacity:.95}.lil-gui .controller.number .fill{border-right:var(--slider-knob-width) solid var(--number-color);box-sizing:content-box;height:100%}.lil-gui-dragging .lil-gui{--hover-color:var(--widget-color)}.lil-gui-dragging *{cursor:ew-resize!important}.lil-gui-dragging.lil-gui-vertical *{cursor:ns-resize!important}.lil-gui .title{--title-height:calc(var(--widget-height) + var(--spacing)*1.25);-webkit-tap-highlight-color:transparent;text-decoration-skip:objects;cursor:pointer;font-weight:600;height:var(--title-height);line-height:calc(var(--title-height) - 4px);outline:none;padding:0 var(--padding)}.lil-gui .title:before{content:"▾";display:inline-block;font-family:lil-gui;padding-right:2px}.lil-gui .title:active{background:var(--title-background-color);opacity:.75}.lil-gui.root>.title:focus{text-decoration:none!important}.lil-gui.closed>.title:before{content:"▸"}.lil-gui.closed>.children{opacity:0;transform:translateY(-7px)}.lil-gui.closed:not(.transition)>.children{display:none}.lil-gui.transition>.children{overflow:hidden;pointer-events:none;transition-duration:.3s;transition-property:height,opacity,transform;transition-timing-function:cubic-bezier(.2,.6,.35,1)}.lil-gui .children:empty:before{content:"Empty";display:block;font-style:italic;height:var(--widget-height);line-height:var(--widget-height);margin:var(--spacing) 0;opacity:.5;padding:0 var(--padding)}.lil-gui.root>.children>.lil-gui>.title{border-width:0;border-bottom:1px solid var(--widget-color);border-left:0 solid var(--widget-color);border-right:0 solid var(--widget-color);border-top:1px solid var(--widget-color);transition:border-color .3s}.lil-gui.root>.children>.lil-gui.closed>.title{border-bottom-color:transparent}.lil-gui+.controller{border-top:1px solid var(--widget-color);margin-top:0;padding-top:var(--spacing)}.lil-gui .lil-gui .lil-gui>.title{border:none}.lil-gui .lil-gui .lil-gui>.children{border:none;border-left:2px solid var(--widget-color);margin-left:var(--folder-indent)}.lil-gui .lil-gui .controller{border:none}.lil-gui input{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:0;border-radius:var(--widget-border-radius);color:var(--text-color);font-family:var(--font-family);font-size:var(--input-font-size);height:var(--widget-height);outline:none;width:100%}.lil-gui input:disabled{opacity:1}.lil-gui input[type=number],.lil-gui input[type=text]{padding:var(--widget-padding)}.lil-gui input[type=number]:focus,.lil-gui input[type=text]:focus{background:var(--focus-color)}.lil-gui input::-webkit-inner-spin-button,.lil-gui input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.lil-gui input[type=number]{-moz-appearance:textfield}.lil-gui input[type=checkbox]{appearance:none;-webkit-appearance:none;border-radius:var(--widget-border-radius);cursor:pointer;height:var(--checkbox-size);text-align:center;width:var(--checkbox-size)}.lil-gui input[type=checkbox]:checked:before{content:"✓";font-family:lil-gui;font-size:var(--checkbox-size);line-height:var(--checkbox-size)}.lil-gui button{-webkit-tap-highlight-color:transparent;background:var(--widget-color);border:1px solid var(--widget-color);border-radius:var(--widget-border-radius);color:var(--text-color);cursor:pointer;font-family:var(--font-family);font-size:var(--font-size);height:var(--widget-height);line-height:calc(var(--widget-height) - 4px);outline:none;text-align:center;text-transform:none;width:100%}.lil-gui button:active{background:var(--focus-color)}@font-face{font-family:lil-gui;src:url("data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAUsAAsAAAAACJwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAAH4AAADAImwmYE9TLzIAAAGIAAAAPwAAAGBKqH5SY21hcAAAAcgAAAD0AAACrukyyJBnbHlmAAACvAAAAF8AAACEIZpWH2hlYWQAAAMcAAAAJwAAADZfcj2zaGhlYQAAA0QAAAAYAAAAJAC5AHhobXR4AAADXAAAABAAAABMAZAAAGxvY2EAAANsAAAAFAAAACgCEgIybWF4cAAAA4AAAAAeAAAAIAEfABJuYW1lAAADoAAAASIAAAIK9SUU/XBvc3QAAATEAAAAZgAAAJCTcMc2eJxVjbEOgjAURU+hFRBK1dGRL+ALnAiToyMLEzFpnPz/eAshwSa97517c/MwwJmeB9kwPl+0cf5+uGPZXsqPu4nvZabcSZldZ6kfyWnomFY/eScKqZNWupKJO6kXN3K9uCVoL7iInPr1X5baXs3tjuMqCtzEuagm/AAlzQgPAAB4nGNgYRBlnMDAysDAYM/gBiT5oLQBAwuDJAMDEwMrMwNWEJDmmsJwgCFeXZghBcjlZMgFCzOiKOIFAB71Bb8AeJy1kjFuwkAQRZ+DwRAwBtNQRUGKQ8OdKCAWUhAgKLhIuAsVSpWz5Bbkj3dEgYiUIszqWdpZe+Z7/wB1oCYmIoboiwiLT2WjKl/jscrHfGg/pKdMkyklC5Zs2LEfHYpjcRoPzme9MWWmk3dWbK9ObkWkikOetJ554fWyoEsmdSlt+uR0pCJR34b6t/TVg1SY3sYvdf8vuiKrpyaDXDISiegp17p7579Gp3p++y7HPAiY9pmTibljrr85qSidtlg4+l25GLCaS8e6rRxNBmsnERunKbaOObRz7N72ju5vdAjYpBXHgJylOAVsMseDAPEP8LYoUHicY2BiAAEfhiAGJgZWBgZ7RnFRdnVJELCQlBSRlATJMoLV2DK4glSYs6ubq5vbKrJLSbGrgEmovDuDJVhe3VzcXFwNLCOILB/C4IuQ1xTn5FPilBTj5FPmBAB4WwoqAHicY2BkYGAA4sk1sR/j+W2+MnAzpDBgAyEMQUCSg4EJxAEAwUgFHgB4nGNgZGBgSGFggJMhDIwMqEAYAByHATJ4nGNgAIIUNEwmAABl3AGReJxjYAACIQYlBiMGJ3wQAEcQBEV4nGNgZGBgEGZgY2BiAAEQyQWEDAz/wXwGAAsPATIAAHicXdBNSsNAHAXwl35iA0UQXYnMShfS9GPZA7T7LgIu03SSpkwzYTIt1BN4Ak/gKTyAeCxfw39jZkjymzcvAwmAW/wgwHUEGDb36+jQQ3GXGot79L24jxCP4gHzF/EIr4jEIe7wxhOC3g2TMYy4Q7+Lu/SHuEd/ivt4wJd4wPxbPEKMX3GI5+DJFGaSn4qNzk8mcbKSR6xdXdhSzaOZJGtdapd4vVPbi6rP+cL7TGXOHtXKll4bY1Xl7EGnPtp7Xy2n00zyKLVHfkHBa4IcJ2oD3cgggWvt/V/FbDrUlEUJhTn/0azVWbNTNr0Ens8de1tceK9xZmfB1CPjOmPH4kitmvOubcNpmVTN3oFJyjzCvnmrwhJTzqzVj9jiSX911FjeAAB4nG3HMRKCMBBA0f0giiKi4DU8k0V2GWbIZDOh4PoWWvq6J5V8If9NVNQcaDhyouXMhY4rPTcG7jwYmXhKq8Wz+p762aNaeYXom2n3m2dLTVgsrCgFJ7OTmIkYbwIbC6vIB7WmFfAAAA==") format("woff")}@media (pointer:coarse){.lil-gui.allow-touch-styles{--widget-height:28px;--padding:6px;--spacing:6px;--font-size:13px;--input-font-size:16px;--folder-indent:10px;--scrollbar-width:7px;--slider-input-min-width:50px;--color-input-min-width:65px}}@media (hover:hover){.lil-gui .controller.color .display:hover:before{border:1px solid #fff9;border-radius:var(--widget-border-radius);bottom:0;content:" ";display:block;left:0;position:absolute;right:0;top:0}.lil-gui .controller.option .display.focus{background:var(--focus-color)}.lil-gui .controller.option .widget:hover .display{background:var(--hover-color)}.lil-gui .controller.number .slider:hover{background-color:var(--hover-color)}body:not(.lil-gui-dragging) .lil-gui .title:hover{background:var(--title-background-color);opacity:.85}.lil-gui .title:focus{text-decoration:underline var(--focus-color)}.lil-gui input:hover{background:var(--hover-color)}.lil-gui input:active{background:var(--focus-color)}.lil-gui input[type=checkbox]:focus{box-shadow:inset 0 0 0 1px var(--focus-color)}.lil-gui button:hover{background:var(--hover-color);border-color:var(--hover-color)}.lil-gui button:focus{border-color:var(--focus-color)}}'),p=!0),e?e.appendChild(this.domElement):i&&(this.domElement.classList.add("autoPlace"),document.body.appendChild(this.domElement)),s&&this.domElement.style.setProperty("--width",s+"px"),this.domElement.addEventListener("keydown",t=>t.stopPropagation()),this.domElement.addEventListener("keyup",t=>t.stopPropagation())}add(t,e,s,n,r){if(Object(s)===s)return new c(this,t,e,s);const l=t[e];switch(typeof l){case"number":return new d(this,t,e,s,n,r);case"boolean":return new i(this,t,e);case"string":return new u(this,t,e);case"function":return new h(this,t,e)}console.error("gui.add failed\n\tproperty:",e,"\n\tobject:",t,"\n\tvalue:",l)}addColor(t,i,e=1){return new a(this,t,i,e)}addFolder(t){return new g({parent:this,title:t})}load(t,i=!0){return t.controllers&&this.controllers.forEach(i=>{i instanceof h||i._name in t.controllers&&i.load(t.controllers[i._name])}),i&&t.folders&&this.folders.forEach(i=>{i._title in t.folders&&i.load(t.folders[i._title])}),this}save(t=!0){const i={controllers:{},folders:{}};return this.controllers.forEach(t=>{if(!(t instanceof h)){if(t._name in i.controllers)throw new Error(`Cannot save GUI with duplicate property "${t._name}"`);i.controllers[t._name]=t.save()}}),t&&this.folders.forEach(t=>{if(t._title in i.folders)throw new Error(`Cannot save GUI with duplicate folder "${t._title}"`);i.folders[t._title]=t.save()}),i}open(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),this.domElement.classList.toggle("closed",this._closed),this}close(){return this.open(!1)}show(t=!0){return this._hidden=!t,this.domElement.style.display=this._hidden?"none":"",this}hide(){return this.show(!1)}openAnimated(t=!0){return this._closed=!t,this.$title.setAttribute("aria-expanded",!this._closed),requestAnimationFrame(()=>{const i=this.$children.clientHeight;this.$children.style.height=i+"px",this.domElement.classList.add("transition");const e=t=>{t.target===this.$children&&(this.$children.style.height="",this.domElement.classList.remove("transition"),this.$children.removeEventListener("transitionend",e))};this.$children.addEventListener("transitionend",e);const s=t?this.$children.scrollHeight:0;this.domElement.classList.toggle("closed",!t),requestAnimationFrame(()=>{this.$children.style.height=s+"px"})}),this}title(t){return this._title=t,this.$title.innerHTML=t,this}reset(t=!0){return(t?this.controllersRecursive():this.controllers).forEach(t=>t.reset()),this}onChange(t){return this._onChange=t,this}_callOnChange(t){this.parent&&this.parent._callOnChange(t),void 0!==this._onChange&&this._onChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}onFinishChange(t){return this._onFinishChange=t,this}_callOnFinishChange(t){this.parent&&this.parent._callOnFinishChange(t),void 0!==this._onFinishChange&&this._onFinishChange.call(this,{object:t.object,property:t.property,value:t.getValue(),controller:t})}destroy(){this.parent&&(this.parent.children.splice(this.parent.children.indexOf(this),1),this.parent.folders.splice(this.parent.folders.indexOf(this),1)),this.domElement.parentElement&&this.domElement.parentElement.removeChild(this.domElement),Array.from(this.children).forEach(t=>t.destroy())}controllersRecursive(){let t=Array.from(this.controllers);return this.folders.forEach(i=>{t=t.concat(i.controllersRecursive())}),t}foldersRecursive(){let t=Array.from(this.folders);return this.folders.forEach(i=>{t=t.concat(i.foldersRecursive())}),t}}export default g;export{i as BooleanController,a as ColorController,t as Controller,h as FunctionController,g as GUI,d as NumberController,c as OptionController,u as StringController}; diff --git a/jsm/libs/meshopt_decoder.module.js b/jsm/libs/meshopt_decoder.module.js new file mode 100644 index 0000000..1991e05 --- /dev/null +++ b/jsm/libs/meshopt_decoder.module.js @@ -0,0 +1,113 @@ +// This file is part of meshoptimizer library and is distributed under the terms of MIT License. +// Copyright (C) 2016-2020, by Arseny Kapoulkine (arseny.kapoulkine@gmail.com) +var MeshoptDecoder = (function() { + "use strict"; + + // Built with clang version 11.0.0 (https://github.com/llvm/llvm-project.git 0160ad802e899c2922bc9b29564080c22eb0908c) + // Built from meshoptimizer 0.14 + var wasm_base = "B9h9z9tFBBBF8fL9gBB9gLaaaaaFa9gEaaaB9gFaFa9gEaaaFaEMcBFFFGGGEIIILF9wFFFLEFBFKNFaFCx/IFMO/LFVK9tv9t9vq95GBt9f9f939h9z9t9f9j9h9s9s9f9jW9vq9zBBp9tv9z9o9v9wW9f9kv9j9v9kv9WvqWv94h919m9mvqBF8Z9tv9z9o9v9wW9f9kv9j9v9kv9J9u9kv94h919m9mvqBGy9tv9z9o9v9wW9f9kv9j9v9kv9J9u9kv949TvZ91v9u9jvBEn9tv9z9o9v9wW9f9kv9j9v9kv69p9sWvq9P9jWBIi9tv9z9o9v9wW9f9kv9j9v9kv69p9sWvq9R919hWBLn9tv9z9o9v9wW9f9kv9j9v9kv69p9sWvq9F949wBKI9z9iqlBOc+x8ycGBM/qQFTa8jUUUUBCU/EBlHL8kUUUUBC9+RKGXAGCFJAI9LQBCaRKAE2BBC+gF9HQBALAEAIJHOAGlAGTkUUUBRNCUoBAG9uC/wgBZHKCUGAKCUG9JyRVAECFJRICBRcGXEXAcAF9PQFAVAFAclAcAVJAF9JyRMGXGXAG9FQBAMCbJHKC9wZRSAKCIrCEJCGrRQANCUGJRfCBRbAIRTEXGXAOATlAQ9PQBCBRISEMATAQJRIGXAS9FQBCBRtCBREEXGXAOAIlCi9PQBCBRISLMANCU/CBJAEJRKGXGXGXGXGXATAECKrJ2BBAtCKZrCEZfIBFGEBMAKhB83EBAKCNJhB83EBSEMAKAI2BIAI2BBHmCKrHYAYCE6HYy86BBAKCFJAICIJAYJHY2BBAmCIrCEZHPAPCE6HPy86BBAKCGJAYAPJHY2BBAmCGrCEZHPAPCE6HPy86BBAKCEJAYAPJHY2BBAmCEZHmAmCE6Hmy86BBAKCIJAYAmJHY2BBAI2BFHmCKrHPAPCE6HPy86BBAKCLJAYAPJHY2BBAmCIrCEZHPAPCE6HPy86BBAKCKJAYAPJHY2BBAmCGrCEZHPAPCE6HPy86BBAKCOJAYAPJHY2BBAmCEZHmAmCE6Hmy86BBAKCNJAYAmJHY2BBAI2BGHmCKrHPAPCE6HPy86BBAKCVJAYAPJHY2BBAmCIrCEZHPAPCE6HPy86BBAKCcJAYAPJHY2BBAmCGrCEZHPAPCE6HPy86BBAKCMJAYAPJHY2BBAmCEZHmAmCE6Hmy86BBAKCSJAYAmJHm2BBAI2BEHICKrHYAYCE6HYy86BBAKCQJAmAYJHm2BBAICIrCEZHYAYCE6HYy86BBAKCfJAmAYJHm2BBAICGrCEZHYAYCE6HYy86BBAKCbJAmAYJHK2BBAICEZHIAICE6HIy86BBAKAIJRISGMAKAI2BNAI2BBHmCIrHYAYCb6HYy86BBAKCFJAICNJAYJHY2BBAmCbZHmAmCb6Hmy86BBAKCGJAYAmJHm2BBAI2BFHYCIrHPAPCb6HPy86BBAKCEJAmAPJHm2BBAYCbZHYAYCb6HYy86BBAKCIJAmAYJHm2BBAI2BGHYCIrHPAPCb6HPy86BBAKCLJAmAPJHm2BBAYCbZHYAYCb6HYy86BBAKCKJAmAYJHm2BBAI2BEHYCIrHPAPCb6HPy86BBAKCOJAmAPJHm2BBAYCbZHYAYCb6HYy86BBAKCNJAmAYJHm2BBAI2BIHYCIrHPAPCb6HPy86BBAKCVJAmAPJHm2BBAYCbZHYAYCb6HYy86BBAKCcJAmAYJHm2BBAI2BLHYCIrHPAPCb6HPy86BBAKCMJAmAPJHm2BBAYCbZHYAYCb6HYy86BBAKCSJAmAYJHm2BBAI2BKHYCIrHPAPCb6HPy86BBAKCQJAmAPJHm2BBAYCbZHYAYCb6HYy86BBAKCfJAmAYJHm2BBAI2BOHICIrHYAYCb6HYy86BBAKCbJAmAYJHK2BBAICbZHIAICb6HIy86BBAKAIJRISFMAKAI8pBB83BBAKCNJAICNJ8pBB83BBAICTJRIMAtCGJRtAECTJHEAS9JQBMMGXAIQBCBRISEMGXAM9FQBANAbJ2BBRtCBRKAfREEXAEANCU/CBJAKJ2BBHTCFrCBATCFZl9zAtJHt86BBAEAGJREAKCFJHKAM9HQBMMAfCFJRfAIRTAbCFJHbAG9HQBMMABAcAG9sJANCUGJAMAG9sTkUUUBpANANCUGJAMCaJAG9sJAGTkUUUBpMAMCBAIyAcJRcAIQBMC9+RKSFMCBC99AOAIlAGCAAGCA9Ly6yRKMALCU/EBJ8kUUUUBAKM+OmFTa8jUUUUBCoFlHL8kUUUUBC9+RKGXAFCE9uHOCtJAI9LQBCaRKAE2BBHNC/wFZC/gF9HQBANCbZHVCF9LQBALCoBJCgFCUFT+JUUUBpALC84Jha83EBALC8wJha83EBALC8oJha83EBALCAJha83EBALCiJha83EBALCTJha83EBALha83ENALha83EBAEAIJC9wJRcAECFJHNAOJRMGXAF9FQBCQCbAVCF6yRSABRECBRVCBRQCBRfCBRICBRKEXGXAMAcuQBC9+RKSEMGXGXAN2BBHOC/vF9LQBALCoBJAOCIrCa9zAKJCbZCEWJHb8oGIRTAb8oGBRtGXAOCbZHbAS9PQBALAOCa9zAIJCbZCGWJ8oGBAVAbyROAb9FRbGXGXAGCG9HQBABAt87FBABCIJAO87FBABCGJAT87FBSFMAEAtjGBAECNJAOjGBAECIJATjGBMAVAbJRVALCoBJAKCEWJHmAOjGBAmATjGIALAICGWJAOjGBALCoBJAKCFJCbZHKCEWJHTAtjGBATAOjGIAIAbJRIAKCFJRKSGMGXGXAbCb6QBAQAbJAbC989zJCFJRQSFMAM1BBHbCgFZROGXGXAbCa9MQBAMCFJRMSFMAM1BFHbCgBZCOWAOCgBZqROGXAbCa9MQBAMCGJRMSFMAM1BGHbCgBZCfWAOqROGXAbCa9MQBAMCEJRMSFMAM1BEHbCgBZCdWAOqROGXAbCa9MQBAMCIJRMSFMAM2BIC8cWAOqROAMCLJRMMAOCFrCBAOCFZl9zAQJRQMGXGXAGCG9HQBABAt87FBABCIJAQ87FBABCGJAT87FBSFMAEAtjGBAECNJAQjGBAECIJATjGBMALCoBJAKCEWJHOAQjGBAOATjGIALAICGWJAQjGBALCoBJAKCFJCbZHKCEWJHOAtjGBAOAQjGIAICFJRIAKCFJRKSFMGXAOCDF9LQBALAIAcAOCbZJ2BBHbCIrHTlCbZCGWJ8oGBAVCFJHtATyROALAIAblCbZCGWJ8oGBAtAT9FHmJHtAbCbZHTyRbAT9FRTGXGXAGCG9HQBABAV87FBABCIJAb87FBABCGJAO87FBSFMAEAVjGBAECNJAbjGBAECIJAOjGBMALAICGWJAVjGBALCoBJAKCEWJHYAOjGBAYAVjGIALAICFJHICbZCGWJAOjGBALCoBJAKCFJCbZCEWJHYAbjGBAYAOjGIALAIAmJCbZHICGWJAbjGBALCoBJAKCGJCbZHKCEWJHOAVjGBAOAbjGIAKCFJRKAIATJRIAtATJRVSFMAVCBAM2BBHYyHTAOC/+F6HPJROAYCbZRtGXGXAYCIrHmQBAOCFJRbSFMAORbALAIAmlCbZCGWJ8oGBROMGXGXAtQBAbCFJRVSFMAbRVALAIAYlCbZCGWJ8oGBRbMGXGXAP9FQBAMCFJRYSFMAM1BFHYCgFZRTGXGXAYCa9MQBAMCGJRYSFMAM1BGHYCgBZCOWATCgBZqRTGXAYCa9MQBAMCEJRYSFMAM1BEHYCgBZCfWATqRTGXAYCa9MQBAMCIJRYSFMAM1BIHYCgBZCdWATqRTGXAYCa9MQBAMCLJRYSFMAMCKJRYAM2BLC8cWATqRTMATCFrCBATCFZl9zAQJHQRTMGXGXAmCb6QBAYRPSFMAY1BBHMCgFZROGXGXAMCa9MQBAYCFJRPSFMAY1BFHMCgBZCOWAOCgBZqROGXAMCa9MQBAYCGJRPSFMAY1BGHMCgBZCfWAOqROGXAMCa9MQBAYCEJRPSFMAY1BEHMCgBZCdWAOqROGXAMCa9MQBAYCIJRPSFMAYCLJRPAY2BIC8cWAOqROMAOCFrCBAOCFZl9zAQJHQROMGXGXAtCb6QBAPRMSFMAP1BBHMCgFZRbGXGXAMCa9MQBAPCFJRMSFMAP1BFHMCgBZCOWAbCgBZqRbGXAMCa9MQBAPCGJRMSFMAP1BGHMCgBZCfWAbqRbGXAMCa9MQBAPCEJRMSFMAP1BEHMCgBZCdWAbqRbGXAMCa9MQBAPCIJRMSFMAPCLJRMAP2BIC8cWAbqRbMAbCFrCBAbCFZl9zAQJHQRbMGXGXAGCG9HQBABAT87FBABCIJAb87FBABCGJAO87FBSFMAEATjGBAECNJAbjGBAECIJAOjGBMALCoBJAKCEWJHYAOjGBAYATjGIALAICGWJATjGBALCoBJAKCFJCbZCEWJHYAbjGBAYAOjGIALAICFJHICbZCGWJAOjGBALCoBJAKCGJCbZCEWJHOATjGBAOAbjGIALAIAm9FAmCb6qJHICbZCGWJAbjGBAIAt9FAtCb6qJRIAKCEJRKMANCFJRNABCKJRBAECSJREAKCbZRKAICbZRIAfCEJHfAF9JQBMMCBC99AMAc6yRKMALCoFJ8kUUUUBAKM/tIFGa8jUUUUBCTlRLC9+RKGXAFCLJAI9LQBCaRKAE2BBC/+FZC/QF9HQBALhB83ENAECFJRKAEAIJC98JREGXAF9FQBGXAGCG6QBEXGXAKAE9JQBC9+bMAK1BBHGCgFZRIGXGXAGCa9MQBAKCFJRKSFMAK1BFHGCgBZCOWAICgBZqRIGXAGCa9MQBAKCGJRKSFMAK1BGHGCgBZCfWAIqRIGXAGCa9MQBAKCEJRKSFMAK1BEHGCgBZCdWAIqRIGXAGCa9MQBAKCIJRKSFMAK2BIC8cWAIqRIAKCLJRKMALCNJAICFZCGWqHGAICGrCBAICFrCFZl9zAG8oGBJHIjGBABAIjGBABCIJRBAFCaJHFQBSGMMEXGXAKAE9JQBC9+bMAK1BBHGCgFZRIGXGXAGCa9MQBAKCFJRKSFMAK1BFHGCgBZCOWAICgBZqRIGXAGCa9MQBAKCGJRKSFMAK1BGHGCgBZCfWAIqRIGXAGCa9MQBAKCEJRKSFMAK1BEHGCgBZCdWAIqRIGXAGCa9MQBAKCIJRKSFMAK2BIC8cWAIqRIAKCLJRKMABAICGrCBAICFrCFZl9zALCNJAICFZCGWqHI8oGBJHG87FBAIAGjGBABCGJRBAFCaJHFQBMMCBC99AKAE6yRKMAKM+lLKFaF99GaG99FaG99GXGXAGCI9HQBAF9FQFEXGXGX9DBBB8/9DBBB+/ABCGJHG1BB+yAB1BBHE+yHI+L+TABCFJHL1BBHK+yHO+L+THN9DBBBB9gHVyAN9DBB/+hANAN+U9DBBBBANAVyHcAc+MHMAECa3yAI+SHIAI+UAcAMAKCa3yAO+SHcAc+U+S+S+R+VHO+U+SHN+L9DBBB9P9d9FQBAN+oRESFMCUUUU94REMAGAE86BBGXGX9DBBB8/9DBBB+/Ac9DBBBB9gyAcAO+U+SHN+L9DBBB9P9d9FQBAN+oRGSFMCUUUU94RGMALAG86BBGXGX9DBBB8/9DBBB+/AI9DBBBB9gyAIAO+U+SHN+L9DBBB9P9d9FQBAN+oRGSFMCUUUU94RGMABAG86BBABCIJRBAFCaJHFQBSGMMAF9FQBEXGXGX9DBBB8/9DBBB+/ABCIJHG8uFB+yAB8uFBHE+yHI+L+TABCGJHL8uFBHK+yHO+L+THN9DBBBB9gHVyAN9DB/+g6ANAN+U9DBBBBANAVyHcAc+MHMAECa3yAI+SHIAI+UAcAMAKCa3yAO+SHcAc+U+S+S+R+VHO+U+SHN+L9DBBB9P9d9FQBAN+oRESFMCUUUU94REMAGAE87FBGXGX9DBBB8/9DBBB+/Ac9DBBBB9gyAcAO+U+SHN+L9DBBB9P9d9FQBAN+oRGSFMCUUUU94RGMALAG87FBGXGX9DBBB8/9DBBB+/AI9DBBBB9gyAIAO+U+SHN+L9DBBB9P9d9FQBAN+oRGSFMCUUUU94RGMABAG87FBABCNJRBAFCaJHFQBMMM/SEIEaE99EaF99GXAF9FQBCBREABRIEXGXGX9D/zI818/AICKJ8uFBHLCEq+y+VHKAI8uFB+y+UHO9DB/+g6+U9DBBB8/9DBBB+/AO9DBBBB9gy+SHN+L9DBBB9P9d9FQBAN+oRVSFMCUUUU94RVMAICIJ8uFBRcAICGJ8uFBRMABALCFJCEZAEqCFWJAV87FBGXGXAKAM+y+UHN9DB/+g6+U9DBBB8/9DBBB+/AN9DBBBB9gy+SHS+L9DBBB9P9d9FQBAS+oRMSFMCUUUU94RMMABALCGJCEZAEqCFWJAM87FBGXGXAKAc+y+UHK9DB/+g6+U9DBBB8/9DBBB+/AK9DBBBB9gy+SHS+L9DBBB9P9d9FQBAS+oRcSFMCUUUU94RcMABALCaJCEZAEqCFWJAc87FBGXGX9DBBU8/AOAO+U+TANAN+U+TAKAK+U+THO9DBBBBAO9DBBBB9gy+R9DB/+g6+U9DBBB8/+SHO+L9DBBB9P9d9FQBAO+oRcSFMCUUUU94RcMABALCEZAEqCFWJAc87FBAICNJRIAECIJREAFCaJHFQBMMM9JBGXAGCGrAF9sHF9FQBEXABAB8oGBHGCNWCN91+yAGCi91CnWCUUU/8EJ+++U84GBABCIJRBAFCaJHFQBMMM9TFEaCBCB8oGUkUUBHFABCEJC98ZJHBjGUkUUBGXGXAB8/BCTWHGuQBCaREABAGlCggEJCTrXBCa6QFMAFREMAEM/lFFFaGXGXAFABqCEZ9FQBABRESFMGXGXAGCT9PQBABRESFMABREEXAEAF8oGBjGBAECIJAFCIJ8oGBjGBAECNJAFCNJ8oGBjGBAECSJAFCSJ8oGBjGBAECTJREAFCTJRFAGC9wJHGCb9LQBMMAGCI9JQBEXAEAF8oGBjGBAFCIJRFAECIJREAGC98JHGCE9LQBMMGXAG9FQBEXAEAF2BB86BBAECFJREAFCFJRFAGCaJHGQBMMABMoFFGaGXGXABCEZ9FQBABRESFMAFCgFZC+BwsN9sRIGXGXAGCT9PQBABRESFMABREEXAEAIjGBAECSJAIjGBAECNJAIjGBAECIJAIjGBAECTJREAGC9wJHGCb9LQBMMAGCI9JQBEXAEAIjGBAECIJREAGC98JHGCE9LQBMMGXAG9FQBEXAEAF86BBAECFJREAGCaJHGQBMMABMMMFBCUNMIT9kBB"; + var wasm_simd = "B9h9z9tFBBBFiI9gBB9gLaaaaaFa9gEaaaB9gFaFaEMcBBFBFFGGGEILF9wFFFLEFBFKNFaFCx/aFMO/LFVK9tv9t9vq95GBt9f9f939h9z9t9f9j9h9s9s9f9jW9vq9zBBp9tv9z9o9v9wW9f9kv9j9v9kv9WvqWv94h919m9mvqBG8Z9tv9z9o9v9wW9f9kv9j9v9kv9J9u9kv94h919m9mvqBIy9tv9z9o9v9wW9f9kv9j9v9kv9J9u9kv949TvZ91v9u9jvBLn9tv9z9o9v9wW9f9kv9j9v9kv69p9sWvq9P9jWBKi9tv9z9o9v9wW9f9kv9j9v9kv69p9sWvq9R919hWBOn9tv9z9o9v9wW9f9kv9j9v9kv69p9sWvq9F949wBNI9z9iqlBVc+N9IcIBTEM9+FLa8jUUUUBCTlRBCBRFEXCBRGCBREEXABCNJAGJAECUaAFAGrCFZHIy86BBAEAIJREAGCFJHGCN9HQBMAFCx+YUUBJAE86BBAFCEWCxkUUBJAB8pEN83EBAFCFJHFCUG9HQBMMk8lLbaE97F9+FaL978jUUUUBCU/KBlHL8kUUUUBC9+RKGXAGCFJAI9LQBCaRKAE2BBC+gF9HQBALAEAIJHOAGlAG/8cBBCUoBAG9uC/wgBZHKCUGAKCUG9JyRNAECFJRKCBRVGXEXAVAF9PQFANAFAVlAVANJAF9JyRcGXGXAG9FQBAcCbJHIC9wZHMCE9sRSAMCFWRQAICIrCEJCGrRfCBRbEXAKRTCBRtGXEXGXAOATlAf9PQBCBRKSLMALCU/CBJAtAM9sJRmATAfJRKCBREGXAMCoB9JQBAOAKlC/gB9JQBCBRIEXAmAIJREGXGXGXGXGXATAICKrJ2BBHYCEZfIBFGEBMAECBDtDMIBSEMAEAKDBBIAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnHPCGD+MFAPDQBTFtGmEYIPLdKeOnC0+G+MiDtD9OHdCEDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMIBAKCIJAeDeBJAiCx+YUUBJ2BBJRKSGMAEAKDBBNAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnC+P+e+8/4BDtD9OHdCbDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMIBAKCNJAeDeBJAiCx+YUUBJ2BBJRKSFMAEAKDBBBDMIBAKCTJRKMGXGXGXGXGXAYCGrCEZfIBFGEBMAECBDtDMITSEMAEAKDBBIAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnHPCGD+MFAPDQBTFtGmEYIPLdKeOnC0+G+MiDtD9OHdCEDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMITAKCIJAeDeBJAiCx+YUUBJ2BBJRKSGMAEAKDBBNAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnC+P+e+8/4BDtD9OHdCbDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMITAKCNJAeDeBJAiCx+YUUBJ2BBJRKSFMAEAKDBBBDMITAKCTJRKMGXGXGXGXGXAYCIrCEZfIBFGEBMAECBDtDMIASEMAEAKDBBIAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnHPCGD+MFAPDQBTFtGmEYIPLdKeOnC0+G+MiDtD9OHdCEDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMIAAKCIJAeDeBJAiCx+YUUBJ2BBJRKSGMAEAKDBBNAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnC+P+e+8/4BDtD9OHdCbDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMIAAKCNJAeDeBJAiCx+YUUBJ2BBJRKSFMAEAKDBBBDMIAAKCTJRKMGXGXGXGXGXAYCKrfIBFGEBMAECBDtDMI8wSEMAEAKDBBIAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnHPCGD+MFAPDQBTFtGmEYIPLdKeOnC0+G+MiDtD9OHdCEDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHYCEWCxkUUBJDBEBAYCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHYCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMI8wAKCIJAeDeBJAYCx+YUUBJ2BBJRKSGMAEAKDBBNAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnC+P+e+8/4BDtD9OHdCbDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHYCEWCxkUUBJDBEBAYCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHYCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMI8wAKCNJAeDeBJAYCx+YUUBJ2BBJRKSFMAEAKDBBBDMI8wAKCTJRKMAICoBJREAICUFJAM9LQFAERIAOAKlC/fB9LQBMMGXAEAM9PQBAECErRIEXGXAOAKlCi9PQBCBRKSOMAmAEJRYGXGXGXGXGXATAECKrJ2BBAICKZrCEZfIBFGEBMAYCBDtDMIBSEMAYAKDBBIAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnHPCGD+MFAPDQBTFtGmEYIPLdKeOnC0+G+MiDtD9OHdCEDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMIBAKCIJAeDeBJAiCx+YUUBJ2BBJRKSGMAYAKDBBNAKDBBBHPCID+MFAPDQBTFtGmEYIPLdKeOnC+P+e+8/4BDtD9OHdCbDbD8jHPAPDQBFGENVcMILKOSQfbHeD8dBh+BsxoxoUwN0AeD8dFhxoUwkwk+gUa0sHnhTkAnsHnhNkAnsHn7CgFZHiCEWCxkUUBJDBEBAiCx+YUUBJDBBBHeAeDQBBBBBBBBBBBBBBBBAnhAk7CgFZHiCEWCxkUUBJDBEBD9uDQBFGEILKOTtmYPdenDfAdAPD9SDMIBAKCNJAeDeBJAiCx+YUUBJ2BBJRKSFMAYAKDBBBDMIBAKCTJRKMAICGJRIAECTJHEAM9JQBMMGXAK9FQBAKRTAtCFJHtCI6QGSFMMCBRKSEMGXAM9FQBALCUGJAbJREALAbJDBGBReCBRYEXAEALCU/CBJAYJHIDBIBHdCFD9tAdCFDbHPD9OD9hD9RHdAIAMJDBIBH8ZCFD9tA8ZAPD9OD9hD9RH8ZDQBTFtGmEYIPLdKeOnHpAIAQJDBIBHyCFD9tAyAPD9OD9hD9RHyAIASJDBIBH8cCFD9tA8cAPD9OD9hD9RH8cDQBTFtGmEYIPLdKeOnH8dDQBFTtGEmYILPdKOenHPAPDQBFGEBFGEBFGEBFGEAeD9uHeDyBjGBAEAGJHIAeAPAPDQILKOILKOILKOILKOD9uHeDyBjGBAIAGJHIAeAPAPDQNVcMNVcMNVcMNVcMD9uHeDyBjGBAIAGJHIAeAPAPDQSQfbSQfbSQfbSQfbD9uHeDyBjGBAIAGJHIAeApA8dDQNVi8ZcMpySQ8c8dfb8e8fHPAPDQBFGEBFGEBFGEBFGED9uHeDyBjGBAIAGJHIAeAPAPDQILKOILKOILKOILKOD9uHeDyBjGBAIAGJHIAeAPAPDQNVcMNVcMNVcMNVcMD9uHeDyBjGBAIAGJHIAeAPAPDQSQfbSQfbSQfbSQfbD9uHeDyBjGBAIAGJHIAeAdA8ZDQNiV8ZcpMyS8cQ8df8eb8fHdAyA8cDQNiV8ZcpMyS8cQ8df8eb8fH8ZDQBFTtGEmYILPdKOenHPAPDQBFGEBFGEBFGEBFGED9uHeDyBjGBAIAGJHIAeAPAPDQILKOILKOILKOILKOD9uHeDyBjGBAIAGJHIAeAPAPDQNVcMNVcMNVcMNVcMD9uHeDyBjGBAIAGJHIAeAPAPDQSQfbSQfbSQfbSQfbD9uHeDyBjGBAIAGJHIAeAdA8ZDQNVi8ZcMpySQ8c8dfb8e8fHPAPDQBFGEBFGEBFGEBFGED9uHeDyBjGBAIAGJHIAeAPAPDQILKOILKOILKOILKOD9uHeDyBjGBAIAGJHIAeAPAPDQNVcMNVcMNVcMNVcMD9uHeDyBjGBAIAGJHIAeAPAPDQSQfbSQfbSQfbSQfbD9uHeDyBjGBAIAGJREAYCTJHYAM9JQBMMAbCIJHbAG9JQBMMABAVAG9sJALCUGJAcAG9s/8cBBALALCUGJAcCaJAG9sJAG/8cBBMAcCBAKyAVJRVAKQBMC9+RKSFMCBC99AOAKlAGCAAGCA9Ly6yRKMALCU/KBJ8kUUUUBAKMNBT+BUUUBM+KmFTa8jUUUUBCoFlHL8kUUUUBC9+RKGXAFCE9uHOCtJAI9LQBCaRKAE2BBHNC/wFZC/gF9HQBANCbZHVCF9LQBALCoBJCgFCUF/8MBALC84Jha83EBALC8wJha83EBALC8oJha83EBALCAJha83EBALCiJha83EBALCTJha83EBALha83ENALha83EBAEAIJC9wJRcAECFJHNAOJRMGXAF9FQBCQCbAVCF6yRSABRECBRVCBRQCBRfCBRICBRKEXGXAMAcuQBC9+RKSEMGXGXAN2BBHOC/vF9LQBALCoBJAOCIrCa9zAKJCbZCEWJHb8oGIRTAb8oGBRtGXAOCbZHbAS9PQBALAOCa9zAIJCbZCGWJ8oGBAVAbyROAb9FRbGXGXAGCG9HQBABAt87FBABCIJAO87FBABCGJAT87FBSFMAEAtjGBAECNJAOjGBAECIJATjGBMAVAbJRVALCoBJAKCEWJHmAOjGBAmATjGIALAICGWJAOjGBALCoBJAKCFJCbZHKCEWJHTAtjGBATAOjGIAIAbJRIAKCFJRKSGMGXGXAbCb6QBAQAbJAbC989zJCFJRQSFMAM1BBHbCgFZROGXGXAbCa9MQBAMCFJRMSFMAM1BFHbCgBZCOWAOCgBZqROGXAbCa9MQBAMCGJRMSFMAM1BGHbCgBZCfWAOqROGXAbCa9MQBAMCEJRMSFMAM1BEHbCgBZCdWAOqROGXAbCa9MQBAMCIJRMSFMAM2BIC8cWAOqROAMCLJRMMAOCFrCBAOCFZl9zAQJRQMGXGXAGCG9HQBABAt87FBABCIJAQ87FBABCGJAT87FBSFMAEAtjGBAECNJAQjGBAECIJATjGBMALCoBJAKCEWJHOAQjGBAOATjGIALAICGWJAQjGBALCoBJAKCFJCbZHKCEWJHOAtjGBAOAQjGIAICFJRIAKCFJRKSFMGXAOCDF9LQBALAIAcAOCbZJ2BBHbCIrHTlCbZCGWJ8oGBAVCFJHtATyROALAIAblCbZCGWJ8oGBAtAT9FHmJHtAbCbZHTyRbAT9FRTGXGXAGCG9HQBABAV87FBABCIJAb87FBABCGJAO87FBSFMAEAVjGBAECNJAbjGBAECIJAOjGBMALAICGWJAVjGBALCoBJAKCEWJHYAOjGBAYAVjGIALAICFJHICbZCGWJAOjGBALCoBJAKCFJCbZCEWJHYAbjGBAYAOjGIALAIAmJCbZHICGWJAbjGBALCoBJAKCGJCbZHKCEWJHOAVjGBAOAbjGIAKCFJRKAIATJRIAtATJRVSFMAVCBAM2BBHYyHTAOC/+F6HPJROAYCbZRtGXGXAYCIrHmQBAOCFJRbSFMAORbALAIAmlCbZCGWJ8oGBROMGXGXAtQBAbCFJRVSFMAbRVALAIAYlCbZCGWJ8oGBRbMGXGXAP9FQBAMCFJRYSFMAM1BFHYCgFZRTGXGXAYCa9MQBAMCGJRYSFMAM1BGHYCgBZCOWATCgBZqRTGXAYCa9MQBAMCEJRYSFMAM1BEHYCgBZCfWATqRTGXAYCa9MQBAMCIJRYSFMAM1BIHYCgBZCdWATqRTGXAYCa9MQBAMCLJRYSFMAMCKJRYAM2BLC8cWATqRTMATCFrCBATCFZl9zAQJHQRTMGXGXAmCb6QBAYRPSFMAY1BBHMCgFZROGXGXAMCa9MQBAYCFJRPSFMAY1BFHMCgBZCOWAOCgBZqROGXAMCa9MQBAYCGJRPSFMAY1BGHMCgBZCfWAOqROGXAMCa9MQBAYCEJRPSFMAY1BEHMCgBZCdWAOqROGXAMCa9MQBAYCIJRPSFMAYCLJRPAY2BIC8cWAOqROMAOCFrCBAOCFZl9zAQJHQROMGXGXAtCb6QBAPRMSFMAP1BBHMCgFZRbGXGXAMCa9MQBAPCFJRMSFMAP1BFHMCgBZCOWAbCgBZqRbGXAMCa9MQBAPCGJRMSFMAP1BGHMCgBZCfWAbqRbGXAMCa9MQBAPCEJRMSFMAP1BEHMCgBZCdWAbqRbGXAMCa9MQBAPCIJRMSFMAPCLJRMAP2BIC8cWAbqRbMAbCFrCBAbCFZl9zAQJHQRbMGXGXAGCG9HQBABAT87FBABCIJAb87FBABCGJAO87FBSFMAEATjGBAECNJAbjGBAECIJAOjGBMALCoBJAKCEWJHYAOjGBAYATjGIALAICGWJATjGBALCoBJAKCFJCbZCEWJHYAbjGBAYAOjGIALAICFJHICbZCGWJAOjGBALCoBJAKCGJCbZCEWJHOATjGBAOAbjGIALAIAm9FAmCb6qJHICbZCGWJAbjGBAIAt9FAtCb6qJRIAKCEJRKMANCFJRNABCKJRBAECSJREAKCbZRKAICbZRIAfCEJHfAF9JQBMMCBC99AMAc6yRKMALCoFJ8kUUUUBAKM/tIFGa8jUUUUBCTlRLC9+RKGXAFCLJAI9LQBCaRKAE2BBC/+FZC/QF9HQBALhB83ENAECFJRKAEAIJC98JREGXAF9FQBGXAGCG6QBEXGXAKAE9JQBC9+bMAK1BBHGCgFZRIGXGXAGCa9MQBAKCFJRKSFMAK1BFHGCgBZCOWAICgBZqRIGXAGCa9MQBAKCGJRKSFMAK1BGHGCgBZCfWAIqRIGXAGCa9MQBAKCEJRKSFMAK1BEHGCgBZCdWAIqRIGXAGCa9MQBAKCIJRKSFMAK2BIC8cWAIqRIAKCLJRKMALCNJAICFZCGWqHGAICGrCBAICFrCFZl9zAG8oGBJHIjGBABAIjGBABCIJRBAFCaJHFQBSGMMEXGXAKAE9JQBC9+bMAK1BBHGCgFZRIGXGXAGCa9MQBAKCFJRKSFMAK1BFHGCgBZCOWAICgBZqRIGXAGCa9MQBAKCGJRKSFMAK1BGHGCgBZCfWAIqRIGXAGCa9MQBAKCEJRKSFMAK1BEHGCgBZCdWAIqRIGXAGCa9MQBAKCIJRKSFMAK2BIC8cWAIqRIAKCLJRKMABAICGrCBAICFrCFZl9zALCNJAICFZCGWqHI8oGBJHG87FBAIAGjGBABCGJRBAFCaJHFQBMMCBC99AKAE6yRKMAKM/dLEK97FaF97GXGXAGCI9HQBAF9FQFCBRGEXABABDBBBHECiD+rFCiD+sFD/6FHIAECND+rFCiD+sFD/6FAID/gFAECTD+rFCiD+sFD/6FHLD/gFD/kFD/lFHKCBDtD+2FHOAICUUUU94DtHND9OD9RD/kFHI9DBB/+hDYAIAID/mFAKAKD/mFALAOALAND9OD9RD/kFHIAID/mFD/kFD/kFD/jFD/nFHLD/mF9DBBX9LDYHOD/kFCgFDtD9OAECUUU94DtD9OD9QAIALD/mFAOD/kFCND+rFCU/+EDtD9OD9QAKALD/mFAOD/kFCTD+rFCUU/8ODtD9OD9QDMBBABCTJRBAGCIJHGAF9JQBSGMMAF9FQBCBRGEXABCTJHVAVDBBBHECBDtHOCUU98D8cFCUU98D8cEHND9OABDBBBHKAEDQILKOSQfbPden8c8d8e8fCggFDtD9OD/6FAKAEDQBFGENVcMTtmYi8ZpyHECTD+sFD/6FHID/gFAECTD+rFCTD+sFD/6FHLD/gFD/kFD/lFHE9DB/+g6DYALAEAOD+2FHOALCUUUU94DtHcD9OD9RD/kFHLALD/mFAEAED/mFAIAOAIAcD9OD9RD/kFHEAED/mFD/kFD/kFD/jFD/nFHID/mF9DBBX9LDYHOD/kFCTD+rFALAID/mFAOD/kFCggEDtD9OD9QHLAEAID/mFAOD/kFCaDbCBDnGCBDnECBDnKCBDnOCBDncCBDnMCBDnfCBDnbD9OHEDQNVi8ZcMpySQ8c8dfb8e8fD9QDMBBABAKAND9OALAEDQBFTtGEmYILPdKOenD9QDMBBABCAJRBAGCIJHGAF9JQBMMM/hEIGaF97FaL978jUUUUBCTlREGXAF9FQBCBRIEXAEABDBBBHLABCTJHKDBBBHODQILKOSQfbPden8c8d8e8fHNCTD+sFHVCID+rFDMIBAB9DBBU8/DY9D/zI818/DYAVCEDtD9QD/6FD/nFHVALAODQBFGENVcMTtmYi8ZpyHLCTD+rFCTD+sFD/6FD/mFHOAOD/mFAVALCTD+sFD/6FD/mFHcAcD/mFAVANCTD+rFCTD+sFD/6FD/mFHNAND/mFD/kFD/kFD/lFCBDtD+4FD/jF9DB/+g6DYHVD/mF9DBBX9LDYHLD/kFCggEDtHMD9OAcAVD/mFALD/kFCTD+rFD9QHcANAVD/mFALD/kFCTD+rFAOAVD/mFALD/kFAMD9OD9QHVDQBFTtGEmYILPdKOenHLD8dBAEDBIBDyB+t+J83EBABCNJALD8dFAEDBIBDyF+t+J83EBAKAcAVDQNVi8ZcMpySQ8c8dfb8e8fHVD8dBAEDBIBDyG+t+J83EBABCiJAVD8dFAEDBIBDyE+t+J83EBABCAJRBAICIJHIAF9JQBMMM9jFF97GXAGCGrAF9sHG9FQBCBRFEXABABDBBBHECND+rFCND+sFD/6FAECiD+sFCnD+rFCUUU/8EDtD+uFD/mFDMBBABCTJRBAFCIJHFAG9JQBMMM9TFEaCBCB8oGUkUUBHFABCEJC98ZJHBjGUkUUBGXGXAB8/BCTWHGuQBCaREABAGlCggEJCTrXBCa6QFMAFREMAEMMMFBCUNMIT9tBB"; + + // Uses bulk-memory and simd extensions + var detector = new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,3,2,0,0,5,3,1,0,1,12,1,0,10,22,2,12,0,65,0,65,0,65,0,252,10,0,0,11,7,0,65,0,253,15,26,11]); + + // Used to unpack wasm + var wasmpack = new Uint8Array([32,0,65,253,3,1,2,34,4,106,6,5,11,8,7,20,13,33,12,16,128,9,116,64,19,113,127,15,10,21,22,14,255,66,24,54,136,107,18,23,192,26,114,118,132,17,77,101,130,144,27,87,131,44,45,74,156,154,70,167]); + + if (typeof WebAssembly !== 'object') { + // This module requires WebAssembly to function + return { + supported: false, + }; + } + + var wasm = wasm_base; + + if (WebAssembly.validate(detector)) { + wasm = wasm_simd; + console.log("Warning: meshopt_decoder is using experimental SIMD support"); + } + + var instance; + + var promise = + WebAssembly.instantiate(unpack(wasm), {}) + .then(function(result) { + instance = result.instance; + instance.exports.__wasm_call_ctors(); + }); + + function unpack(data) { + var result = new Uint8Array(data.length); + for (var i = 0; i < data.length; ++i) { + var ch = data.charCodeAt(i); + result[i] = ch > 96 ? ch - 71 : ch > 64 ? ch - 65 : ch > 47 ? ch + 4 : ch > 46 ? 63 : 62; + } + var write = 0; + for (var i = 0; i < data.length; ++i) { + result[write++] = (result[i] < 60) ? wasmpack[result[i]] : (result[i] - 60) * 64 + result[++i]; + } + return result.buffer.slice(0, write); + } + + function decode(fun, target, count, size, source, filter) { + var sbrk = instance.exports.sbrk; + var count4 = (count + 3) & ~3; // pad for SIMD filter + var tp = sbrk(count4 * size); + var sp = sbrk(source.length); + var heap = new Uint8Array(instance.exports.memory.buffer); + heap.set(source, sp); + var res = fun(tp, count, size, sp, source.length); + if (res == 0 && filter) { + filter(tp, count4, size); + } + target.set(heap.subarray(tp, tp + count * size)); + sbrk(tp - sbrk(0)); + if (res != 0) { + throw new Error("Malformed buffer data: " + res); + } + }; + + var filters = { + // legacy index-based enums for glTF + 0: "", + 1: "meshopt_decodeFilterOct", + 2: "meshopt_decodeFilterQuat", + 3: "meshopt_decodeFilterExp", + // string-based enums for glTF + NONE: "", + OCTAHEDRAL: "meshopt_decodeFilterOct", + QUATERNION: "meshopt_decodeFilterQuat", + EXPONENTIAL: "meshopt_decodeFilterExp", + }; + + var decoders = { + // legacy index-based enums for glTF + 0: "meshopt_decodeVertexBuffer", + 1: "meshopt_decodeIndexBuffer", + 2: "meshopt_decodeIndexSequence", + // string-based enums for glTF + ATTRIBUTES: "meshopt_decodeVertexBuffer", + TRIANGLES: "meshopt_decodeIndexBuffer", + INDICES: "meshopt_decodeIndexSequence", + }; + + return { + ready: promise, + supported: true, + decodeVertexBuffer: function(target, count, size, source, filter) { + decode(instance.exports.meshopt_decodeVertexBuffer, target, count, size, source, instance.exports[filters[filter]]); + }, + decodeIndexBuffer: function(target, count, size, source) { + decode(instance.exports.meshopt_decodeIndexBuffer, target, count, size, source); + }, + decodeIndexSequence: function(target, count, size, source) { + decode(instance.exports.meshopt_decodeIndexSequence, target, count, size, source); + }, + decodeGltfBuffer: function(target, count, size, source, mode, filter) { + decode(instance.exports[decoders[mode]], target, count, size, source, instance.exports[filters[filter]]); + } + }; +})(); + +export { MeshoptDecoder }; diff --git a/jsm/libs/mikktspace.module.js b/jsm/libs/mikktspace.module.js new file mode 100644 index 0000000..0a1baaa --- /dev/null +++ b/jsm/libs/mikktspace.module.js @@ -0,0 +1,128 @@ +const lTextDecoder = typeof TextDecoder === 'undefined' ? (0, module.require)('util').TextDecoder : TextDecoder; + +let cachedTextDecoder = new lTextDecoder('utf-8', { ignoreBOM: true, fatal: true }); + +cachedTextDecoder.decode(); + +let cachegetUint8Memory0 = null; +function getUint8Memory0() { + if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) { + cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer); + } + return cachegetUint8Memory0; +} + +function getStringFromWasm0(ptr, len) { + return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len)); +} + +const heap = new Array(32).fill(undefined); + +heap.push(undefined, null, true, false); + +let heap_next = heap.length; + +function addHeapObject(obj) { + if (heap_next === heap.length) heap.push(heap.length + 1); + const idx = heap_next; + heap_next = heap[idx]; + + heap[idx] = obj; + return idx; +} + +function getObject(idx) { return heap[idx]; } + +function dropObject(idx) { + if (idx < 36) return; + heap[idx] = heap_next; + heap_next = idx; +} + +function takeObject(idx) { + const ret = getObject(idx); + dropObject(idx); + return ret; +} + +let cachegetFloat32Memory0 = null; +function getFloat32Memory0() { + if (cachegetFloat32Memory0 === null || cachegetFloat32Memory0.buffer !== wasm.memory.buffer) { + cachegetFloat32Memory0 = new Float32Array(wasm.memory.buffer); + } + return cachegetFloat32Memory0; +} + +let WASM_VECTOR_LEN = 0; + +function passArrayF32ToWasm0(arg, malloc) { + const ptr = malloc(arg.length * 4); + getFloat32Memory0().set(arg, ptr / 4); + WASM_VECTOR_LEN = arg.length; + return ptr; +} + +let cachegetInt32Memory0 = null; +function getInt32Memory0() { + if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) { + cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer); + } + return cachegetInt32Memory0; +} + +function getArrayF32FromWasm0(ptr, len) { + return getFloat32Memory0().subarray(ptr / 4, ptr / 4 + len); +} +/** +* Generates vertex tangents for the given position/normal/texcoord attributes. +* @param {Float32Array} position +* @param {Float32Array} normal +* @param {Float32Array} texcoord +* @returns {Float32Array} +*/ +export function generateTangents(position, normal, texcoord) { + try { + const retptr = wasm.__wbindgen_add_to_stack_pointer(-16); + var ptr0 = passArrayF32ToWasm0(position, wasm.__wbindgen_malloc); + var len0 = WASM_VECTOR_LEN; + var ptr1 = passArrayF32ToWasm0(normal, wasm.__wbindgen_malloc); + var len1 = WASM_VECTOR_LEN; + var ptr2 = passArrayF32ToWasm0(texcoord, wasm.__wbindgen_malloc); + var len2 = WASM_VECTOR_LEN; + wasm.generateTangents(retptr, ptr0, len0, ptr1, len1, ptr2, len2); + var r0 = getInt32Memory0()[retptr / 4 + 0]; + var r1 = getInt32Memory0()[retptr / 4 + 1]; + var v3 = getArrayF32FromWasm0(r0, r1).slice(); + wasm.__wbindgen_free(r0, r1 * 4); + return v3; + } finally { + wasm.__wbindgen_add_to_stack_pointer(16); + } +} + +export const __wbindgen_string_new = function(arg0, arg1) { + var ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); +}; + +export const __wbindgen_rethrow = function(arg0) { + throw takeObject(arg0); +}; + +// + +const wasmDataURI = 'data:application/octet-stream;base64,AGFzbQEAAAABiQEWYAN/f38AYAJ/fwBgAn9/AX9gAX8AYAN/f38Bf2AEf39/fwBgAX8Bf2AFf39/f38Bf2ABfwF+YAV/f39/fwBgBH9/f38Bf2ACf38BfWAAAGAHf39/f39/fwBgBX9/fX1/AGACfn8Bf2ABfQF/YAN9fX0Bf2ADf39/AX5gAX8BfWAAAXxgAXwBfAJiAhkuL21pa2t0c3BhY2VfbW9kdWxlX2JnLmpzFV9fd2JpbmRnZW5fc3RyaW5nX25ldwACGS4vbWlra3RzcGFjZV9tb2R1bGVfYmcuanMSX193YmluZGdlbl9yZXRocm93AAMDeXgGAwkABQQCFAcCEwkVAgULBwUPAAAFAAAAAAABBQ4ADQABAQUAAAsBAAAABQIFAgUFAwEBAQEAAQUEERIHAAIAAAABAAAEAAMDAQADAAoGBgMDAwMDAwMBAgMBAwMBAQEKAQEEAgIAAQAMAgIGEAECBgIGCAgIAwEEBQFwASEhBQMBABEGCQF/AUGAgMAACwdlBQZtZW1vcnkCABBnZW5lcmF0ZVRhbmdlbnRzACEfX193YmluZGdlbl9hZGRfdG9fc3RhY2tfcG9pbnRlcgBuEV9fd2JpbmRnZW5fbWFsbG9jAFAPX193YmluZGdlbl9mcmVlAGQJJgEAQQELIHg6cXJ4MnN0eHhEYHh1eXhlD0B3Xh0kS2lddnVtbHh3CvXrAXiUUQQqfwN+Cn0BfCMAQeAGayIBJAAgACgCCCIIQQluIRMQCSE4AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIAhBCU8EQCABQbgDaiATQQNsIgpBARA4IAEgCjYCyAMgASABKQO4AzcDwAMgAUHgA2pByAAQWhogAUGYBmogAUHgA2pByAAQTiABQdADaiABQZgGaiATEEUgASgC0AMhDiABKALYAyEMIAFBwANqKAIAIQMCQAJAAkACQAJAAkACQCAAKAIIQQlPBEAgASgCyAMhBSAOQcYAaiEEA0AgAiAMRg0CIARBAjoAACAEQX5qQYACOwEAIARBemogCTYCACAEQXJqIAI2AgAgAkEAEGYhBiAJIAVPDQMgAyAGNgIAIAJBARBmIQYgCUEBaiAFTw0EIANBBGogBjYCACACQQIQZiEGIAlBAmogBU8NBSADQQhqIAY2AgAgBEHIAGohBCADQQxqIQMgCUEDaiEJIAJBAWoiAiAAKAIIQQluSQ0ACwsgOLYhNiATQX9qISUgDkE8aiECQQAhBANAIAQgDEYNBSACQQA2AgAgAkHIAGohAiATIARBAWoiBEcNAAsgAUHAA2ooAgAhBSABQbADakEAEGEgAUG4BWogACABKAKwAyABKAK0AxAxIAEqAsAFIS8gASoCvAUhMiABKgK4BSE0IApBAk8NBSAvITAgMiEuIDQhMQwGCyAMIAxB7IXAABA/AAsgCSAFQfyFwAAQPwALIAlBAWogBUGMhsAAED8ACyAJQQJqIAVBnIbAABA/AAsgDCAMQayGwAAQPwALIAVBBGohAiAKQX9qIQQgNCExIDIhLiAvITADQCABQagDaiACKAIAEGEgAUGYBmogACABKAKoAyABKAKsAxAxAkAgNCABKgKYBiIzXgRAIDMhNAwBCyAxIDNdQQFzDQAgMyExCwJAIDIgASoCnAYiM14EQCAzITIMAQsgLiAzXUEBcw0AIDMhLgsCQCAvIAEqAqAGIjNeBEAgMyEvDAELIDAgM11BAXMNACAzITALIAJBBGohAiAEQX9qIgQNAAsgASAvOALABSABIDI4ArwFIAEgNDgCuAULIAEgMDgC6AMgASAuOALkAyABIDE4AuADIAFBoAZqIAFBwAVqKAIANgIAIAEgASkDuAU3A5gGIAFBiAZqIAFB4ANqIAFBmAZqEBVBASEGAn8gAUG4BWpBBHIgASoCjAYiLyABKgKIBiIyXkEBcyAvIAEqApAGIi9eRXJFDQAaQQEhCyAvIDJeQQFzBEBBACEGIDEhLiABQbgFagwBC0EAIQsgMCEuQQAhBiABQcAFagsqAgAhMCABQaADaiAKQQEQOCABIAo2AtAFIAEgASkDoAM3A8gFIAFBmANqQYAQQQEQOCABQYAQNgLgBSABIAEpA5gDNwPYBSABQZADakGAEEEBEDggAUGAEDYC8AUgASABKQOQAzcD6AUgAUGIA2pBgBBBARA4IAFBgBA2AoAGIAEgASkDiAM3A/gFIAhBCU8EQCABQZgGakEAQQRBCCAGGyALG2ohByAFIQIgCiEDA0AgAUGAA2ogAigCABBhIAFBmAZqIAAgASgCgAMgASgChAMQMSAwIC4gByoCABA8IQQgAUHoBWooAgAgASgC8AUiESAETQ0WIARBAnRqIgQgBCgCAEEBajYCACACQQRqIQIgA0F/aiIDDQALCyABQdgFaigCACABKALgBUUNFUEAIQJBADYCAEEBIQQDQCABQfgCaiABQdgFahBjIAEoAvwCIgMgBEF/aiIHTQ0UIAEoAvgCIAJqKAIAIQMgAUHwAmogAUHoBWoQYyABKAL0AiINIAdNDRMgASgC8AIgAmooAgAhByABQdgFaigCACENIAEoAuAFIhEgBE0NEiACIA1qQQRqIAMgB2o2AgAgAkEEaiECIARBAWoiBEGAEEcNAAsgCEEJTwRAQQAhBCABQZgGakEAQQRBCCAGGyALG2ohCCAFIQMDQCABQegCaiADKAIAEGEgAUGYBmogACABKALoAiABKALsAhAxIDAgLiAIKgIAEDwhAiABQeACaiABQdgFahBjIAEoAuQCIgYgAk0NEiABQcgFaigCACABKALQBSINIAJBAnQiBiABKALgAmooAgAiC00NESABQdgCaiABQfgFahBjIAEoAtwCIg0gAk0NECALQQJ0aiABKALYAiAGaigCAEECdGogBDYCACABQfgFaigCACELIAEoAoAGIgcgAk0NDyAGIAtqIgIgAigCAEEBajYCACADQQRqIQMgCiAEQQFqIgRHDQALCyABQdACaiABQegFahBjIAEoAtQCRQ0MIAEoAtACKAIAIQNBBCECQQEhBANAIAFByAJqIAFB6AVqEGMgASgCzAIiCiAETQ0MIAMgASgCyAIgAmooAgBJBEAgAUHAAmogAUHoBWoQYyABKALEAiIDIARNDQwgASgCwAIgAmooAgAhAwsgAkEEaiECIARBAWoiBEGAEEcNAAsgAUHgA2oiAkIANwIAIAJBCGpCADcCACABQaAGaiABQegDaikDADcDACABIAEpA+ADNwOYBiABQYgGaiABQZgGaiADEEEgASgCiAYhCiABKAKQBiELQQAhBgNAIAFBuAJqIAFB2AVqEGMgASgCvAIiAyAGTQ0KIAFByAVqKAIAIQIgASgC0AUiBCAGQQJ0IgggASgCuAJqKAIAIgNNDQkgAUGwAmogAUHoBWoQYyABKAK0AiIEIAZNDQggASgCsAIgCGooAgAiCEECTwRAIAIgA0ECdGohBEEAIQMgCiECA0AgAUGoAmogBSAEKAIAIgdBAnRqKAIAEGEgAUGYBmogACABKAKoAiABKAKsAhAxIAMgC0YNCSACIAEoApgGNgIAIAJBBGogASgCnAY2AgAgASgCoAYhDSACQQxqIAc2AgAgAkEIaiANNgIAIARBBGohBCACQRBqIQIgCCADQQFqIgNHDQALIAUgCiAAQQAgCEF/ahAECyAGQQFqIgZBgBBHDQALIBNBASATQQFLGyEFIA5BPGohAyABQYgGahBSIAFB+AVqKAIAGiABQfgFahBWIAFB6AVqKAIAGiABQegFahBWIAFB2AVqKAIAGiABQdgFahBWIAFByAVqKAIAGiABQcgFahBWQQAhAkEAIQRBACEGQQAhBwNAIAFBoAJqIAFBwANqEGMgASgCpAIiCiAETQ0GIAEoAqACIAJqKAIAIQogAUGYAmogAUHAA2oQYyABKAKcAiILIARBAWpNDQUgASgCmAIgAmpBBGooAgAhCyABQZACaiABQcADahBjIAEoApQCIgggBEECak0NBCABKAKQAiACakEIaigCACEIIAFBiAJqIAoQYSABQYgGaiAAIAEoAogCIAEoAowCEDEgAUGAAmogCxBhIAFB4ANqIAAgASgCgAIgASgChAIQMSABQfgBaiAIEGEgAUGYBmogACABKAL4ASABKAL8ARAxAkACQCABQYgGaiABQeADahALDQAgAUGIBmogAUGYBmoQCw0AIAFB4ANqIAFBmAZqEAtFDQELIAwgBk0NBCADIAMoAgBBAXI2AgAgB0EBaiEHCyACQQxqIQIgBEEDaiEEIANByABqIQMgBSAGQQFqIgZHDQALIA4gAUHAA2ooAgAgEyAHayIKIBMQECABQcADaigCACELAkACQCAKRQRAQX8hBgwBC0EAIQYDQCAOIAZByABsaiIDQn83AgAgA0EIakF/NgIAIANBDGpCADcCACADQRRqQQA2AgAgA0E8aiADQRhqIQIgAygCPCEEQQMhAwNAIAJCADcCACACQRhqQgA3AgAgAkEQakIANwIAIAJBCGpCADcCACAEQQRyIQQgA0F/aiIDDQALIAQ2AgAgBkEBaiIGIApHDQALIAFBpAZqIQYgAUHsA2ohDEEAIQUDQCABQfABaiALIAVBDGxqIgMoAgAQYSABQagFaiAAIAEoAvABIAEoAvQBEDEgAUHoAWogA0EEaiICKAIAEGEgAUG4BWogACABKALoASABKALsARAxIAFB4AFqIANBCGoiBCgCABBhIAFByAVqIAAgASgC4AEgASgC5AEQMSABQdgBaiADKAIAEGEgACABKALYASABKALcARA9ISsgAUHQAWogAigCABBhIAAgASgC0AEgASgC1AEQPSEsIAFByAFqIAQoAgAQYSAAIAEoAsgBIAEoAswBED0hLSABQegDaiIDIAFBwAVqKAIANgIAIAEgASkDuAU3A+ADIAFBoAZqIgQgAUGwBWoiAigCADYCACABIAEpA6gFNwOYBiABQdgFaiABQeADaiABQZgGahAVIAMgAUHQBWooAgA2AgAgASABKQPIBTcD4AMgBCACKAIANgIAIAEgASkDqAU3A5gGIAFB6AVqIAFB4ANqIAFBmAZqEBUgAyABQeAFaiIIKAIANgIAIAEgASkD2AU3A+ADIAFBwAFqIAFB4ANqIAwQaCAtQiCIp74gK0IgiKe+IjCTIS4gASgCwAEiAiABKALEASIHRwRAA0AgAiAuIAIqAgCUOAIAIAcgAkEEaiICRw0ACwsgLEIgiKe+IDCTITAgBCABQfAFaiIHKAIANgIAIAEgASkD6AU3A5gGIAFBuAFqIAFBmAZqIAYQaCABKAK4ASICIAEoArwBIg1HBEADQCACIDAgAioCAJQ4AgAgDSACQQRqIgJHDQALCyAtp74gK6e+Ii+TITEgAUH4BWogAUHgA2ogAUGYBmoQFSADIAgoAgA2AgAgASABKQPYBTcD4AMgAUGwAWogAUHgA2ogDBBoIAEoArABIgIgASgCtAEiA0cEQCAxjCEyA0AgAiACKgIAIDKUOAIAIAMgAkEEaiICRw0ACwsgLKe+IC+TIS8gBCAHKAIANgIAIAEgASkD6AU3A5gGIAFBqAFqIAFBmAZqIAYQaCABKAKoASICIAEoAqwBIgNHBEADQCACIC8gAioCAJQ4AgAgAyACQQRqIgJHDQALCyABQYgGaiABQeADaiABQZgGahAWIA4gBUHIAGxqIgMgAygCPCAvIC6UIDAgMZSTIi5DAAAAAF5BA3RyNgI8AkAgLhBvRQ0AIAFB+AVqEAwgAUGIBmoQDCEvQwAAgD9DAACAvyADQTxqIggoAgBBCHEbITCRIjEQbwRAIAQgAUGABmooAgA2AgAgASABKQP4BTcDmAYgAUGgAWogAUGYBmogBhBoIAEoAqABIgIgASgCpAEiB0cEQCAwIDGVITIDQCACIDIgAioCAJQ4AgAgByACQQRqIgJHDQALCyADIAEpA5gGNwIYIANBIGogBCgCADYCAAsgL5EiLxBvBEAgBCABQZAGaigCADYCACABIAEpA4gGNwOYBiABQZgBaiABQZgGaiAGEGggASgCmAEiAiABKAKcASIHRwRAIDAgL5UhMANAIAIgMCACKgIAlDgCACAHIAJBBGoiAkcNAAsLIAMgASkDmAY3AiQgA0EsaiAEKAIANgIACyADIC8gLosiLpU4AjQgAyAxIC6VIi44AjAgLhBvRQ0AIANBNGoqAgAQb0UNACAIIAgoAgBBe3E2AgALIAVBAWoiBSAKRw0ACyAKQX9qIgZFDQELQQAhAgNAAn8gAkEBaiIDIA4gAkHIAGxqIgUoAjggBUGAAWooAgBHDQAaIA4gA0HIAGxqKAI8IgQgBSgCPCIFckEBcSAFQQhxQQN2IARBCHFBA3ZGckUEQAJAAkAgBEEEcQ0AIAAgCyACQQxsahAoIAAgCyADQQxsahAoYEEBc0UNACADIQUgAiEDDAELIAIhBQsgDiADQcgAbGoiAyADKAI8QXdxIgQ2AjwgAyAOIAVByABsaigCPEEIcSAEcjYCPAsgAkECagsiAiAGSQ0ACwsgAUHgA2oiA0EANgIIIANCADcCACABQaAGaiIDIAFB6ANqIgIoAgA2AgAgASABKQPgAzcDmAYgAUGIBmogAUGYBmogCkEDbCIbEEIgDiABKAKIBiALIAoQBiABQYgGahBVIAFB4ANqIgVCADcCACAFQQVqQgA3AAAgAyACKQMANwMAIAEgASkD4AM3A5gGIAFBqARqIAFBmAZqIBsQQSABQZABaiAbQQEQOCABIBs2AsAEIAEgASkDkAE3A7gEIAEoAtADIgsgASgCqAQiDiABQbgEaigCACABQcADaigCACAKEBIhBiABQeADahBKIAEoAoAEIQMgAS0AhAQhAiABQbAGakKAgICAgICAwD83AwAgASACOgC8BiABIAM2ArgGIAFCgICAgICAgMA/NwOoBiABQoCAgICAgIDAPzcDoAYgAUKAgID8AzcDmAYgAUHIBGogAUGYBmogCRBGIAEoAsgEIRQgASgC0AQhIiABQcADaigCACEcAkAgBkEBSA0AQQAhAiAOIQQgBiEDA0AgBCgCACIFIAIgAiAFSRshAiAEQRBqIQQgA0F/aiIDDQALIAJFDQAgAUHgA2oQSiABQZgGaiABQeADakEoEE4gAUHYBGogAUGYBmogAhBGIAFB4ANqIgNCgICAgMAANwIAIANBCGpCADcCACABQaAGaiIDIAFB6ANqKQMANwMAIAEgASkD4AM3A5gGIAFB6ARqIAFBmAZqIAIQQyABQYgBaiACQQEQOCABIAI2AoAFIAEgASkDiAE3A/gEIAFBlAZqIRkgAUGoBmohFSABQaQGaiEeIAFB4ANqQQRyIREDQAJAIA4gI0EEdGoiDSgCAEEBSA0AIA1BDGohJiANQQhqIScgDUEEaiEkQQAhH0EAIQwCQAJAAkACQANAICQoAgAgH0ECdGooAgAhAiABQgA3A+gDIAFCgICAgMAANwPgAyABQQA2ApAFIAFCADcDiAUgAUEANgKgBSABQgA3A5gFIAFBADYCsAUgAUIANwOoBSABQYABaiAcAn8gDSALIAJByABsaiIIKAIMRgRAQQEhIEEADAELIA0gCEEQaigCAEYEQEEBISBBAQwBC0ECQX8gCEEUaigCACANRiIgGwsiKCACQQNsakECdGooAgAQYSABQYgFaiAAIAEoAoABIAEoAoQBEC8gAUGQBmoiBCAIQSBqKAIANgIAIAEgCCkCGDcDiAYgAUGIBWogCEEYahARIS4gAyABQZAFaiIQKAIANgIAIAEgASkDiAU3A5gGIAFB+ABqIAFBmAZqIB4QaCABKAJ4IgIgASgCfCIFRwRAA0AgAiAuIAIqAgCUOAIAIAUgAkEEaiICRw0ACwsgAUGYBWogAUGIBmogAUGYBmoQFSAEIAhBLGooAgA2AgAgASAIKQIkNwOIBiABQYgFaiAIQSRqEBEhLiADIBAoAgA2AgAgASABKQOIBTcDmAYgAUHwAGogAUGYBmogHhBoIAEoAnAiAiABKAJ0IgVHBEADQCACIC4gAioCAJQ4AgAgBSACQQRqIgJHDQALCyABQagFaiABQYgGaiABQZgGahAVIAMgAUGgBWoiAigCADYCACABIAEpA5gFNwOYBiABQZgGahBRBEAgAyACKAIANgIAIAEgASkDmAU3A5gGIAFBmAVqIAFBmAZqECMLIAMgAUGwBWoiAigCADYCACABIAEpA6gFNwOYBiABQZgGahBRBEAgAyACKAIANgIAIAEgASkDqAU3A5gGIAFBqAVqIAFBmAZqECMLQQAhBUEAIQkCQAJAAkACQAJAAkACQCANKAIAQQFIDQAgCCgCOCEWIAhBPGohGkEAIQcDQCALICQoAgAgB0ECdGooAgAiEkHIAGxqIg8oAjghFyAEIA9BIGooAgA2AgAgASAPKQIYNwOIBiABQYgFaiAPQRhqEBEhLiADIBAoAgA2AgAgASABKQOIBTcDmAYgAUHoAGogAUGYBmogHhBoIAEoAmgiAiABKAJsIhhHBEADQCACIC4gAioCAJQ4AgAgGCACQQRqIgJHDQALCyABQegFaiABQYgGaiABQZgGahAVIAQgD0EsaigCADYCACABIA8pAiQ3A4gGIAFBiAVqIA9BJGoQESEuIAMgECgCADYCACABIAEpA4gFNwOYBiABQeAAaiABQZgGaiAeEGggASgCYCICIAEoAmQiGEcEQANAIAIgLiACKgIAlDgCACAYIAJBBGoiAkcNAAsLIAFB+AVqIAFBiAZqIAFBmAZqEBUgAyABQfAFaiICKAIANgIAIAEgASkD6AU3A5gGIAFBmAZqEFEEQCADIAIoAgA2AgAgASABKQPoBTcDmAYgAUHoBWogAUGYBmoQIwsgAyABQYAGaiICKAIANgIAIAEgASkD+AU3A5gGIAFBmAZqEFEEQCADIAIoAgA2AgAgASABKQP4BTcDmAYgAUH4BWogAUGYBmoQIwsgDygCPCAaKAIAckEEcSAWIBdGckVBACABQZgFaiABQegFahARIDZeQQFzIAFBqAVqIAFB+AVqEBEgNl5BAXNyG0UEQCABQfgEaigCACABKAKABSIPIAlNDQMgCUECdGogEjYCACAJQQFqIQkLIAdBAWoiByANKAIASA0ACyAJQQFNDQAgAUH4BGooAgBBACAJQX9qQcrLgRMQHgsgASAJNgLgAyABQZgGaiABQfgEahA5IBEoAgAaIBEQViARQQhqIAMoAgA2AgAgESABKQOYBjcCACAMBEADQCABKALwBCICIAVNDQMgBSABQeADaiABKALoBCAFQQR0ahAwIgJBAXNqIQUgAkVBACAFIAxJGw0ACyACDQYLIAEoAvAEIgIgDE0NAiAMQQR0IgIgASgC6ARqIAk2AgAgAUGYBmogERA5IAEoAvAEIgcgDE0NAyABKALoBCACaiICQQRqIgcoAgAaIAcQViACQQxqIAMoAgA2AgAgAiABKQOYBjcCBCARKAIAISkgJygCACEaIAFBmAZqQSUQWhogCUEBSARAQwAAAAAhLgwFC0EAIQdDAAAAACEuA0AgCyApIAdBAnRqKAIAIgJByABsaiIPLQA8QQRxRQRAQQAhEiABQQA2AsAFIAFCADcDuAUgAUEANgLQBSABQgA3A8gFIAFBADYC4AUgAUIANwPYBQJ/IBogHCACQQNsIhdBAnRqIgIoAgBGBEBBACEQQQEMAQtBASESQQEhEEEBIAJBBGooAgAgGkYNABpBAkF/IAJBCGooAgAiAiAaRiISGyEQIAIgGkcLIR0gAUHYAGogHCAQIBdqQQJ0aiIqKAIAEGEgAUG4BWogACABKAJYIAEoAlwQLyABQYAGaiIWIA9BIGooAgA2AgAgASAPKQIYNwP4BSABQbgFaiAPQRhqEBEhMCAEIAFBwAVqIhgoAgA2AgAgASABKQO4BTcDiAYgAUHQAGogAUGIBmogGRBoIAEoAlAiAiABKAJUIiFHBEADQCACIDAgAioCAJQ4AgAgISACQQRqIgJHDQALCyABQegFaiABQfgFaiABQYgGahAVIAEqAugFITMgASoC7AUhLyABKgLwBSEyIBYgD0EsaigCADYCACABIA8pAiQ3A/gFIAFBuAVqIA9BJGoQESEwIAQgGCgCADYCACABIAEpA7gFNwOIBiABQcgAaiABQYgGaiAZEGggASgCSCICIAEoAkwiIUcEQANAIAIgMCACKgIAlDgCACAhIAJBBGoiAkcNAAsLIAFB6AVqIAFB+AVqIAFBiAZqEBUgASoC6AUhNCABKgLsBSEwIAEqAvAFITEgASAyOAKQBiABIC84AowGIAEgMzgCiAYgAUGIBmoQUQRAIAEgMjgCkAYgASAvOAKMBiABIDM4AogGIAFB+AVqIAFBiAZqECMgASoC+AUhMyABKgKABiEyIAEqAvwFIS8LIAEgMTgCkAYgASAwOAKMBiABIDQ4AogGIAFBiAZqEFEEQCABIDE4ApAGIAEgMDgCjAYgASA0OAKIBiABQfgFaiABQYgGahAjIAEqAvgFITQgASoCgAYhMSABKgL8BSEwCyAcIBBBAWpBACAdGyAXakECdGooAgAhHSAqKAIAIQIgAUFAayAcIBBBf2pBAiASGyAXakECdGooAgAQYSABQYgGaiAAIAEoAkAgASgCRBAxIAEpA4gGISsgASgCkAYhFyABQThqIAIQYSABQYgGaiAAIAEoAjggASgCPBAxIAEoAogGIQIgASgCjAYhECABKAKQBiESIAFBMGogHRBhIAFBiAZqIAAgASgCMCABKAI0EDEgASkDiAYhLCABKAKQBiEdIAEgFzYCgAYgASArNwP4BSABIBI2ApAGIAEgEDYCjAYgASACNgKIBiABQcgFaiABQfgFaiABQYgGahAVIAEgHTYCgAYgASAsNwP4BSABIBI2ApAGIAEgEDYCjAYgASACNgKIBiABQdgFaiABQfgFaiABQYgGahAVIBYgAUHQBWoiECgCADYCACABIAEpA8gFNwP4BSABQbgFaiABQcgFahARITUgBCAYKAIANgIAIAEgASkDuAU3A4gGIAFBKGogAUGIBmogGRBoIAEoAigiAiABKAIsIhJHBEADQCACIDUgAioCAJQ4AgAgEiACQQRqIgJHDQALCyABQcgFaiABQfgFaiABQYgGahAVIAQgECgCADYCACABIAEpA8gFNwOIBiABQYgGahBRBEAgBCAQKAIANgIAIAEgASkDyAU3A4gGIAFByAVqIAFBiAZqECMLIBYgAUHgBWoiECgCADYCACABIAEpA9gFNwP4BSABQbgFaiABQdgFahARITUgBCAYKAIANgIAIAEgASkDuAU3A4gGIAFBIGogAUGIBmogGRBoIAEoAiAiAiABKAIkIhJHBEADQCACIDUgAioCAJQ4AgAgEiACQQRqIgJHDQALCyABQdgFaiABQfgFaiABQYgGahAVIAQgECgCADYCACABIAEpA9gFNwOIBiABQYgGahBRBEAgBCAQKAIANgIAIAEgASkD2AU3A4gGIAFB2AVqIAFBiAZqECMLQwAAgD8gAUHIBWogAUHYBWoQESI1QwAAgL+XIDVDAACAP14buxAOIA8qAjQhNSAPKgIwITcgFiADKAIANgIAIAEgASkDmAY3A/gFIAEgMjgCkAYgASAvOAKMBiABIDM4AogGIAFBGGogAUGIBmogGRBotiEvIAEoAhgiAiABKAIcIg9HBEADQCACIAIqAgAgL5Q4AgAgDyACQQRqIgJHDQALCyABQZgGaiABQfgFaiABQYgGahAWIBYgFUEIaiIPKAIANgIAIAEgFSkCADcD+AUgASAxOAKQBiABIDA4AowGIAEgNDgCiAYgAUEQaiABQYgGaiAZEGggASgCECICIAEoAhQiEEcEQANAIAIgAioCACAvlDgCACAQIAJBBGoiAkcNAAsLIAFB6AVqIAFB+AVqIAFBiAZqEBYgDyABQfAFaigCADYCACAVIAEpA+gFNwIAIAEgNyAvlCABKgKkBpI4AqQGIAEgNSAvlCABKgK0BpI4ArQGIC4gL5IhLgsgCSAHQQFqIgdHDQALDAQLIAkgD0HcgsAAED8ACyAFIAJB7ILAABA/AAsgDCACQfyCwAAQPwALIAwgB0GMg8AAED8ACyAEIAMoAgA2AgAgASABKQOYBjcDiAYgAUGIBmoQUQRAIAQgAygCADYCACABIAEpA5gGNwOIBiABQZgGaiABQYgGahAjCyAEIBVBCGoiAigCADYCACABIBUpAgA3A4gGIAFBiAZqEFEEQCAEIAIoAgA2AgAgASAVKQIANwOIBiABQfgFaiABQYgGahAjIAIgAUGABmooAgA2AgAgFSABKQP4BTcCAAsgLkMAAAAAXkEBc0UEQCABIAEqAqQGIC6VOAKkBiABIAEqArQGIC6VOAK0BgsgASgC4AQiAiAMTQ0DIAEoAtgEIAxBKGxqIAFBmAZqQSgQTiAMQQFqIQwLICAEQCAIKAJAIAggKGpBxABqLQAAaiICICJPDQICfyAUIAJBKGxqIgIoAiBBAUcEQCABKALgBCIEIAVNDQYgAiABKALYBCAFQShsakEoEDsaQQEMAQsgASgC4AQiBCAFTQ0GIAFBmAZqIAIgASgC2AQgBUEobGoQBSACIAFBmAZqQSgQTkECCyEFIAJBIGogBTYCACACICYtAAA6ACQgESgCABogERBWIB9BAWoiHyANKAIASA0BDAYLC0F/QQRBrIPAABA/AAsgAiAiQbyDwAAQPwALIAwgAkGcg8AAED8ACyAFIARBzIPAABA/AAsgBSAEQdyDwAAQPwALICNBAWoiIyAGRw0ACyABQfgEaigCABogAUH4BGoQViABQegEahBJIAFB6ARqEFIgAUHYBGoQVCABKALIBCEUIAEoAtADIQsLIAFBwANqKAIAIQUgCiATSARAIBtBAUghCSAKIQMDQCALIANByABsaiIELQA8QQJxRQRAIANBA2whDCAEQUBrIQhBACEGA0ACQCAJDQAgBSAGIAxqQQJ0aigCACEOQQAhAgNAAkAgAiAOIAUgAkECdGooAgAiB0ciDWohAiAHIA5GDQAgAiAbSA0BCwsgDQ0AIBQgCCgCACAEIAZqQcQAai0AAGpBKGxqIBQgCyACQQNuIg5ByABsaiIHKAJAIAcgAiAOQQNsa2pBxABqLQAAakEobGpBKBA7GgsgBkEBaiIGQQNHDQALCyADICVGIANBAWohA0UNAAsLIApBAU4EQCALQcQAaiEEQQAhBQNAAkAgCyAFQcgAbGoiAy0APEECcUUNACABQQA2AugDIAFCADcD4AMgAUEIaiADKAI4IgYCf0EBQQEgA0HFAGotAABBH3F0QQEgAy0AREEfcXRyQQEgA0HGAGotAABBH3F0ciICQQJxRQ0AGkECIAJBBHFFDQAaQQBBAyACQQhxGwsiDhBmEGEgAUGYBmogACABKAIIIAEoAgwQMSABQegDaiABQaAGaigCADYCACABIAEpA5gGNwPgAyADQUBrIQNBACECA0AgASAGIAIgBGotAAAiCRBmEGEgAUGYBmogACABKAIAIAEoAgQQMSABQZgGaiABQeADahALBEAgFCADKAIAIgMgDmpBKGxqIBQgAyAJakEobGpBKBA7GgwCCyACQQFqIgJBA0cNAAsLIARByABqIQQgBUEBaiIFIApHDQALCyABKALQBCEFQQAhBkEAIQkDQEEAIAUgCWsiAyADIAVLGyEKIBQgCUEobGohDkEAIQNBACEEA0AgBCAKRg0DIAMgDmoiAikCACErIAEgAkEIaigCADYC6AMgASArNwLgAyAAIAFB4ANqIAJBDGoqAgAgAkEcaioCACACQSRqLQAAEB8gBEEBaiEEIANBKGoiA0H4AEcNAAsgBCAJaiEJIAZBAWoiBiATRw0ACyABQcgEahBUIAFBuARqKAIAGiABQbgEahBWIAFBqARqEFIgAUHQA2oQUyABQcADaigCABogAUHAA2oQVkEBIQILIAFB4AZqJAAgAg8LIAQgCWogBUHMgsAAED8ACyAGIAxBvILAABA/AAsgBEECaiAIQayCwAAQPwALIARBAWogC0GcgsAAED8ACyAEIApBjILAABA/AAsgCyALQdyFwAAQPwALIAYgBEHMhcAAED8ACyADIARBvIXAABA/AAsgBiADQayFwAAQPwALIAQgA0GchcAAED8ACyAEIApBjIXAABA/AAtBAEEAQfyEwAAQPwALIAIgB0HshMAAED8ACyACIA1B3ITAABA/AAsgCyANQcyEwAAQPwALIAIgBkG8hMAAED8ACyAEIBFBrITAABA/AAsgBEF/aiANQZyEwAAQPwALIARBf2ogA0GMhMAAED8ACyAEIBFB7IPAABA/AAtBAEEAQfyDwAAQPwALvQECAX8CfCMAQTBrIgEkAAJAAkBBgAhEAAAAAACAdz4iAkQxY2IaYbTgPSIDob1CNIinQf8PcWtBEEoNAAwBC0GACEQA0HLPpXd3PkRzcAMuihmzOyIDob1CNIinQf8PcWtBMkgEQEQA0HLPpXd3PiECDAELRM7Ocs+ld3c+IgJE7TI+8kfXGbsiA6EaCyAARM7Ocs+ld3c+OQMAIABBAjYCCCAAIAJEzs5yz6V3dz6hIAOhOQMQIAFBMGokAAuHCQMIfwN+CH0jAEGgAWsiBSQAIAVBOGogASADQQR0aiIHQQhqKAIAIgY2AgAgBUHIAGogBjYCACAFIAcpAgAiDTcDMCAFIA03A0BBASEMIANBAWoiCiAETARAIAEgCkEEdGohCQNAQQAhBkEAIQgDQAJAAkAgBUEwaiAGaioCACAGIAlqKgIAIhBeBEAgBUEwaiAIQQJ0aiEHDAELIAVBQGsgBmoiByoCACAQXUEBcw0BCyAHIBA4AgALIAhBAWohCCAGQQRqIgZBDEcNAAsgCUEQaiEJIApBAWoiCiAETA0ACwsCQCAFKgJEIAUqAjSTIhIgBSoCQCAFKgIwkyIQXkEBc0VBACASIAUqAkggBSoCOJMiEV4bDQBBACEMIBEgEF5BAXMNAEECIQwLAkAgDEECdCIGIAVBQGtqKgIAIhIgBUEwaiAGaioCACIQkkMAAAA/lCIRIBJgRUEAIBEgEF9BAXMbRQRAIAMgBEoNASADIQcDQCAFQShqIAAgASAHQQR0aigCDEECdGoiCigCACIGEGEgBUHQAGogAiAFKAIoIAUoAiwQMSAFQSBqIAYQYSAFQeAAaiACIAUoAiAgBSgCJBAvIAVBGGogBhBhIAIgBSgCGCAFKAIcED0hDQJAIAcgA0wNACANp74hEyANQiCIp74hFEEBIQkgBSoCaCEVIAUqAmQhFiAFKgJgIRcgBSoCWCERIAUqAlQhEiAFKgJQIRAgAyEGA0AgBUEQaiAAIAEgBkEEdGooAgxBAnRqIggoAgAiCxBhIAVB8ABqIAIgBSgCECAFKAIUEDEgBUEIaiALEGEgBUGAAWogAiAFKAIIIAUoAgwQLyAFIAsQYSACIAUoAgAgBSgCBBA9IQ0CQAJAIBAgBSoCcFwNACASIAUqAnRcDQAgESAFKgJ4XA0AIBcgBSoCgAFcDQAgFiAFKgKEAVwgFCANQiCIp75cciATIA2nvlxyDQAgFSAFKgKIAVwNAEEAIQkMAQsgBkEBaiEGCyAGIAdIQQAgCUEBcRsNAAsgCUEBcQ0AIAogCCgCADYCAAsgB0EBaiIHIARMDQALDAELIAMiCCAEIgZIBEAgDEECdCEJA0BBASEHAkAgCCAGTg0AA0AgCCABIAhBBHRqIAlqKgIAIBFdIgdqIQggB0EBcw0BIAggBkgNAAsLAkAgCCAGTg0AA0ACQCAGIAEgBkEEdGogCWoqAgAgEV0iC0EBcyIKayEGIAsNACAIIAZIDQELCyAHIApyDQAgASAIQQR0aiIHKQIAIQ4gASAGQQR0aiILQQhqIgopAgAhDyAHIAspAgA3AgAgB0EIaiIHKQIAIQ0gByAPNwIAIAogDTcCACALIA43AgAgBkF/aiEGIAhBAWohCAsgCCAGSA0ACwsCQCAGIAhHBEAgBiEHIAghBgwBCyABIAZBBHRqIAxBAnRqKgIAIBFdRQRAIAZBf2ohBwwBCyAGIQcgBkEBaiEGCyAHIANKBEAgACABIAIgAyAHEAQLIAYgBE4NACAAIAEgAiAGIAQQBAsgBUGgAWokAAunCAMNfwF+An0jAEEwayIDJAAgAEElEFoiBUEQaiEHAkACQCABKgIMIAIqAgxcDQAgASoCHCACKgIcXA0AIAJBDGohCCABQQxqIQlBfCEGIAIhCiACIQAgASELIAEhBANAIAZBAWoiDiAGTwRAAkAgBCAJRgRAIAtBEGohDCAJQQxqIQkgC0EMaiILIQQMAQsgBEEEaiEMCwJAIAAgCEYEQCAKQRBqIQ0gCEEMaiEIIApBDGoiCiEADAELIABBBGohDQsgACoCACERIAQqAgAgDiEGIA0hACAMIQQgEVsNAQwCCwsgAkEcaiEIIAFBHGohCUF8IQYgAkEQaiIKIQAgAUEQaiIOIQsgDiEEAkADQCAGQQFqIg8gBkkNAQJAIAQgCUYEQCALQRBqIQwgCUEMaiEJIAtBDGoiCyEEDAELIARBBGohDAsCQCAAIAhGBEAgCkEQaiENIAhBDGohCCAKQQxqIgohAAwBCyAAQQRqIQ0LIAAqAgAhESAEKgIAIA8hBiANIQAgDCEEIBFbDQALDAELIAUgASgCHDYCHCAFIAEpAgA3AgAgByAOKQIANwIAIAVBCGogAUEIaikCADcCACAHQQhqIA5BCGooAgA2AgAMAQsgBSABKgIMIAIqAgySQwAAAD+UOAIMIAUgASoCHCACKgIckkMAAAA/lDgCHCADQRhqIgwgAUEIaigCACIANgIAIAMgASkCACIQNwMQIAUgEDcCACAFQQhqIAA2AgAgA0EoaiINIAJBCGooAgA2AgAgAyACKQIANwMgIANBCGpBAEEDEGggAygCCCIAIAMoAgwiBEkEQCAEIABrIQYgBSAAQQJ0IgRqIQAgA0EgaiAEaiEEA0AgACAEKgIAIAAqAgCSOAIAIABBBGohACAEQQRqIQQgBkF/aiIGDQALCyAMIAFBGGooAgA2AgAgAyABKQIQNwMQIA0gAkEYaigCADYCACADIAIpAhA3AyAgA0EAQQMQaCADKAIAIgAgAygCBCIBSQRAIAEgAGshBiAAQQJ0IgEgA0EQamohACADQSBqIAFqIQQDQCAAIAQqAgAgACoCAJI4AgAgAEEEaiEAIARBBGohBCAGQX9qIgYNAAsLIAcgAykDEDcCACAHQQhqIANBGGooAgA2AgACQAJAIAUqAgCLQwAAgABeDQAgBSoCBItDAACAAF4NACAFKgIIi0MAAIAAXkUNAQsgA0EoaiAFQQhqKAIANgIAIAMgBSkCADcDICAFIANBIGoQIwsCQCAFKgIQi0MAAIAAXg0AIAUqAhSLQwAAgABeDQAgBSoCGItDAACAAF5BAXMNAQsgA0EoaiAHQQhqIgAoAgA2AgAgAyAHKQIANwMgIANBEGogA0EgahAjIAAgA0EYaigCADYCACAHIAMpAxA3AgALIANBMGokAAuuBgENfyADQQFOBEAgAiEGIAEhBwNAIAlBA2whEEEBIQggBiEFQQAhBANAIAJBACAIIARBGEYbIBBqQQJ0aigCACEKIAUoAgAhDSAEIAdqIg9BCGogCTYCACAPQQRqIAogDSANIApIIgsbNgIAIA8gDSAKIAsbNgIAIAhBAWohCCAFQQRqIQUgBEEMaiIEQSRHDQALIAZBDGohBiAHQSRqIQcgCUEBaiIJIANHDQALC0EAIQYgAUEAIANBA2wiDEF/akEAQcrLgRMQDSAMQQJOBEAgAUEMaiEEQQEhBQNAIAEgBkEMbGooAgAgBCgCAEcEQCABIAYgBUF/akEBQcrLgRMQDSAFIQYLIARBDGohBCAMIAVBAWoiBUcNAAsgAUEQaiEEQQAhBkEBIQUDQAJAIAEgBkEMbGoiAygCACAEQXxqKAIARgRAIAMoAgQgBCgCAEYNAQsgASAGIAVBf2pBAkHKy4ETEA0gBSEGCyAEQQxqIQQgDCAFQQFqIgVHDQALCyAMQQFOBEBBACEDA0AgASADQQxsaiIGKAIEIQ4gAiAGKAIIIg1BDGxqIgcoAgQhCwJAIAcoAgAiBSAGKAIAIgpGIAUgDkZyRQRAIAcoAgghBkEBIQQMAQtBACEEIAogC0YgCyAORnJFBEAgBygCCCELQQIhBCAFIQYMAQsgCyEGIAUhCwsCQCADQQFqIgMgDE4NACAAIA1ByABsaiAEQQJ0aiIPKAIAQX9HDQBBACEJQQEhCCADIQUCQANAAkAgCiABIAVBDGxqIgcoAgBHDQAgDiAHKAIERw0AIAhBAXFFDQIgAiAHKAIIIhBBDGxqIggoAgQhBwJAIAgoAgAiBCAKRiAEIA5GckUEQCAIKAIIIQRBASEJIAchCAwBC0EAIQkgByAKRiAHIA5GckUEQCAIKAIIIQhBAiEJDAELIAQhCCAHIQQLIAUgBiAIRyAEIAtHciAAIBBByABsaiAJQQJ0aigCAEF/R3IiCGoiBSAMSA0BCwsgCEEBcQ0BCyAPIAEgBUEMbGooAggiBjYCACAAIAZByABsaiAJQQJ0aiANNgIACyADIAxHDQALCwvqBAEHf0ErQYCAxAAgACgCACIDQQFxIgQbIQYgAiAEaiEEQfCPwABBACADQQRxGyEHAkACQCAAKAIIQQFHBEAgACAGIAcQRw0BDAILIABBDGooAgAiBSAETQRAIAAgBiAHEEcNAQwCCwJAAkACQAJAIANBCHEEQCAAKAIEIQggAEEwNgIEIAAtACAhCSAAQQE6ACAgACAGIAcQRw0FQQAhAyAFIARrIgQhBUEBIAAtACAiBiAGQQNGG0EDcUEBaw4DAgECAwtBACEDIAUgBGsiBCEFAkACQAJAQQEgAC0AICIIIAhBA0YbQQNxQQFrDgMBAAECCyAEQQF2IQMgBEEBakEBdiEFDAELQQAhBSAEIQMLIANBAWohAwNAIANBf2oiA0UNBCAAKAIYIAAoAgQgACgCHCgCEBECAEUNAAtBAQ8LIARBAXYhAyAEQQFqQQF2IQUMAQtBACEFIAQhAwsgA0EBaiEDAkADQCADQX9qIgNFDQEgACgCGCAAKAIEIAAoAhwoAhARAgBFDQALQQEPCyAAKAIEIQQgACgCGCABIAIgACgCHCgCDBEEAA0BIAVBAWohAyAAKAIcIQEgACgCGCECA0AgA0F/aiIDBEAgAiAEIAEoAhARAgBFDQEMAwsLIAAgCToAICAAIAg2AgRBAA8LIAAoAgQhBCAAIAYgBxBHDQAgACgCGCABIAIgACgCHCgCDBEEAA0AIAVBAWohAyAAKAIcIQEgACgCGCEAA0AgA0F/aiIDRQRAQQAPCyAAIAQgASgCEBECAEUNAAsLQQEPCyAAKAIYIAEgAiAAQRxqKAIAKAIMEQQAC/0FAQt/IwBBMGsiAiQAIAJBJGpB2I3AADYCACACQQM6ACggAkKAgICAgAQ3AwggAiAANgIgIAJBADYCGCACQQA2AhACfwJAAkACQCABKAIIIgMEQCABKAIAIQUgASgCBCIHIAFBDGooAgAiBCAEIAdLGyIERQ0BIAAgBSgCACAFKAIEQeSNwAAoAgARBAANAyAFQQxqIQAgASgCFCEGIAEoAhAhCyAEIQgDQCACIANBHGotAAA6ACggAiADQQRqKQIAQiCJNwMIIANBGGooAgAhAUEAIQlBACEKAkACQAJAIANBFGooAgBBAWsOAgACAQsgASAGTwRAIAEgBkG4ksAAED8ACyABQQN0IAtqIgwoAgRBHkcNASAMKAIAKAIAIQELQQEhCgsgAiABNgIUIAIgCjYCECADQRBqKAIAIQECQAJAAkAgA0EMaigCAEEBaw4CAAIBCyABIAZPBEAgASAGQbiSwAAQPwALIAFBA3QgC2oiCigCBEEeRw0BIAooAgAoAgAhAQtBASEJCyACIAE2AhwgAiAJNgIYIAMoAgAiASAGSQRAIAsgAUEDdGoiASgCACACQQhqIAEoAgQRAgANBSAIQX9qIghFDQQgA0EgaiEDIABBfGohASAAKAIAIQkgAEEIaiEAIAIoAiAgASgCACAJIAIoAiQoAgwRBABFDQEMBQsLIAEgBkGoksAAED8ACyABKAIAIQUgASgCBCIHIAFBFGooAgAiBCAEIAdLGyIERQ0AIAEoAhAhAyAAIAUoAgAgBSgCBEHkjcAAKAIAEQQADQIgBUEMaiEAIAQhAQNAIAMoAgAgAkEIaiADQQRqKAIAEQIADQMgAUF/aiIBRQ0CIANBCGohAyAAQXxqIQggACgCACEGIABBCGohACACKAIgIAgoAgAgBiACKAIkKAIMEQQARQ0ACwwCC0EAIQQLIAcgBEsEQCACKAIgIAUgBEEDdGoiACgCACAAKAIEIAIoAiQoAgwRBAANAQtBAAwBC0EBCyACQTBqJAAL1AQCAX8FfCMAQSBrIgAkAAJ8AkACQAJAAkAgAEEIahADIAArAxghBCAAKwMIIQEgACgCEEEDcQ4DAQIDAAsgASABIAEgAaIiAaIiAkRJVVVVVVXFP6IgASAERAAAAAAAAOA/oiACIAEgASABoqIgAUR81c9aOtnlPaJE65wriublWr6goiABIAFEff6xV+Mdxz6iRNVhwRmgASq/oKJEpvgQERERgT+goKKhoiAEoaChDAMLRAAAAAAAAPA/IAEgAaIiAkQAAAAAAADgP6IiA6EiBUQAAAAAAADwPyAFoSADoSACIAIgAiACRJAVyxmgAfo+okR3UcEWbMFWv6CiRExVVVVVVaU/oKIgAiACoiIDIAOiIAIgAkTUOIi+6fqovaJExLG0vZ7uIT6gokStUpyAT36SvqCioKIgASAEoqGgoAwCCyABIAEgASABoiIBoiICRElVVVVVVcU/oiABIAREAAAAAAAA4D+iIAIgASABIAGioiABRHzVz1o62eU9okTrnCuK5uVavqCiIAEgAUR9/rFX4x3HPqJE1WHBGaABKr+gokSm+BARERGBP6CgoqGiIAShoKGaDAELRAAAAAAAAPA/IAEgAaIiAkQAAAAAAADgP6IiA6EiBUQAAAAAAADwPyAFoSADoSACIAIgAiACRJAVyxmgAfo+okR3UcEWbMFWv6CiRExVVVVVVaU/oKIgAiACoiIDIAOiIAIgAkTUOIi+6fqovaJExLG0vZ7uIT6gokStUpyAT36SvqCioKIgASAEoqGgoJoMAAsgAEEgaiQAC7EEAQh/AkAgAigCACIFBEAgAUF/aiEKIABBAnQhCUEAIAFrIQsDQCAFQQhqIQYgBSgCCCIHQQFxBEADQCAGIAdBfnE2AgACf0EAIAUoAgQiB0F8cSIGRQ0AGkEAIAYgBi0AAEEBcRsLIQECQCAFKAIAIghBfHEiDEUNAEEAIAwgCEECcRsiCEUNACAIIAgoAgRBA3EgBnI2AgQgBSgCBCIHQXxxIQYLIAUgBgR/IAYgBigCAEEDcSAFKAIAQXxxcjYCACAFKAIEBSAHC0EDcTYCBCAFIAUoAgAiBUEDcTYCACAFQQJxBEAgASABKAIAQQJyNgIACyACIAE2AgAgAUEIaiEGIAEiBSgCCCIHQQFxDQALCwJAIAUoAgBBfHEiASAGayAJSQ0AIAYgAyAAIAQoAhARAgBBAnRqQQhqIAEgCWsgC3EiAUsEQCAGIApxDQEgAiAGKAIAQXxxNgIAIAUhAQwECyABQQA2AgAgAUF4aiIBQgA3AgAgASAFKAIAQXxxNgIAAkAgBSgCACIAQXxxIgJFDQBBACACIABBAnEbIgBFDQAgACAAKAIEQQNxIAFyNgIECyABIAEoAgRBA3EgBXI2AgQgBSAFKAIAQQNxIAFyNgIAIAYgBigCAEF+cTYCACAFKAIAIgBBAnFFDQMgBSAAQX1xNgIAIAEgASgCAEECcjYCAAwDCyACIAUoAggiBTYCACAFDQALC0EADwsgASABKAIAQQFyNgIAIAFBCGoLvQQCC38CfSMAQfAAayICJAAgAkEwaiAAEF8gAkFAayABEF8gAkHYAGogAkE4aikDADcDACACIAIpAzA3A1AgAkEQaiACQcgAaikDADcDACACIAIpA0A3AwggAkHgAGoiACACQQhqIgEpAgA3AgAgAEEIaiABQQhqKQIANwIAIAJBCGogAkHQAGogAkHgAGoQTCACQSRqKAIAIgMgAigCFCIEayELIAQgA0F/c2ohDCACQRxqKAIAIQcgAkEgaigCACEIIAIoAhghACACKAIMIQkgAigCECEGIAIoAgghAQJ/A0ACQAJAIAQEfwJAAkAgASAGRgRAIAlBEGohBSAGQQxqIQYgCUEMaiIJIQEMAQsgAUEEaiEFIAFFDQELIAMEfyAAIAhGBEAgB0EQaiEKIAhBDGohCCAHQQxqIgchAAwFCyAAQQRqIQogAA0EIARBf2ohDCAKIQAgA0F/agVBAAshASACIAY2AhAgAiAFNgIIIAIgDDYCFCACIAE2AiQMAgsgAyELIAUhASAEQX9qBUEACyEFIAIgBjYCECACIAE2AgggAiAFNgIUIAIgCzYCJAsgAiAJNgIMIAIgCDYCICACIAA2AhggAiAHNgIcQQEMAgsgBEF/aiEEIANBf2ohAyAAKgIAIQ0gASoCACAKIQAgBSEBIA1bDQALIAIgBjYCECACIAU2AgggAiAENgIUIAIgAzYCJCACIAk2AgwgAiAINgIgIAIgCjYCGCACIAc2AhxBAAsgAkHwAGokAAuUAQIDfwJ9IwBBIGsiASQAIAFBGGpBAEEBEGggASgCGCICIAEoAhwiA0kEQCAAKgIAIgQgBJQgAEEEaioCACIEIASUkiAAQQhqKgIAIgQgBJSSIQVDAAAAACEEA0ACQCACRQRAIAJBAWohAgwBC0G8hsAAQcSHwAAQXAALIAQgBZIhBCACIANHDQALCyABQSBqJAAgBAvAAwIJfwJ+IANBAnQhDAJAA0AgAiABa0EBaiIFQQJIDQEgBUECRwRAIAAgBCAEIAR3IARBACAEa3hyakEDaiIEIAVwIAFqQQxsaiAMaigCACENIAIhBiABIQUDQCAMIAVBDGwiCGohByAFIQoDQCAIQQxqIQggCkEBaiEKIAAgB2ogB0EMaiEHKAIAIA1IDQALIApBf2ohBSAMIAZBDGwiCWohByAGIQsDQCAJQXRqIQkgC0F/aiELIAAgB2ogB0F0aiEHKAIAIA1KDQALIAUgC0EBaiIGTARAIAAgCGoiBkF0aiIFKQIAIQ4gACAJaiIHQRRqIggoAgAhCSAFIAdBDGoiBykCADcCACAGQXxqKAIAIQYgBUEIaiAJNgIAIAggBjYCACAHIA43AgAgCyEGIAohBQsgBSAGTA0ACyAGIAFKBEAgACABIAYgAyAEEA0LIAUiASACSA0BDAILCyAAIAFBDGxqIgEgA0ECdCIDaigCACAAIAJBDGxqIgAgA2ooAgBMDQAgASkCBCEOIAApAgAhDyABQQhqIABBCGooAgA2AgAgASgCACECIAEgDzcCACAAIA43AgQgACACNgIACwvFBQMBfwF+AnwCfAJAAkAgAL0iAkIgiKdB/////wdxIgFB//+//wNNBEAgAUGAgID/A0kNASACQn9XDQJEAAAAAAAA8D8gAKFEAAAAAAAA4D+iIgAgAJ8iBL1CgICAgHCDvyIDIAOioSAEIAOgoyAEIAAgACAAIAAgACAARAn3/Q3hPQI/okSIsgF14O9JP6CiRDuPaLUogqS/oKJEVUSIDlXByT+gokR9b+sDEtbUv6CiRFVVVVVVVcU/oKIgACAAIAAgAESCki6xxbizP6JEWQGNG2wG5r+gokTIilmc5SoAQKCiREstihwnOgPAoKJEAAAAAAAA8D+go6KgIAOgIgAgAKAPCyACpyABQYCAwIB8anIEQEQAAAAAAAAAACAAIAChow8LRAAAAAAAAAAARBgtRFT7IQlAIAJCf1UbDwtEGC1EVPsh+T8gAUGBgIDjA0kNARpEB1wUMyamkTwgACAAoiIDIAMgAyADIAMgA0QJ9/0N4T0CP6JEiLIBdeDvST+gokQ7j2i1KIKkv6CiRFVEiA5Vwck/oKJEfW/rAxLW1L+gokRVVVVVVVXFP6CiIAMgAyADIANEgpIuscW4sz+iRFkBjRtsBua/oKJEyIpZnOUqAECgokRLLYocJzoDwKCiRAAAAAAAAPA/oKMgAKKhIAChRBgtRFT7Ifk/oA8LRBgtRFT7Ifk/IABEAAAAAAAA8D+gRAAAAAAAAOA/oiIAnyIDIAMgACAAIAAgACAAIABECff9DeE9Aj+iRIiyAXXg70k/oKJEO49otSiCpL+gokRVRIgOVcHJP6CiRH1v6wMS1tS/oKJEVVVVVVVVxT+goiAAIAAgACAARIKSLrHFuLM/okRZAY0bbAbmv6CiRMiKWZzlKgBAoKJESy2KHCc6A8CgokQAAAAAAADwP6CjokQHXBQzJqaRvKCgoSIAIACgCwu6AwEEfyMAQRBrIgIkACAAKAIAIQQCQAJAAkACfwJAAkAgAUGAAU8EQCACQQA2AgwgAUGAEEkNASACQQxqIQAgAUGAgARJBEAgAiABQT9xQYABcjoADiACIAFBDHZB4AFyOgAMIAIgAUEGdkE/cUGAAXI6AA1BAyEBDAYLIAIgAUE/cUGAAXI6AA8gAiABQRJ2QfABcjoADCACIAFBBnZBP3FBgAFyOgAOIAIgAUEMdkE/cUGAAXI6AA1BBCEBDAULIAQoAggiACAEQQRqKAIARwRAIAQoAgAhBQwECwJAIABBAWoiAyAASQ0AIABBAXQiBSADIAUgA0sbIgNBCCADQQhLGyEDIAAEQCADQQBIDQEgBCgCACIFRQ0DIAUgAEEBIAMQYgwECyADQQBODQILEGsACyACIAFBP3FBgAFyOgANIAIgAUEGdkHAAXI6AAwgAkEMaiEAQQIhAQwDCyADQQEQZwsiBQRAIAQgBTYCACAEQQRqIAM2AgAgBCgCCCEADAELIANBARBwAAsgACAFaiABOgAAIAQgBCgCCEEBajYCCAwBCyAEIAAgACABahAiCyACQRBqJABBAAuRAwEKfyMAQdAAayIKJAAgA0F/aiIHQQFOBEADQCAEQQFqIQYCfyAGIAAgBEHIAGxqIgUoAjggBUGAAWooAgBHDQAaIAUoAjwiCEEBcSAAIAZByABsaiIGKAI8IglBAXFHBEAgBkE8aiAJQQJyNgIAIAVBPGogCEECcjYCAAsgBEECagsiBCAHSA0ACwsCQCACQQFIDQBBACEHIAEhBkEBIQQDQAJ/AkAgACAHQcgAbGoiCS0APEEBcQRAIAQgA0gNAQwECyAHQQJqIgUgBCAEIAVIGwwBCwNAAkAgACAEQcgAbGooAjxBAXEiBSAEaiEEIAVFDQAgBCADSA0BCwsgBQ0CIARBAWogASAEQQxsaiEMQQAhCANAIAYgCGoiCygCACENIAsgCCAMaiILKAIANgIAIAsgDTYCACAIQQRqIghBDEcNAAsgCkEIaiAJQcgAEE4gCSAAIARByABsaiIEQcgAEDsaIAQgCkEIakHIABBOCyEEIAZBDGohBiAHQQFqIgcgAkcNAAsLIApB0ABqJAALOQIBfwF9IwBBEGsiAiQAIAAqAgAgASoCAJQgACoCBCABKgIElJIgACoCCCABKgIIlJIgAkEQaiQAC8oCAQ1/IARBAUgEQEEADwsgACEKIAMhCwNAIAAgCUHIAGxqIhBBPGohD0F/IQ1BACEHA0ACQCAPLQAAQQRxDQAgByAKaiIOQQxqIgUoAgANACAHIAtqKAIAIQYgBSABIAhBBHRqIhE2AgAgESAGNgIIIAUoAgAgDy0AAEEDdkEBcToADCAFKAIAQQA2AgAgBSgCACACIAxBAnRqNgIEIAUoAgAiBigCBCAGKAIAQQJ0aiAJNgIAIAYgBigCAEEBajYCACAQIA1BAiAHG0ECdGooAgAhBiAOKAIAIg5BAE4EQCADIAAgDiAFKAIAEBcLIAZBAE4EQCADIAAgBiAFKAIAEBcLIAhBAWohCCAFKAIAKAIAIAxqIQwLIA1BAWohDSAHQQRqIgdBDEcNAAsgCkHIAGohCiALQQxqIQsgCUEBaiIJIARHDQALIAgLygIBA38gACgCACIEQQA2AgAgBEF4aiIFIAUoAgBBfnE2AgACQAJAIAIgAygCFBEGAEUNAAJAIARBfGoiAygCAEF8cSIABEAgACgCACIGQQFxRQ0BCyAFKAIAIgBBfHEiAkUNAUEAIAIgAEECcRsiAEUNASAALQAAQQFxDQEgBCAAKAIIQXxxNgIAIAAgBUEBcjYCCA8LAkACQCAFKAIAIgRBfHEiAkUEQCAAIQEMAQsgACEBQQAgAiAEQQJxGyIERQ0AIAQgBCgCBEEDcSAAcjYCBCADKAIAIgJBfHEiAUUNASAFKAIAQXxxIQIgASgCACEGCyABIAZBA3EgAnI2AgAgAygCACECCyADIAJBA3E2AgAgBSAFKAIAIgFBA3E2AgAgAUECcUUNASAAIAAoAgBBAnI2AgAPCyAEIAEoAgA2AgAgASAFNgIACwu3AgIFfwF+IwBBMGsiBCQAQSchAgJAIABCkM4AVARAIAAhBwwBCwNAIARBCWogAmoiA0F8aiAAIABCkM4AgCIHQpDOAH59pyIFQf//A3FB5ABuIgZBAXRBxJDAAGovAAA7AAAgA0F+aiAFIAZB5ABsa0H//wNxQQF0QcSQwABqLwAAOwAAIAJBfGohAiAAQv/B1y9WIAchAA0ACwsgB6ciA0HjAEoEQCACQX5qIgIgBEEJamogB6ciAyADQf//A3FB5ABuIgNB5ABsa0H//wNxQQF0QcSQwABqLwAAOwAACwJAIANBCk4EQCACQX5qIgIgBEEJamogA0EBdEHEkMAAai8AADsAAAwBCyACQX9qIgIgBEEJamogA0EwajoAAAsgASAEQQlqIAJqQScgAmsQByAEQTBqJAALqAEBA38jAEEwayIDJAAgA0EoaiIEIAFBCGooAgA2AgAgAyABKQIANwMgIAAgAykDIDcCACAAQQhqIAQoAgA2AgAgA0EIakEAQQMQaCADKAIIIgEgAygCDCIESQRAIAQgAWshBCAAIAFBAnQiBWohASACIAVqIQADQCABIAEqAgAgACoCAJM4AgAgAUEEaiEBIABBBGohACAEQX9qIgQNAAsLIANBMGokAAuoAQEDfyMAQTBrIgMkACADQShqIgQgAUEIaigCADYCACADIAEpAgA3AyAgACADKQMgNwIAIABBCGogBCgCADYCACADQQhqQQBBAxBoIAMoAggiASADKAIMIgRJBEAgBCABayEEIAAgAUECdCIFaiEBIAIgBWohAANAIAEgACoCACABKgIAkjgCACABQQRqIQEgAEEEaiEAIARBf2oiBA0ACwsgA0EwaiQAC7wCAQd/AkADQEEAIQZBACEHAkAgAygCCCIEIAAgAkEMbGoiBSgCAEYNAEEBIQcgBCAFKAIERgRAQQEhBgwBC0ECIQYgBSgCCCAERw0CCwJAIANFDQAgASACQcgAbGoiBCAGQQJ0aiIIQQxqIgkoAgANAAJAIAQoAjwiBUEEcUUNACAEKAIMDQAgBEEQaigCAA0AIARBFGooAgANACAEQTxqIgogBUF3cSIFNgIAIAogAy0ADEEDdCAFciIFNgIACyAFQQhxQQN2IAMtAAxBAEdzDQAgAygCBCADKAIAQQJ0aiACNgIAIAMgAygCAEEBajYCACAJIAM2AgAgBCAGQX9qQQIgBxtBAnRqKAIAIQIgCCgCACIEQQBOBEAgACABIAQgAxAXCyACQX9KDQELCw8LQX9BA0GkjcAAED8AC6oCAgN/AX4jAEEwayIDJAACfwJAAkAgACgCBCIEIAFrIAJJBEAgASACaiICIAFJDQJBBCEBAkAgBEEBdCIFIAIgBSACSxsiAkEEIAJBBEsbrULIAH4iBkIgiKdFBEAgBqchAgwBCyAAKAIEIQRBACEBCwJAIAQEQCAAKAIAIQUgA0EoakEENgIAIAMgBEHIAGw2AiQgAyAFNgIgDAELIANBADYCIAsgA0EQaiACIAEgA0EgahAtIANBGGooAgAhASADKAIUIQIgAygCEEEBRg0BIAAgAjYCACAAIAFByABuNgIECyADQTBqJAAPCyADQQhqIAIgARBoIAMoAgghASADKAIMDAELIAMgAkEAEGggAygCACEBIAMoAgQLIgAEQCABIAAQcAALEGsAC6cCAgN/AX4jAEEwayIDJAACfwJAAkAgACgCBCIEIAFrIAJJBEAgASACaiICIAFJDQJBBCEBAkAgBEEBdCIFIAIgBSACSxsiAkEEIAJBBEsbrUIofiIGQiCIp0UEQCAGpyECDAELIAAoAgQhBEEAIQELAkAgBARAIAAoAgAhBSADQShqQQQ2AgAgAyAEQShsNgIkIAMgBTYCIAwBCyADQQA2AiALIANBEGogAiABIANBIGoQLSADQRhqKAIAIQEgAygCFCECIAMoAhBBAUYNASAAIAI2AgAgACABQShuNgIECyADQTBqJAAPCyADQQhqIAIgARBoIAMoAgghASADKAIMDAELIAMgAkEAEGggAygCACEBIAMoAgQLIgAEQCABIAAQcAALEGsAC6cCAgN/AX4jAEEwayIDJAACfwJAAkAgACgCBCIEIAFrIAJJBEAgASACaiICIAFJDQJBBCEBAkAgBEEBdCIFIAIgBSACSxsiAkEEIAJBBEsbrUIMfiIGQiCIp0UEQCAGpyECDAELIAAoAgQhBEEAIQELAkAgBARAIAAoAgAhBSADQShqQQQ2AgAgAyAEQQxsNgIkIAMgBTYCIAwBCyADQQA2AiALIANBEGogAiABIANBIGoQLSADQRhqKAIAIQEgAygCFCECIAMoAhBBAUYNASAAIAI2AgAgACABQQxuNgIECyADQTBqJAAPCyADQQhqIAIgARBoIAMoAgghASADKAIMDAELIAMgAkEAEGggAygCACEBIAMoAgQLIgAEQCABIAAQcAALEGsAC6gCAQN/IwBBMGsiAyQAAn8CQAJAIAAoAgQiBCABayACSQRAIAEgAmoiAiABSQ0CQQQhAQJAIARBAXQiBSACIAUgAksbIgJBBCACQQRLGyICQf////8DcSACRgRAIAJBAnQhAgwBCyAAKAIEIQRBACEBCwJAIAQEQCAAKAIAIQUgA0EoakEENgIAIAMgBEECdDYCJCADIAU2AiAMAQsgA0EANgIgCyADQRBqIAIgASADQSBqEC0gA0EYaigCACEBIAMoAhQhAiADKAIQQQFGDQEgACACNgIAIAAgAUECdjYCBAsgA0EwaiQADwsgA0EIaiACIAEQaCADKAIIIQEgAygCDAwBCyADIAJBABBoIAMoAgAhASADKAIECyIABEAgASAAEHAACxBrAAuoAgEDfyMAQTBrIgMkAAJ/AkACQCAAKAIEIgQgAWsgAkkEQCABIAJqIgIgAUkNAkEEIQECQCAEQQF0IgUgAiAFIAJLGyICQQQgAkEESxsiAkH/////AHEgAkYEQCACQQR0IQIMAQsgACgCBCEEQQAhAQsCQCAEBEAgACgCACEFIANBKGpBBDYCACADIARBBHQ2AiQgAyAFNgIgDAELIANBADYCIAsgA0EQaiACIAEgA0EgahAtIANBGGooAgAhASADKAIUIQIgAygCEEEBRg0BIAAgAjYCACAAIAFBBHY2AgQLIANBMGokAA8LIANBCGogAiABEGggAygCCCEBIAMoAgwMAQsgAyACQQAQaCADKAIAIQEgAygCBAsiAARAIAEgABBwAAsQawALsgIBBX8jAEFAaiICJAAgASgCBCIDRQRAIAFBBGohAyABKAIAIQQgAkEANgIgIAJCATcDGCACIAJBGGo2AiQgAkE4aiAEQRBqKQIANwMAIAJBMGogBEEIaikCADcDACACIAQpAgA3AyggAkEkaiACQShqEAgaIAJBEGoiBCACKAIgNgIAIAIgAikDGDcDCAJAIAEoAgQiBUUNACABQQhqKAIAIgZFDQAgBSAGQQEQagsgAyACKQMINwIAIANBCGogBCgCADYCACADKAIAIQMLIAFBATYCBCABQQxqKAIAIQQgAUEIaiIBKAIAIQUgAUIANwIAQQxBBBBnIgFFBEBBDEEEEHAACyABIAQ2AgggASAFNgIEIAEgAzYCACAAQZCPwAA2AgQgACABNgIAIAJBQGskAAuNAgELfyAAQXxqIQwCQANAIAIgAWsiBEEBaiIFIARJDQEgACADIAMgA3cgA0EAIANreHJqQQNqIgMgBXAgAWpBAnRqKAIAIQogAiEFIAEhBANAIAwgBEECdGohCCAEIQYDQCAGQQFqIQYgCEEEaiIIKAIAIg0gCkgNAAsgBkF/aiEEIAAgBUECdGohCSAFIQcDQCAHQX9qIQcgCSgCACELIAlBfGoiDiEJIAsgCkoNAAsgBCAHQQFqIgVMBEAgCCALNgIAIA5BBGogDTYCACAHIQUgBiEECyAEIAVMDQALIAUgAUoEQCAAIAEgBSADEB4LIAQhASAEIAJIDQALDwtB0IrAAEE5QbSKwAAQSAALmgIBBH8gAEEkaiEFIAEoAgghBiABKAIEIQcgASgCACEIIABBLGooAgAiASAAQShqKAIARgRAIAUgAUEBEBsgACgCLCEBCyAAKAIkIAFBAnRqIAg2AgAgACAAKAIsQQFqIgE2AiwgACgCKCABRgRAIAUgAUEBEBsgACgCLCEBCyAAKAIkIAFBAnRqIAc2AgAgACAAKAIsQQFqIgE2AiwgACgCKCABRgRAIAUgAUEBEBsgACgCLCEBCyAAKAIkIAFBAnRqIAY2AgAgACAAKAIsQQFqIgE2AiwgACgCKCABRgRAIAUgAUEBEBsgACgCLCEBCyAAKAIkIAFBAnRqQYCAgPwDQYCAgPx7IAQbNgIAIAAgACgCLEEBajYCLAv9AQIJfwF+IwBBIGsiBCQAIAAgACgCCCABEBwgACgCACAAKAIIIQUgBEEIakEBIAEQaCAFQQR0aiEDIAQoAggiBiAEKAIMIgdJBEAgByAGayEIIAJBBGohCSAEQRhqIQoDQCACKAIAIQsgBEEQaiAJEDkgAyALNgIAIANBBGogBCkDEDcCACADQQxqIAooAgA2AgAgA0EQaiEDIAhBf2oiCA0ACyAFIAdqIAZrIQULAkAgAQRAIAIpAgAhDCADQQhqIAJBCGopAgA3AgAgAyAMNwIAIAAgBUEBajYCCAwBCyAAIAU2AgggAkEEaiIAKAIAGiAAEFYLIARBIGokAAuAAgIBfwF+IwBBMGsiByQAIAdBLGpBADYCACAHQSBqIAY2AgAgB0EcaiAGNgIAIAdBFGogBDYCACAHQRBqIAQ2AgAgB0IENwIkIAcgBTYCGCAHIAM2AgwgByACNgIIIAcgAjYCBCAHIAE2AgAgB0EYaiEBIAdBDGohAyAHEAIEQCAHKAIkIQIgBykDKCEIIAcQViADEFYgARBWIAcgCDcCBCAHIAI2AgAgCKcgCEIgiKciBEsEQCAHIAQQKSAHKAIIIQQgBygCACECCyAAIAQ2AgQgACACNgIAIAdBMGokAA8LQYyBwABBHBAAIAcQViADEFYgARBWIAdBJGoQVhABAAvYAQEDfwJAIABBBGooAgAiBCAAQQhqKAIAIgNrIAIgAWsiBU8EQCAAKAIAIQQMAQsCfwJAAkAgAyAFaiICIANJDQAgBEEBdCIDIAIgAyACSxsiAkEIIAJBCEsbIQIgBARAIAJBAEgNASAAKAIAIgNFDQIgAyAEQQEgAhBiDAMLIAJBAE4NAQsQawALIAJBARBnCyIEBEAgACAENgIAIABBBGogAjYCACAAQQhqKAIAIQMMAQsgAkEBEHAACyADIARqIAEgBRBOIABBCGoiACAAKAIAIAVqNgIAC9kBAgV/An0jAEEQayICJAAgAkEIakEAQQEQaAJAIAIoAggiBCACKAIMIgVJBEAgASoCACIHIAeUIAFBBGoqAgAiByAHlJIgAUEIaioCACIHIAeUkiEIQwAAAAAhBwNAIAQNAiAIIAeSIQcgBEEBaiEGQQEhBCAFIAZHDQALCyAAIAEpAgA3AgAgAEEIaiABQQhqKAIANgIAQwAAgD8gB5GVIQcDQCAAIANqIgEgByABKgIAlDgCACADQQRqIgNBDEcNAAsgAkEQaiQADwtBiYvAAEGQjMAAEFwAC90BAQR/IwBBQGoiAiQAIAFBBGohBCABKAIERQRAIAEoAgAhAyACQQA2AiAgAkIBNwMYIAIgAkEYajYCJCACQThqIANBEGopAgA3AwAgAkEwaiADQQhqKQIANwMAIAIgAykCADcDKCACQSRqIAJBKGoQCBogAkEQaiIDIAIoAiA2AgAgAiACKQMYNwMIAkAgASgCBCIFRQ0AIAFBCGooAgAiAUUNACAFIAFBARBqCyAEIAIpAwg3AgAgBEEIaiADKAIANgIACyAAQZCPwAA2AgQgACAENgIAIAJBQGskAAuYAgECfyMAQSBrIgQkAEEBIQVB1J3AAEHUncAAKAIAQQFqNgIAAkACQAJAQdidwAAoAgBBAUcEQEHYncAAQoGAgIAQNwMADAELQdydwABB3J3AACgCAEEBaiIFNgIAIAVBAksNAQsgBCADNgIcIAQgAjYCGCAEQfCNwAA2AhQgBEHwjcAANgIQQcidwAAoAgAiAkF/TA0AQcidwAAgAkEBaiICNgIAQcidwABB0J3AACgCACIDBH9BzJ3AACgCACAEQQhqIAAgASgCEBEBACAEIAQpAwg3AxAgBEEQaiADKAIMEQEAQcidwAAoAgAFIAILQX9qNgIAIAVBAU0NAQsACyMAQRBrIgIkACACIAE2AgwgAiAANgIIAAvEAQIGfwF+IwBBEGsiBSQAIAAgACgCCCABEBogACgCACAAKAIIIQQgBUEIakEBIAEQaCAEQQxsaiEDIAUoAggiBiAFKAIMIgdJBEAgByAGayEIA0AgAikCACEJIANBCGogAkEIaigCADYCACADIAk3AgAgA0EMaiEDIAhBf2oiCA0ACyAEIAdqIAZrIQQLIAAgAQR/IAIpAgAhCSADQQhqIAJBCGooAgA2AgAgAyAJNwIAIARBAWoFIAQLNgIIIAVBEGokAAvEAQIGfwF+IwBBEGsiBSQAIAAgACgCCCABEBwgACgCACAAKAIIIQQgBUEIakEBIAEQaCAEQQR0aiEDIAUoAggiBiAFKAIMIgdJBEAgByAGayEIA0AgAikCACEJIANBCGogAkEIaikCADcCACADIAk3AgAgA0EQaiEDIAhBf2oiCA0ACyAEIAdqIAZrIQQLIAAgAQR/IAIpAgAhCSADQQhqIAJBCGopAgA3AgAgAyAJNwIAIARBAWoFIAQLNgIIIAVBEGokAAuqAQMBfwN+An0jAEEgayICJAAgAkEYaiABKAIAEGEgACACKAIYIAIoAhwQPSEDIAJBEGogASgCBBBhIAAgAigCECACKAIUED0hBCACQQhqIAEoAggQYSAAIAIoAgggAigCDBA9IQUgAkEgaiQAIASnviADp74iBpMgBUIgiKe+IANCIIinviIHk5QgBEIgiKe+IAeTIAWnviAGk5STIgaMIAYgBkMAAAAAXRsLsQEBBX8jAEEQayIDJAACQCAAKAIEIgIgAU8EQAJAIAIEQCACQQJ0IQIgACgCACEEAkAgAUECdCIFRQRAQQQhBiACRQ0BIAQgAkEEEGoMAQsgBCACQQQgBRBiIgZFDQILIAAgBjYCACAAIAFB/////wNxNgIECyADQRBqJAAPCyADQQhqIAVBBBBoIAMoAgwiAEUNASADKAIIIAAQcAALQcWIwABBJEHsiMAAEEgACxBrAAu2AQEBfyMAQRBrIgMkAAJAIABFDQAgAyAANgIEIAFFDQACQCACQQRLDQAgAUEDakECdkF/aiIAQf8BSw0AIANBoJXAADYCCCADIABBAnRBpJXAAGoiACgCADYCDCADQQRqIANBDGogA0EIakGUicAAEBMgACADKAIMNgIADAELIANBoJXAACgCADYCDCADQQRqIANBDGpB/IjAAEH8iMAAEBNBoJXAACADKAIMNgIACyADQRBqJAALmQEBBn8jAEEQayIFJAAgACAAKAIIIAEQGCAAKAIAIAAoAgghAyAFQQhqQQEgARBoIANByABsaiEEIAUoAggiBiAFKAIMIgdJBEAgByAGayEIA0AgBCACQcgAEDtByABqIQQgCEF/aiIIDQALIAMgB2ogBmshAwsgACABBH8gBCACQcgAEDsaIANBAWoFIAMLNgIIIAVBEGokAAuVAQEGfyMAQRBrIgUkACAAIAAoAgggARAZIAAoAgAgACgCCCEDIAVBCGpBASABEGggA0EobGohBCAFKAIIIgYgBSgCDCIHSQRAIAcgBmshCANAIAQgAkEoEDtBKGohBCAIQX9qIggNAAsgAyAHaiAGayEDCyAAIAEEfyAEIAJBKBA7GiADQQFqBSADCzYCCCAFQRBqJAALqgEBAn8CQAJAAkAgAgRAQQEhBCABQQBODQEMAgsgACABNgIEQQEhBAwBCwJAAkACQAJAAkAgAygCACIFRQRAIAFFDQEMAwsgAygCBCIDDQEgAQ0CCyACIQMMAwsgBSADIAIgARBiIgNFDQEMAgsgASACEGciAw0BCyAAIAE2AgQgAiEBDAILIAAgAzYCBEEAIQQMAQtBACEBCyAAIAQ2AgAgAEEIaiABNgIAC7IBAQJ/IwBBEGsiAiQAAkAgAEUNACAAQQNqQQJ2IQACQCABQQRLDQAgAEF/aiIDQf8BSw0AIAJBoJXAADYCBCACIANBAnRBpJXAAGoiAygCADYCDCAAIAEgAkEMaiACQQRqQZSJwAAQPiEBIAMgAigCDDYCAAwBCyACQaCVwAAoAgA2AgggACABIAJBCGpB/IjAAEH8iMAAED4hAUGglcAAIAIoAgg2AgALIAJBEGokACABC5cBAQJ/AkACQCABQRRqKAIAIgQgA0EDbCACQQlsaiICSwRAIAQgAkEBaiIDTQ0BIAQgAkECaiIFTQ0CIAAgASgCDCIBIAJBAnRqKAIANgIAIAAgASAFQQJ0aigCADYCCCAAIAEgA0ECdGooAgA2AgQPCyACIARBvIDAABA/AAsgAyAEQcyAwAAQPwALIAUgBEHcgMAAED8AC5MBAQZ/AkACQAJAIAAoAgAiBCABKAIARw0AIARFBEBBAQ8LIABBDGooAgAhBQNAIAUgAk0NAiABKAIMIgMgAk0NAyACIAJBAnQiAyAAKAIEaigCACIGIAEoAgQgA2ooAgAiB0YiA2oiAiAETw0BIAYgB0YNAAsLIAMPCyACIAVBhI3AABA/AAsgAiADQZSNwAAQPwALlAEBAn8CQAJAIAEoAggiBCADQQNsIAJBCWxqIgJLBEAgBCACQQFqIgNNDQEgBCACQQJqIgVNDQIgACABKAIAIgEgAkECdGooAgA2AgAgACABIAVBAnRqKAIANgIIIAAgASADQQJ0aigCADYCBA8LIAIgBEGMgMAAED8ACyADIARBnIDAABA/AAsgBSAEQayAwAAQPwALiwEBAX8jAEEQayIDJAAgAyABKAIAIgEoAgA2AgwgAkECaiICIAJsIgJBgBAgAkGAEEsbIgRBBCADQQxqQayJwABBrInAABA+IQIgASADKAIMNgIAIAIEfyACQgA3AgQgAiACIARBAnRqQQJyNgIAQQAFQQELIQEgACACNgIEIAAgATYCACADQRBqJAALogEBA38jAEEQayIBJAAgACgCACICQRRqKAIAIQMCQAJ/AkACQCACKAIEDgIAAQMLIAMNAkEAIQJB8I3AAAwBCyADDQEgAigCACIDKAIEIQIgAygCAAshAyABIAI2AgQgASADNgIAIAFB/I7AACAAKAIEKAIIIAAoAggQJQALIAFBADYCBCABIAI2AgAgAUHojsAAIAAoAgQoAgggACgCCBAlAAtTAgF/AX4CQCABrUIMfiIDQiCIpw0AIAOnIgFBf0wNAAJAIAEEQCABQQQQZyICDQEgAUEEEHAAC0EEIQILIAAgAjYCACAAIAFBDG42AgQPCxBrAAtTAgF/AX4CQCABrUIofiIDQiCIpw0AIAOnIgFBf0wNAAJAIAEEQCABQQQQZyICDQEgAUEEEHAAC0EEIQILIAAgAjYCACAAIAFBKG42AgQPCxBrAAtVAgF/AX4CQCABrULIAH4iA0IgiKcNACADpyIBQX9MDQACQCABBEAgAUEEEGciAg0BIAFBBBBwAAtBBCECCyAAIAI2AgAgACABQcgAbjYCBA8LEGsAC1MBAX8CQCABIAFB/////wBxRw0AIAFBBHQiAUF/TA0AAkAgAQRAIAFBBBBnIgINASABQQQQcAALQQQhAgsgACACNgIAIAAgAUEEdjYCBA8LEGsAC4MBAQF/AkAgASABQf////8DcUcNACABQQJ0IgFBf0wNAAJAAkACQAJAIAIEQCABDQEMAwsgAUUNAiABQQQQZyICDQMMAQsgASICQQQQLiIDBEAgAyACEFoaCyADIgINAgsgAUEEEHAAC0EEIQILIAAgAjYCACAAIAFBAnY2AgQPCxBrAAt0AgJ/AX4jAEEQayICJAAgAkEIaiABEGMgAigCCCEDIAIgAigCDCIBQQAQOCACKQMAIQQgAEEANgIIIAAgBDcCACAAQQAgARAbIAAoAgAgACgCCEECdGogAyABQQJ0EE4gACABIAAoAghqNgIIIAJBEGokAAtxAAJ/IAJBAnQiASADQQN0QYCAAWoiAiABIAJLG0GHgARqIgFBEHZAACICQX9GBEBBACEDQQEMAQsgAkEQdCIDQgA3AwAgA0EANgIIIAMgAyABQYCAfHFqQQJyNgIAQQALIQIgACADNgIEIAAgAjYCAAtvAQF/AkAgASAATwRAIAJFDQEgACEDA0AgAyABLQAAOgAAIAFBAWohASADQQFqIQMgAkF/aiICDQALDAELIAJFDQAgAUF/aiEBIABBf2ohAwNAIAIgA2ogASACai0AADoAACACQX9qIgINAAsLIAALbwECfwJ/IAIgAJMgASAAk5VDAAAARZQiAEMAAADPYCIDQQFzIABD////Tl9FckUEQCAAqAwBC0H/////B0GAgICAeCADGyIEIARBACAAQ////05fGyADGwsiA0EAIANBAEobIgNB/w8gA0H/D0gbC2YBAX8CQCAAQSBqKAIAIgMgAUEGbCACQQF0aiIBSwRAIAMgAUEBciICTQ0BIAAoAhgiACACQQJ0ajUCAEIghiAAIAFBAnRqNQIAhA8LIAEgA0HsgMAAED8ACyACIANB/IDAABA/AAtrAQJ/IwBBEGsiBiQAAkAgACABIAIgAyAEEAoiBQ0AIAZBCGogAyAAIAEgBCgCDBEFAEEAIQUgBigCCA0AIAYoAgwiBSACKAIANgIIIAIgBTYCACAAIAEgAiADIAQQCiEFCyAGQRBqJAAgBQtsAQF/IwBBMGsiAyQAIAMgATYCBCADIAA2AgAgA0EcakECNgIAIANBLGpBHTYCACADQgI3AgwgA0G0kMAANgIIIANBHTYCJCADIANBIGo2AhggAyADNgIoIAMgA0EEajYCICADQQhqIAIQWQALVAEBfyMAQSBrIgIkACACIAAoAgA2AgQgAkEYaiABQRBqKQIANwMAIAJBEGogAUEIaikCADcDACACIAEpAgA3AwggAkEEaiACQQhqEAggAkEgaiQAC1kCAX8BfiMAQSBrIgMkACADQQhqIAIQNyADKQMIIQQgAEEANgIIIAAgBDcCACADQRhqIAFBCGopAgA3AwAgAyABKQIANwMQIAAgAiADQRBqECcgA0EgaiQAC1kCAX8BfiMAQSBrIgMkACADQQhqIAIQNCADKQMIIQQgAEEANgIIIAAgBDcCACADQRhqIAFBCGooAgA2AgAgAyABKQIANwMQIAAgAiADQRBqECYgA0EgaiQAC1kCAX8BfiMAQSBrIgMkACADQQhqIAIQNyADKQMIIQQgAEEANgIIIAAgBDcCACADQRhqIAFBCGopAgA3AwAgAyABKQIANwMQIAAgAiADQRBqECAgA0EgaiQAC1QBAn8gASgCACECIAFBADYCAAJAIAIEQCABKAIEIQNBCEEEEGciAUUNASABIAM2AgQgASACNgIAIABByI3AADYCBCAAIAE2AgAPCwALQQhBBBBwAAtKAgF/AX4jAEHQAGsiAyQAIAMgAhA2IAMpAwAhBCAAQQA2AgggACAENwIAIANBCGogAUHIABBOIAAgAiADQQhqECsgA0HQAGokAAtHAgF/AX4jAEEwayIDJAAgAyACEDUgAykDACEEIABBADYCCCAAIAQ3AgAgA0EIaiABQSgQTiAAIAIgA0EIahAsIANBMGokAAtKAAJ/IAFBgIDEAEcEQEEBIAAoAhggASAAQRxqKAIAKAIQEQIADQEaCyACRQRAQQAPCyAAKAIYIAJBACAAQRxqKAIAKAIMEQQACwtHAQF/IwBBIGsiAyQAIANBFGpBADYCACADQfCPwAA2AhAgA0IBNwIEIAMgATYCHCADIAA2AhggAyADQRhqNgIAIAMgAhBZAAs8AQF/IAAoAggiAQRAIAFBBHQhASAAKAIAQQRqIQADQCAAKAIAGiAAEFYgAEEQaiEAIAFBcGoiAQ0ACwsLOAAgAEIANwIAIABBCGpBADYCACAAQSRqQQA6AAAgAEEcakIANwIAIABBFGpCADcCACAAQgA3AgwLRAECfyABKAIEIQIgASgCACEDQQhBBBBnIgFFBEBBCEEEEHAACyABIAI2AgQgASADNgIAIABBoI/AADYCBCAAIAE2AgALPQAgAEIANwIgIAAgASkCADcCACAAIAIpAgA3AhAgAEEIaiABQQhqKQIANwIAIABBGGogAkEIaikCADcCAAtbAQN/IwBBEGsiASQAIAAoAgwiAkUEQEGAjsAAQStByI7AABBIAAsgACgCCCIDRQRAQYCOwABBK0HYjsAAEEgACyABIAI2AgggASAANgIEIAEgAzYCACABEFgACysAIAIEQANAIAAgAS0AADoAACABQQFqIQEgAEEBaiEAIAJBf2oiAg0ACwsLKQEBfyADIAIQLiIEBEAgBCAAIAMgASABIANLGxBOIAAgASACECoLIAQLLAACQCAAQXxNBEAgAEUEQEEEIQAMAgsgACAAQX1JQQJ0EGciAA0BCwALIAALNwEBf0EBIQECQCAAKgIAi0MAAIAAXg0AIAAqAgSLQwAAgABeDQAgACoCCItDAACAAF4hAQsgAQsmAQF/AkAgACgCBCIBRQ0AIAAoAgAgAUEEdCIBRQ0AIAFBBBBqCwsnAQF/AkAgACgCBCIBRQ0AIAAoAgAgAUHIAGwiAUUNACABQQQQagsLJgEBfwJAIAAoAgQiAUUNACAAKAIAIAFBKGwiAUUNACABQQQQagsLJgEBfwJAIAAoAgQiAUUNACAAKAIAIAFBDGwiAUUNACABQQQQagsLJgEBfwJAIAAoAgQiAUUNACAAKAIAIAFBAnQiAUUNACABQQQQagsLLAEBfyMAQRBrIgEkACABQQhqIABBCGooAgA2AgAgASAAKQIANwMAIAEQWwALLAEBfyMAQRBrIgEkACABQQhqIABBCGooAgA2AgAgASAAKQIANwMAIAEQMwALNAEBfyMAQRBrIgIkACACIAE2AgwgAiAANgIIIAJB8I/AADYCBCACQfCPwAA2AgAgAhBNAAspAQF/IAEEQCAAIQIDQCACQQA6AAAgAkEBaiECIAFBf2oiAQ0ACwsgAAsrAQF/IwBBEGsiASQAIAEgACkCADcDCCABQQhqQbSNwABBACAAKAIIECUACycBAX8jAEEQayICJAAgAiABNgIIIAJBHTYCBCACIAA2AgAgAhBXAAsmAQF/AkAgACgCACIBRQ0AIABBBGooAgAiAEUNACABIABBARBqCwsmAQF/AkAgACgCBCIBRQ0AIABBCGooAgAiAEUNACABIABBARBqCwshACAAQQM2AgwgACABNgIEIAAgATYCACAAIAFBDGo2AggLHQAgASgCAEUEQAALIABByI3AADYCBCAAIAE2AgALFgAgACABQQNxNgIEIAAgAUECdjYCAAsMACAAIAEgAiADEE8LFgAgACABKAIINgIEIAAgASgCADYCAAsPACABBEAgACABQQQQagsLEgAgACgCACABIAEgAmoQIkEACw0AIAFBA3EgAEECdHILCAAgACABEC4LEAAgACACNgIEIAAgATYCAAsTACAAQaCPwAA2AgQgACABNgIACwoAIAAgASACECoLEQBBzI/AAEERQeCPwAAQSAALDgAgACgCABoDQAwACwALCwAgADUCACABEBQLCwAgACMAaiQAIwALCwAgAItDAACAAF4LGQAgACABQcSdwAAoAgAiAEEPIAAbEQEAAAsFAEGABAsEAEEBCwQAIAELBABBAAsNAEL0+Z7m7qOq+f4ACw0AQve47vqqzNXu5QALDABC6dCi28yi6rtGCwMAAQsDAAELC6QVAgBBgIDAAAvBCnNyYy9saWIucnMAAAAAEAAKAAAANgAAAA0AAAAAABAACgAAADcAAAANAAAAAAAQAAoAAAA4AAAADQAAAAAAEAAKAAAAPgAAAA0AAAAAABAACgAAAD8AAAANAAAAAAAQAAoAAABAAAAADQAAAAAAEAAKAAAARgAAAA0AAAAAABAACgAAAEcAAAANAAAARmFpbGVkIHRvIGdlbmVyYXRlIHRhbmdlbnRzLi9Vc2Vycy9kb25tY2N1cmR5Ly5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL21pa2t0c3BhY2UtMC4yLjAvc3JjL2dlbmVyYXRlZC5ycwCoABAAYwAAANcAAAASAAAAqAAQAGMAAADYAAAAEgAAAKgAEABjAAAA2QAAABIAAACoABAAYwAAAN4AAAANAAAAqAAQAGMAAAAjAQAAOAAAAKgAEABjAAAA/QEAABUAAACoABAAYwAAAA8CAABAAAAAqAAQAGMAAAAVAgAAEQAAAKgAEABjAAAAFgIAABEAAACoABAAYwAAABgCAAARAAAAqAAQAGMAAAAjAgAAGQAAAKgAEABjAAAAJAIAADIAAACoABAAYwAAACoCAAAcAAAAqAAQAGMAAAAmAgAANAAAAKgAEABjAAAAvQUAAAkAAACoABAAYwAAAMAFAAAFAAAAqAAQAGMAAADDBQAAHAAAAKgAEABjAAAAwwUAADMAAACoABAAYwAAAMMFAAAJAAAAqAAQAGMAAADTBQAAIwAAAKgAEABjAAAA0wUAABcAAACoABAAYwAAANQFAAAYAAAAqAAQAGMAAADVBQAACQAAAKgAEABjAAAA3AUAABEAAACoABAAYwAAAN8FAAAYAAAAqAAQAGMAAADgBQAAGQAAAKgAEABjAAAA6AUAAC0AAACoABAAYwAAAOgFAAAhAAAAqAAQAGMAAADpBQAAGAAAAKgAEABjAAAA8gUAABEAAACoABAAYwAAAKcGAAANAAAAqAAQAGMAAACuBgAAEQAAAKgAEABjAAAArwYAABEAAACoABAAYwAAALAGAAARAAAAqAAQAGMAAAD9BgAACQAAAE1hdHJpeCBzbGljaW5nIG91dCBvZiBib3VuZHMuL1VzZXJzL2Rvbm1jY3VyZHkvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbmFsZ2VicmEtMC4xOS4wL3NyYy9iYXNlL21hdHJpeF9zbGljZS5ycwBZAxAAagAAAOMAAAAJAAAAL1VzZXJzL2Rvbm1jY3VyZHkvLnJ1c3R1cC90b29sY2hhaW5zL3N0YWJsZS14ODZfNjQtYXBwbGUtZGFyd2luL2xpYi9ydXN0bGliL3NyYy9ydXN0L2xpYnJhcnkvYWxsb2Mvc3JjL3Jhd192ZWMucnNUcmllZCB0byBzaHJpbmsgdG8gYSBsYXJnZXIgY2FwYWNpdHkAAADUAxAAcQAAAMUBAAAJAAAAAQAAAAAAAAABAAAAAgAAAAMAAAAEAAAABQAAAAQAAAAEAAAABgAAAAcAAAAIAAAACQAAAAAAAAABAAAAAgAAAAMAAAAEAAAAL1VzZXJzL2Rvbm1jY3VyZHkvLnJ1c3R1cC90b29sY2hhaW5zL3N0YWJsZS14ODZfNjQtYXBwbGUtZGFyd2luL2xpYi9ydXN0bGliL3NyYy9ydXN0L2xpYnJhcnkvY29yZS9zcmMvbnVtL21vZC5yc8QEEABwAAAAnQIAAAUAQdCKwAAL0AphdHRlbXB0IHRvIGNhbGN1bGF0ZSB0aGUgcmVtYWluZGVyIHdpdGggYSBkaXZpc29yIG9mIHplcm9NYXRyaXggc2xpY2luZyBvdXQgb2YgYm91bmRzLi9Vc2Vycy9kb25tY2N1cmR5Ly5jYXJnby9yZWdpc3RyeS9zcmMvZ2l0aHViLmNvbS0xZWNjNjI5OWRiOWVjODIzL25hbGdlYnJhLTAuMTkuMC9zcmMvYmFzZS9tYXRyaXhfc2xpY2UucnOmBRAAagAAAOMAAAAJAAAAL1VzZXJzL2Rvbm1jY3VyZHkvLmNhcmdvL3JlZ2lzdHJ5L3NyYy9naXRodWIuY29tLTFlY2M2Mjk5ZGI5ZWM4MjMvbWlra3RzcGFjZS0wLjIuMC9zcmMvZ2VuZXJhdGVkLnJzACAGEABjAAAA2wIAABkAAAAgBhAAYwAAANsCAAAyAAAAIAYQAGMAAAB+AwAACAAAAAoAAAAIAAAABAAAAAsAAAAMAAAADQAAAAgAAAAEAAAADgAAABAAAAAEAAAABAAAABEAAAASAAAAEwAAABAAAAAAAAAAAQAAABQAAABjYWxsZWQgYE9wdGlvbjo6dW53cmFwKClgIG9uIGEgYE5vbmVgIHZhbHVlbGlicmFyeS9zdGQvc3JjL3Bhbmlja2luZy5ycwArBxAAHAAAAO0BAAAfAAAAKwcQABwAAADuAQAAHgAAABUAAAAQAAAABAAAABYAAAAXAAAAEAAAAAgAAAAEAAAAGAAAABkAAAAaAAAADAAAAAQAAAAbAAAAEAAAAAgAAAAEAAAAHAAAAGxpYnJhcnkvYWxsb2Mvc3JjL3Jhd192ZWMucnNjYXBhY2l0eSBvdmVyZmxvdwAAALAHEAAcAAAAHgIAAAUAAAAfAAAAAAAAAAEAAAAgAAAAaW5kZXggb3V0IG9mIGJvdW5kczogdGhlIGxlbiBpcyAgYnV0IHRoZSBpbmRleCBpcyAAAAAIEAAgAAAAIAgQABIAAAAwMDAxMDIwMzA0MDUwNjA3MDgwOTEwMTExMjEzMTQxNTE2MTcxODE5MjAyMTIyMjMyNDI1MjYyNzI4MjkzMDMxMzIzMzM0MzUzNjM3MzgzOTQwNDE0MjQzNDQ0NTQ2NDc0ODQ5NTA1MTUyNTM1NDU1NTY1NzU4NTk2MDYxNjI2MzY0NjU2NjY3Njg2OTcwNzE3MjczNzQ3NTc2Nzc3ODc5ODA4MTgyODM4NDg1ODY4Nzg4ODk5MDkxOTI5Mzk0OTU5Njk3OTg5OWxpYnJhcnkvY29yZS9zcmMvZm10L21vZC5ycwAMCRAAGwAAAFUEAAARAAAADAkQABsAAABfBAAAJAAAAAMAAAAEAAAABAAAAAYAAACD+aIARE5uAPwpFQDRVycA3TT1AGLbwAA8mZUAQZBDAGNR/gC73qsAt2HFADpuJADSTUIASQbgAAnqLgAcktEA6x3+ACmxHADoPqcA9TWCAES7LgCc6YQAtCZwAEF+XwDWkTkAU4M5AJz0OQCLX4QAKPm9APgfOwDe/5cAD5gFABEv7wAKWosAbR9tAM9+NgAJyycARk+3AJ5mPwAt6l8Auid1AOXrxwA9e/EA9zkHAJJSigD7a+oAH7FfAAhdjQAwA1YAe/xGAPCrawAgvM8ANvSaAOOpHQBeYZEACBvmAIWZZQCgFF8AjUBoAIDY/wAnc00ABgYxAMpWFQDJqHMAe+JgAGuMwAAAAABA+yH5PwAAAAAtRHQ+AAAAgJhG+DwAAABgUcx4OwAAAICDG/A5AAAAQCAlejgAAACAIoLjNgAAAAAd82k1AHsJcHJvZHVjZXJzAghsYW5ndWFnZQEEUnVzdAAMcHJvY2Vzc2VkLWJ5AwVydXN0Yx0xLjQ5LjAgKGUxODg0YThlMyAyMDIwLTEyLTI5KQZ3YWxydXMGMC4xOC4wDHdhc20tYmluZGdlbhIwLjIuNzMgKDNjZWZlMmM4Mik='; + +export let wasm; + +export let isReady = false; + +export const ready = fetch(wasmDataURI) + .then((res) => res.arrayBuffer()) + .then((buffer) => WebAssembly.instantiate(buffer, { + './mikktspace_module_bg.js': {__wbindgen_string_new, __wbindgen_rethrow} + })) + .then((result) => { + wasm = result.instance.exports; + isReady = true; + }); diff --git a/jsm/libs/mmdparser.module.js b/jsm/libs/mmdparser.module.js new file mode 100644 index 0000000..5a587f1 --- /dev/null +++ b/jsm/libs/mmdparser.module.js @@ -0,0 +1,11530 @@ +/** + * @author Takahiro / https://github.com/takahirox + * + * Simple CharsetEncoder. + */ + +function CharsetEncoder() { +} + +/* + * Converts from Shift_JIS Uint8Array data to Unicode strings. + */ +CharsetEncoder.prototype.s2u = function(uint8Array) { + var t = this.s2uTable; + var str = ''; + var p = 0; + + while(p < uint8Array.length) { + var key = uint8Array[p++]; + + if(! ((key >= 0x00 && key <= 0x7e) || + (key >= 0xa1 && key <= 0xdf)) && + p < uint8Array.length) { + key = (key << 8) | uint8Array[p++]; + } + + if(t[key] === undefined) { + console.error('unknown char code ' + key + '.'); + return str; + } + + str += String.fromCharCode(t[key]); + } + + return str; +}; + +CharsetEncoder.prototype.s2uTable = { +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, +14:14, +15:15, +16:16, +17:17, +18:18, +19:19, +20:20, +21:21, +22:22, +23:23, +24:24, +25:25, +26:26, +27:27, +28:28, +29:29, +30:30, +31:31, +32:32, +33:33, +34:34, +35:35, +36:36, +37:37, +38:38, +39:39, +40:40, +41:41, +42:42, +43:43, +44:44, +45:45, +46:46, +47:47, +48:48, +49:49, +50:50, +51:51, +52:52, +53:53, +54:54, +55:55, +56:56, +57:57, +58:58, +59:59, +60:60, +61:61, +62:62, +63:63, +64:64, +65:65, +66:66, +67:67, +68:68, +69:69, +70:70, +71:71, +72:72, +73:73, +74:74, +75:75, +76:76, +77:77, +78:78, +79:79, +80:80, +81:81, +82:82, +83:83, +84:84, +85:85, +86:86, +87:87, +88:88, +89:89, +90:90, +91:91, +92:92, +93:93, +94:94, +95:95, +96:96, +97:97, +98:98, +99:99, +100:100, +101:101, +102:102, +103:103, +104:104, +105:105, +106:106, +107:107, +108:108, +109:109, +110:110, +111:111, +112:112, +113:113, +114:114, +115:115, +116:116, +117:117, +118:118, +119:119, +120:120, +121:121, +122:122, +123:123, +124:124, +125:125, +126:126, +161:65377, +162:65378, +163:65379, +164:65380, +165:65381, +166:65382, +167:65383, +168:65384, +169:65385, +170:65386, +171:65387, +172:65388, +173:65389, +174:65390, +175:65391, +176:65392, +177:65393, +178:65394, +179:65395, +180:65396, +181:65397, +182:65398, +183:65399, +184:65400, +185:65401, +186:65402, +187:65403, +188:65404, +189:65405, +190:65406, +191:65407, +192:65408, +193:65409, +194:65410, +195:65411, +196:65412, +197:65413, +198:65414, +199:65415, +200:65416, +201:65417, +202:65418, +203:65419, +204:65420, +205:65421, +206:65422, +207:65423, +208:65424, +209:65425, +210:65426, +211:65427, +212:65428, +213:65429, +214:65430, +215:65431, +216:65432, +217:65433, +218:65434, +219:65435, +220:65436, +221:65437, +222:65438, +223:65439, +33088:12288, +33089:12289, +33090:12290, +33091:65292, +33092:65294, +33093:12539, +33094:65306, +33095:65307, +33096:65311, +33097:65281, +33098:12443, +33099:12444, +33100:180, +33101:65344, +33102:168, +33103:65342, +33104:65507, +33105:65343, +33106:12541, +33107:12542, +33108:12445, +33109:12446, +33110:12291, +33111:20189, +33112:12293, +33113:12294, +33114:12295, +33115:12540, +33116:8213, +33117:8208, +33118:65295, +33119:65340, +33120:65374, +33121:8741, +33122:65372, +33123:8230, +33124:8229, +33125:8216, +33126:8217, +33127:8220, +33128:8221, +33129:65288, +33130:65289, +33131:12308, +33132:12309, +33133:65339, +33134:65341, +33135:65371, +33136:65373, +33137:12296, +33138:12297, +33139:12298, +33140:12299, +33141:12300, +33142:12301, +33143:12302, +33144:12303, +33145:12304, +33146:12305, +33147:65291, +33148:65293, +33149:177, +33150:215, +33152:247, +33153:65309, +33154:8800, +33155:65308, +33156:65310, +33157:8806, +33158:8807, +33159:8734, +33160:8756, +33161:9794, +33162:9792, +33163:176, +33164:8242, +33165:8243, +33166:8451, +33167:65509, +33168:65284, +33169:65504, +33170:65505, +33171:65285, +33172:65283, +33173:65286, +33174:65290, +33175:65312, +33176:167, +33177:9734, +33178:9733, +33179:9675, +33180:9679, +33181:9678, +33182:9671, +33183:9670, +33184:9633, +33185:9632, +33186:9651, +33187:9650, +33188:9661, +33189:9660, +33190:8251, +33191:12306, +33192:8594, +33193:8592, +33194:8593, +33195:8595, +33196:12307, +33208:8712, +33209:8715, +33210:8838, +33211:8839, +33212:8834, +33213:8835, +33214:8746, +33215:8745, +33224:8743, +33225:8744, +33226:65506, +33227:8658, +33228:8660, +33229:8704, +33230:8707, +33242:8736, +33243:8869, +33244:8978, +33245:8706, +33246:8711, +33247:8801, +33248:8786, +33249:8810, +33250:8811, +33251:8730, +33252:8765, +33253:8733, +33254:8757, +33255:8747, +33256:8748, +33264:8491, +33265:8240, +33266:9839, +33267:9837, +33268:9834, +33269:8224, +33270:8225, +33271:182, +33276:9711, +33359:65296, +33360:65297, +33361:65298, +33362:65299, +33363:65300, +33364:65301, +33365:65302, +33366:65303, +33367:65304, +33368:65305, +33376:65313, +33377:65314, +33378:65315, +33379:65316, +33380:65317, +33381:65318, +33382:65319, +33383:65320, +33384:65321, +33385:65322, +33386:65323, +33387:65324, +33388:65325, +33389:65326, +33390:65327, +33391:65328, +33392:65329, +33393:65330, +33394:65331, +33395:65332, +33396:65333, +33397:65334, +33398:65335, +33399:65336, +33400:65337, +33401:65338, +33409:65345, +33410:65346, +33411:65347, +33412:65348, +33413:65349, +33414:65350, +33415:65351, +33416:65352, +33417:65353, +33418:65354, +33419:65355, +33420:65356, +33421:65357, +33422:65358, +33423:65359, +33424:65360, +33425:65361, +33426:65362, +33427:65363, +33428:65364, +33429:65365, +33430:65366, +33431:65367, +33432:65368, +33433:65369, +33434:65370, +33439:12353, +33440:12354, +33441:12355, +33442:12356, +33443:12357, +33444:12358, +33445:12359, +33446:12360, +33447:12361, +33448:12362, +33449:12363, +33450:12364, +33451:12365, +33452:12366, +33453:12367, +33454:12368, +33455:12369, +33456:12370, +33457:12371, +33458:12372, +33459:12373, +33460:12374, +33461:12375, +33462:12376, +33463:12377, +33464:12378, +33465:12379, +33466:12380, +33467:12381, +33468:12382, +33469:12383, +33470:12384, +33471:12385, +33472:12386, +33473:12387, +33474:12388, +33475:12389, +33476:12390, +33477:12391, +33478:12392, +33479:12393, +33480:12394, +33481:12395, +33482:12396, +33483:12397, +33484:12398, +33485:12399, +33486:12400, +33487:12401, +33488:12402, +33489:12403, +33490:12404, +33491:12405, +33492:12406, +33493:12407, +33494:12408, +33495:12409, +33496:12410, +33497:12411, +33498:12412, +33499:12413, +33500:12414, +33501:12415, +33502:12416, +33503:12417, +33504:12418, +33505:12419, +33506:12420, +33507:12421, +33508:12422, +33509:12423, +33510:12424, +33511:12425, +33512:12426, +33513:12427, +33514:12428, +33515:12429, +33516:12430, +33517:12431, +33518:12432, +33519:12433, +33520:12434, +33521:12435, +33600:12449, +33601:12450, +33602:12451, +33603:12452, +33604:12453, +33605:12454, +33606:12455, +33607:12456, +33608:12457, +33609:12458, +33610:12459, +33611:12460, +33612:12461, +33613:12462, +33614:12463, +33615:12464, +33616:12465, +33617:12466, +33618:12467, +33619:12468, +33620:12469, +33621:12470, +33622:12471, +33623:12472, +33624:12473, +33625:12474, +33626:12475, +33627:12476, +33628:12477, +33629:12478, +33630:12479, +33631:12480, +33632:12481, +33633:12482, +33634:12483, +33635:12484, +33636:12485, +33637:12486, +33638:12487, +33639:12488, +33640:12489, +33641:12490, +33642:12491, +33643:12492, +33644:12493, +33645:12494, +33646:12495, +33647:12496, +33648:12497, +33649:12498, +33650:12499, +33651:12500, +33652:12501, +33653:12502, +33654:12503, +33655:12504, +33656:12505, +33657:12506, +33658:12507, +33659:12508, +33660:12509, +33661:12510, +33662:12511, +33664:12512, +33665:12513, +33666:12514, +33667:12515, +33668:12516, +33669:12517, +33670:12518, +33671:12519, +33672:12520, +33673:12521, +33674:12522, +33675:12523, +33676:12524, +33677:12525, +33678:12526, +33679:12527, +33680:12528, +33681:12529, +33682:12530, +33683:12531, +33684:12532, +33685:12533, +33686:12534, +33695:913, +33696:914, +33697:915, +33698:916, +33699:917, +33700:918, +33701:919, +33702:920, +33703:921, +33704:922, +33705:923, +33706:924, +33707:925, +33708:926, +33709:927, +33710:928, +33711:929, +33712:931, +33713:932, +33714:933, +33715:934, +33716:935, +33717:936, +33718:937, +33727:945, +33728:946, +33729:947, +33730:948, +33731:949, +33732:950, +33733:951, +33734:952, +33735:953, +33736:954, +33737:955, +33738:956, +33739:957, +33740:958, +33741:959, +33742:960, +33743:961, +33744:963, +33745:964, +33746:965, +33747:966, +33748:967, +33749:968, +33750:969, +33856:1040, +33857:1041, +33858:1042, +33859:1043, +33860:1044, +33861:1045, +33862:1025, +33863:1046, +33864:1047, +33865:1048, +33866:1049, +33867:1050, +33868:1051, +33869:1052, +33870:1053, +33871:1054, +33872:1055, +33873:1056, +33874:1057, +33875:1058, +33876:1059, +33877:1060, +33878:1061, +33879:1062, +33880:1063, +33881:1064, +33882:1065, +33883:1066, +33884:1067, +33885:1068, +33886:1069, +33887:1070, +33888:1071, +33904:1072, +33905:1073, +33906:1074, +33907:1075, +33908:1076, +33909:1077, +33910:1105, +33911:1078, +33912:1079, +33913:1080, +33914:1081, +33915:1082, +33916:1083, +33917:1084, +33918:1085, +33920:1086, +33921:1087, +33922:1088, +33923:1089, +33924:1090, +33925:1091, +33926:1092, +33927:1093, +33928:1094, +33929:1095, +33930:1096, +33931:1097, +33932:1098, +33933:1099, +33934:1100, +33935:1101, +33936:1102, +33937:1103, +33951:9472, +33952:9474, +33953:9484, +33954:9488, +33955:9496, +33956:9492, +33957:9500, +33958:9516, +33959:9508, +33960:9524, +33961:9532, +33962:9473, +33963:9475, +33964:9487, +33965:9491, +33966:9499, +33967:9495, +33968:9507, +33969:9523, +33970:9515, +33971:9531, +33972:9547, +33973:9504, +33974:9519, +33975:9512, +33976:9527, +33977:9535, +33978:9501, +33979:9520, +33980:9509, +33981:9528, +33982:9538, +34624:9312, +34625:9313, +34626:9314, +34627:9315, +34628:9316, +34629:9317, +34630:9318, +34631:9319, +34632:9320, +34633:9321, +34634:9322, +34635:9323, +34636:9324, +34637:9325, +34638:9326, +34639:9327, +34640:9328, +34641:9329, +34642:9330, +34643:9331, +34644:8544, +34645:8545, +34646:8546, +34647:8547, +34648:8548, +34649:8549, +34650:8550, +34651:8551, +34652:8552, +34653:8553, +34655:13129, +34656:13076, +34657:13090, +34658:13133, +34659:13080, +34660:13095, +34661:13059, +34662:13110, +34663:13137, +34664:13143, +34665:13069, +34666:13094, +34667:13091, +34668:13099, +34669:13130, +34670:13115, +34671:13212, +34672:13213, +34673:13214, +34674:13198, +34675:13199, +34676:13252, +34677:13217, +34686:13179, +34688:12317, +34689:12319, +34690:8470, +34691:13261, +34692:8481, +34693:12964, +34694:12965, +34695:12966, +34696:12967, +34697:12968, +34698:12849, +34699:12850, +34700:12857, +34701:13182, +34702:13181, +34703:13180, +34704:8786, +34705:8801, +34706:8747, +34707:8750, +34708:8721, +34709:8730, +34710:8869, +34711:8736, +34712:8735, +34713:8895, +34714:8757, +34715:8745, +34716:8746, +34975:20124, +34976:21782, +34977:23043, +34978:38463, +34979:21696, +34980:24859, +34981:25384, +34982:23030, +34983:36898, +34984:33909, +34985:33564, +34986:31312, +34987:24746, +34988:25569, +34989:28197, +34990:26093, +34991:33894, +34992:33446, +34993:39925, +34994:26771, +34995:22311, +34996:26017, +34997:25201, +34998:23451, +34999:22992, +35000:34427, +35001:39156, +35002:32098, +35003:32190, +35004:39822, +35005:25110, +35006:31903, +35007:34999, +35008:23433, +35009:24245, +35010:25353, +35011:26263, +35012:26696, +35013:38343, +35014:38797, +35015:26447, +35016:20197, +35017:20234, +35018:20301, +35019:20381, +35020:20553, +35021:22258, +35022:22839, +35023:22996, +35024:23041, +35025:23561, +35026:24799, +35027:24847, +35028:24944, +35029:26131, +35030:26885, +35031:28858, +35032:30031, +35033:30064, +35034:31227, +35035:32173, +35036:32239, +35037:32963, +35038:33806, +35039:34915, +35040:35586, +35041:36949, +35042:36986, +35043:21307, +35044:20117, +35045:20133, +35046:22495, +35047:32946, +35048:37057, +35049:30959, +35050:19968, +35051:22769, +35052:28322, +35053:36920, +35054:31282, +35055:33576, +35056:33419, +35057:39983, +35058:20801, +35059:21360, +35060:21693, +35061:21729, +35062:22240, +35063:23035, +35064:24341, +35065:39154, +35066:28139, +35067:32996, +35068:34093, +35136:38498, +35137:38512, +35138:38560, +35139:38907, +35140:21515, +35141:21491, +35142:23431, +35143:28879, +35144:32701, +35145:36802, +35146:38632, +35147:21359, +35148:40284, +35149:31418, +35150:19985, +35151:30867, +35152:33276, +35153:28198, +35154:22040, +35155:21764, +35156:27421, +35157:34074, +35158:39995, +35159:23013, +35160:21417, +35161:28006, +35162:29916, +35163:38287, +35164:22082, +35165:20113, +35166:36939, +35167:38642, +35168:33615, +35169:39180, +35170:21473, +35171:21942, +35172:23344, +35173:24433, +35174:26144, +35175:26355, +35176:26628, +35177:27704, +35178:27891, +35179:27945, +35180:29787, +35181:30408, +35182:31310, +35183:38964, +35184:33521, +35185:34907, +35186:35424, +35187:37613, +35188:28082, +35189:30123, +35190:30410, +35191:39365, +35192:24742, +35193:35585, +35194:36234, +35195:38322, +35196:27022, +35197:21421, +35198:20870, +35200:22290, +35201:22576, +35202:22852, +35203:23476, +35204:24310, +35205:24616, +35206:25513, +35207:25588, +35208:27839, +35209:28436, +35210:28814, +35211:28948, +35212:29017, +35213:29141, +35214:29503, +35215:32257, +35216:33398, +35217:33489, +35218:34199, +35219:36960, +35220:37467, +35221:40219, +35222:22633, +35223:26044, +35224:27738, +35225:29989, +35226:20985, +35227:22830, +35228:22885, +35229:24448, +35230:24540, +35231:25276, +35232:26106, +35233:27178, +35234:27431, +35235:27572, +35236:29579, +35237:32705, +35238:35158, +35239:40236, +35240:40206, +35241:40644, +35242:23713, +35243:27798, +35244:33659, +35245:20740, +35246:23627, +35247:25014, +35248:33222, +35249:26742, +35250:29281, +35251:20057, +35252:20474, +35253:21368, +35254:24681, +35255:28201, +35256:31311, +35257:38899, +35258:19979, +35259:21270, +35260:20206, +35261:20309, +35262:20285, +35263:20385, +35264:20339, +35265:21152, +35266:21487, +35267:22025, +35268:22799, +35269:23233, +35270:23478, +35271:23521, +35272:31185, +35273:26247, +35274:26524, +35275:26550, +35276:27468, +35277:27827, +35278:28779, +35279:29634, +35280:31117, +35281:31166, +35282:31292, +35283:31623, +35284:33457, +35285:33499, +35286:33540, +35287:33655, +35288:33775, +35289:33747, +35290:34662, +35291:35506, +35292:22057, +35293:36008, +35294:36838, +35295:36942, +35296:38686, +35297:34442, +35298:20420, +35299:23784, +35300:25105, +35301:29273, +35302:30011, +35303:33253, +35304:33469, +35305:34558, +35306:36032, +35307:38597, +35308:39187, +35309:39381, +35310:20171, +35311:20250, +35312:35299, +35313:22238, +35314:22602, +35315:22730, +35316:24315, +35317:24555, +35318:24618, +35319:24724, +35320:24674, +35321:25040, +35322:25106, +35323:25296, +35324:25913, +35392:39745, +35393:26214, +35394:26800, +35395:28023, +35396:28784, +35397:30028, +35398:30342, +35399:32117, +35400:33445, +35401:34809, +35402:38283, +35403:38542, +35404:35997, +35405:20977, +35406:21182, +35407:22806, +35408:21683, +35409:23475, +35410:23830, +35411:24936, +35412:27010, +35413:28079, +35414:30861, +35415:33995, +35416:34903, +35417:35442, +35418:37799, +35419:39608, +35420:28012, +35421:39336, +35422:34521, +35423:22435, +35424:26623, +35425:34510, +35426:37390, +35427:21123, +35428:22151, +35429:21508, +35430:24275, +35431:25313, +35432:25785, +35433:26684, +35434:26680, +35435:27579, +35436:29554, +35437:30906, +35438:31339, +35439:35226, +35440:35282, +35441:36203, +35442:36611, +35443:37101, +35444:38307, +35445:38548, +35446:38761, +35447:23398, +35448:23731, +35449:27005, +35450:38989, +35451:38990, +35452:25499, +35453:31520, +35454:27179, +35456:27263, +35457:26806, +35458:39949, +35459:28511, +35460:21106, +35461:21917, +35462:24688, +35463:25324, +35464:27963, +35465:28167, +35466:28369, +35467:33883, +35468:35088, +35469:36676, +35470:19988, +35471:39993, +35472:21494, +35473:26907, +35474:27194, +35475:38788, +35476:26666, +35477:20828, +35478:31427, +35479:33970, +35480:37340, +35481:37772, +35482:22107, +35483:40232, +35484:26658, +35485:33541, +35486:33841, +35487:31909, +35488:21000, +35489:33477, +35490:29926, +35491:20094, +35492:20355, +35493:20896, +35494:23506, +35495:21002, +35496:21208, +35497:21223, +35498:24059, +35499:21914, +35500:22570, +35501:23014, +35502:23436, +35503:23448, +35504:23515, +35505:24178, +35506:24185, +35507:24739, +35508:24863, +35509:24931, +35510:25022, +35511:25563, +35512:25954, +35513:26577, +35514:26707, +35515:26874, +35516:27454, +35517:27475, +35518:27735, +35519:28450, +35520:28567, +35521:28485, +35522:29872, +35523:29976, +35524:30435, +35525:30475, +35526:31487, +35527:31649, +35528:31777, +35529:32233, +35530:32566, +35531:32752, +35532:32925, +35533:33382, +35534:33694, +35535:35251, +35536:35532, +35537:36011, +35538:36996, +35539:37969, +35540:38291, +35541:38289, +35542:38306, +35543:38501, +35544:38867, +35545:39208, +35546:33304, +35547:20024, +35548:21547, +35549:23736, +35550:24012, +35551:29609, +35552:30284, +35553:30524, +35554:23721, +35555:32747, +35556:36107, +35557:38593, +35558:38929, +35559:38996, +35560:39000, +35561:20225, +35562:20238, +35563:21361, +35564:21916, +35565:22120, +35566:22522, +35567:22855, +35568:23305, +35569:23492, +35570:23696, +35571:24076, +35572:24190, +35573:24524, +35574:25582, +35575:26426, +35576:26071, +35577:26082, +35578:26399, +35579:26827, +35580:26820, +35648:27231, +35649:24112, +35650:27589, +35651:27671, +35652:27773, +35653:30079, +35654:31048, +35655:23395, +35656:31232, +35657:32000, +35658:24509, +35659:35215, +35660:35352, +35661:36020, +35662:36215, +35663:36556, +35664:36637, +35665:39138, +35666:39438, +35667:39740, +35668:20096, +35669:20605, +35670:20736, +35671:22931, +35672:23452, +35673:25135, +35674:25216, +35675:25836, +35676:27450, +35677:29344, +35678:30097, +35679:31047, +35680:32681, +35681:34811, +35682:35516, +35683:35696, +35684:25516, +35685:33738, +35686:38816, +35687:21513, +35688:21507, +35689:21931, +35690:26708, +35691:27224, +35692:35440, +35693:30759, +35694:26485, +35695:40653, +35696:21364, +35697:23458, +35698:33050, +35699:34384, +35700:36870, +35701:19992, +35702:20037, +35703:20167, +35704:20241, +35705:21450, +35706:21560, +35707:23470, +35708:24339, +35709:24613, +35710:25937, +35712:26429, +35713:27714, +35714:27762, +35715:27875, +35716:28792, +35717:29699, +35718:31350, +35719:31406, +35720:31496, +35721:32026, +35722:31998, +35723:32102, +35724:26087, +35725:29275, +35726:21435, +35727:23621, +35728:24040, +35729:25298, +35730:25312, +35731:25369, +35732:28192, +35733:34394, +35734:35377, +35735:36317, +35736:37624, +35737:28417, +35738:31142, +35739:39770, +35740:20136, +35741:20139, +35742:20140, +35743:20379, +35744:20384, +35745:20689, +35746:20807, +35747:31478, +35748:20849, +35749:20982, +35750:21332, +35751:21281, +35752:21375, +35753:21483, +35754:21932, +35755:22659, +35756:23777, +35757:24375, +35758:24394, +35759:24623, +35760:24656, +35761:24685, +35762:25375, +35763:25945, +35764:27211, +35765:27841, +35766:29378, +35767:29421, +35768:30703, +35769:33016, +35770:33029, +35771:33288, +35772:34126, +35773:37111, +35774:37857, +35775:38911, +35776:39255, +35777:39514, +35778:20208, +35779:20957, +35780:23597, +35781:26241, +35782:26989, +35783:23616, +35784:26354, +35785:26997, +35786:29577, +35787:26704, +35788:31873, +35789:20677, +35790:21220, +35791:22343, +35792:24062, +35793:37670, +35794:26020, +35795:27427, +35796:27453, +35797:29748, +35798:31105, +35799:31165, +35800:31563, +35801:32202, +35802:33465, +35803:33740, +35804:34943, +35805:35167, +35806:35641, +35807:36817, +35808:37329, +35809:21535, +35810:37504, +35811:20061, +35812:20534, +35813:21477, +35814:21306, +35815:29399, +35816:29590, +35817:30697, +35818:33510, +35819:36527, +35820:39366, +35821:39368, +35822:39378, +35823:20855, +35824:24858, +35825:34398, +35826:21936, +35827:31354, +35828:20598, +35829:23507, +35830:36935, +35831:38533, +35832:20018, +35833:27355, +35834:37351, +35835:23633, +35836:23624, +35904:25496, +35905:31391, +35906:27795, +35907:38772, +35908:36705, +35909:31402, +35910:29066, +35911:38536, +35912:31874, +35913:26647, +35914:32368, +35915:26705, +35916:37740, +35917:21234, +35918:21531, +35919:34219, +35920:35347, +35921:32676, +35922:36557, +35923:37089, +35924:21350, +35925:34952, +35926:31041, +35927:20418, +35928:20670, +35929:21009, +35930:20804, +35931:21843, +35932:22317, +35933:29674, +35934:22411, +35935:22865, +35936:24418, +35937:24452, +35938:24693, +35939:24950, +35940:24935, +35941:25001, +35942:25522, +35943:25658, +35944:25964, +35945:26223, +35946:26690, +35947:28179, +35948:30054, +35949:31293, +35950:31995, +35951:32076, +35952:32153, +35953:32331, +35954:32619, +35955:33550, +35956:33610, +35957:34509, +35958:35336, +35959:35427, +35960:35686, +35961:36605, +35962:38938, +35963:40335, +35964:33464, +35965:36814, +35966:39912, +35968:21127, +35969:25119, +35970:25731, +35971:28608, +35972:38553, +35973:26689, +35974:20625, +35975:27424, +35976:27770, +35977:28500, +35978:31348, +35979:32080, +35980:34880, +35981:35363, +35982:26376, +35983:20214, +35984:20537, +35985:20518, +35986:20581, +35987:20860, +35988:21048, +35989:21091, +35990:21927, +35991:22287, +35992:22533, +35993:23244, +35994:24314, +35995:25010, +35996:25080, +35997:25331, +35998:25458, +35999:26908, +36000:27177, +36001:29309, +36002:29356, +36003:29486, +36004:30740, +36005:30831, +36006:32121, +36007:30476, +36008:32937, +36009:35211, +36010:35609, +36011:36066, +36012:36562, +36013:36963, +36014:37749, +36015:38522, +36016:38997, +36017:39443, +36018:40568, +36019:20803, +36020:21407, +36021:21427, +36022:24187, +36023:24358, +36024:28187, +36025:28304, +36026:29572, +36027:29694, +36028:32067, +36029:33335, +36030:35328, +36031:35578, +36032:38480, +36033:20046, +36034:20491, +36035:21476, +36036:21628, +36037:22266, +36038:22993, +36039:23396, +36040:24049, +36041:24235, +36042:24359, +36043:25144, +36044:25925, +36045:26543, +36046:28246, +36047:29392, +36048:31946, +36049:34996, +36050:32929, +36051:32993, +36052:33776, +36053:34382, +36054:35463, +36055:36328, +36056:37431, +36057:38599, +36058:39015, +36059:40723, +36060:20116, +36061:20114, +36062:20237, +36063:21320, +36064:21577, +36065:21566, +36066:23087, +36067:24460, +36068:24481, +36069:24735, +36070:26791, +36071:27278, +36072:29786, +36073:30849, +36074:35486, +36075:35492, +36076:35703, +36077:37264, +36078:20062, +36079:39881, +36080:20132, +36081:20348, +36082:20399, +36083:20505, +36084:20502, +36085:20809, +36086:20844, +36087:21151, +36088:21177, +36089:21246, +36090:21402, +36091:21475, +36092:21521, +36160:21518, +36161:21897, +36162:22353, +36163:22434, +36164:22909, +36165:23380, +36166:23389, +36167:23439, +36168:24037, +36169:24039, +36170:24055, +36171:24184, +36172:24195, +36173:24218, +36174:24247, +36175:24344, +36176:24658, +36177:24908, +36178:25239, +36179:25304, +36180:25511, +36181:25915, +36182:26114, +36183:26179, +36184:26356, +36185:26477, +36186:26657, +36187:26775, +36188:27083, +36189:27743, +36190:27946, +36191:28009, +36192:28207, +36193:28317, +36194:30002, +36195:30343, +36196:30828, +36197:31295, +36198:31968, +36199:32005, +36200:32024, +36201:32094, +36202:32177, +36203:32789, +36204:32771, +36205:32943, +36206:32945, +36207:33108, +36208:33167, +36209:33322, +36210:33618, +36211:34892, +36212:34913, +36213:35611, +36214:36002, +36215:36092, +36216:37066, +36217:37237, +36218:37489, +36219:30783, +36220:37628, +36221:38308, +36222:38477, +36224:38917, +36225:39321, +36226:39640, +36227:40251, +36228:21083, +36229:21163, +36230:21495, +36231:21512, +36232:22741, +36233:25335, +36234:28640, +36235:35946, +36236:36703, +36237:40633, +36238:20811, +36239:21051, +36240:21578, +36241:22269, +36242:31296, +36243:37239, +36244:40288, +36245:40658, +36246:29508, +36247:28425, +36248:33136, +36249:29969, +36250:24573, +36251:24794, +36252:39592, +36253:29403, +36254:36796, +36255:27492, +36256:38915, +36257:20170, +36258:22256, +36259:22372, +36260:22718, +36261:23130, +36262:24680, +36263:25031, +36264:26127, +36265:26118, +36266:26681, +36267:26801, +36268:28151, +36269:30165, +36270:32058, +36271:33390, +36272:39746, +36273:20123, +36274:20304, +36275:21449, +36276:21766, +36277:23919, +36278:24038, +36279:24046, +36280:26619, +36281:27801, +36282:29811, +36283:30722, +36284:35408, +36285:37782, +36286:35039, +36287:22352, +36288:24231, +36289:25387, +36290:20661, +36291:20652, +36292:20877, +36293:26368, +36294:21705, +36295:22622, +36296:22971, +36297:23472, +36298:24425, +36299:25165, +36300:25505, +36301:26685, +36302:27507, +36303:28168, +36304:28797, +36305:37319, +36306:29312, +36307:30741, +36308:30758, +36309:31085, +36310:25998, +36311:32048, +36312:33756, +36313:35009, +36314:36617, +36315:38555, +36316:21092, +36317:22312, +36318:26448, +36319:32618, +36320:36001, +36321:20916, +36322:22338, +36323:38442, +36324:22586, +36325:27018, +36326:32948, +36327:21682, +36328:23822, +36329:22524, +36330:30869, +36331:40442, +36332:20316, +36333:21066, +36334:21643, +36335:25662, +36336:26152, +36337:26388, +36338:26613, +36339:31364, +36340:31574, +36341:32034, +36342:37679, +36343:26716, +36344:39853, +36345:31545, +36346:21273, +36347:20874, +36348:21047, +36416:23519, +36417:25334, +36418:25774, +36419:25830, +36420:26413, +36421:27578, +36422:34217, +36423:38609, +36424:30352, +36425:39894, +36426:25420, +36427:37638, +36428:39851, +36429:30399, +36430:26194, +36431:19977, +36432:20632, +36433:21442, +36434:23665, +36435:24808, +36436:25746, +36437:25955, +36438:26719, +36439:29158, +36440:29642, +36441:29987, +36442:31639, +36443:32386, +36444:34453, +36445:35715, +36446:36059, +36447:37240, +36448:39184, +36449:26028, +36450:26283, +36451:27531, +36452:20181, +36453:20180, +36454:20282, +36455:20351, +36456:21050, +36457:21496, +36458:21490, +36459:21987, +36460:22235, +36461:22763, +36462:22987, +36463:22985, +36464:23039, +36465:23376, +36466:23629, +36467:24066, +36468:24107, +36469:24535, +36470:24605, +36471:25351, +36472:25903, +36473:23388, +36474:26031, +36475:26045, +36476:26088, +36477:26525, +36478:27490, +36480:27515, +36481:27663, +36482:29509, +36483:31049, +36484:31169, +36485:31992, +36486:32025, +36487:32043, +36488:32930, +36489:33026, +36490:33267, +36491:35222, +36492:35422, +36493:35433, +36494:35430, +36495:35468, +36496:35566, +36497:36039, +36498:36060, +36499:38604, +36500:39164, +36501:27503, +36502:20107, +36503:20284, +36504:20365, +36505:20816, +36506:23383, +36507:23546, +36508:24904, +36509:25345, +36510:26178, +36511:27425, +36512:28363, +36513:27835, +36514:29246, +36515:29885, +36516:30164, +36517:30913, +36518:31034, +36519:32780, +36520:32819, +36521:33258, +36522:33940, +36523:36766, +36524:27728, +36525:40575, +36526:24335, +36527:35672, +36528:40235, +36529:31482, +36530:36600, +36531:23437, +36532:38635, +36533:19971, +36534:21489, +36535:22519, +36536:22833, +36537:23241, +36538:23460, +36539:24713, +36540:28287, +36541:28422, +36542:30142, +36543:36074, +36544:23455, +36545:34048, +36546:31712, +36547:20594, +36548:26612, +36549:33437, +36550:23649, +36551:34122, +36552:32286, +36553:33294, +36554:20889, +36555:23556, +36556:25448, +36557:36198, +36558:26012, +36559:29038, +36560:31038, +36561:32023, +36562:32773, +36563:35613, +36564:36554, +36565:36974, +36566:34503, +36567:37034, +36568:20511, +36569:21242, +36570:23610, +36571:26451, +36572:28796, +36573:29237, +36574:37196, +36575:37320, +36576:37675, +36577:33509, +36578:23490, +36579:24369, +36580:24825, +36581:20027, +36582:21462, +36583:23432, +36584:25163, +36585:26417, +36586:27530, +36587:29417, +36588:29664, +36589:31278, +36590:33131, +36591:36259, +36592:37202, +36593:39318, +36594:20754, +36595:21463, +36596:21610, +36597:23551, +36598:25480, +36599:27193, +36600:32172, +36601:38656, +36602:22234, +36603:21454, +36604:21608, +36672:23447, +36673:23601, +36674:24030, +36675:20462, +36676:24833, +36677:25342, +36678:27954, +36679:31168, +36680:31179, +36681:32066, +36682:32333, +36683:32722, +36684:33261, +36685:33311, +36686:33936, +36687:34886, +36688:35186, +36689:35728, +36690:36468, +36691:36655, +36692:36913, +36693:37195, +36694:37228, +36695:38598, +36696:37276, +36697:20160, +36698:20303, +36699:20805, +36700:21313, +36701:24467, +36702:25102, +36703:26580, +36704:27713, +36705:28171, +36706:29539, +36707:32294, +36708:37325, +36709:37507, +36710:21460, +36711:22809, +36712:23487, +36713:28113, +36714:31069, +36715:32302, +36716:31899, +36717:22654, +36718:29087, +36719:20986, +36720:34899, +36721:36848, +36722:20426, +36723:23803, +36724:26149, +36725:30636, +36726:31459, +36727:33308, +36728:39423, +36729:20934, +36730:24490, +36731:26092, +36732:26991, +36733:27529, +36734:28147, +36736:28310, +36737:28516, +36738:30462, +36739:32020, +36740:24033, +36741:36981, +36742:37255, +36743:38918, +36744:20966, +36745:21021, +36746:25152, +36747:26257, +36748:26329, +36749:28186, +36750:24246, +36751:32210, +36752:32626, +36753:26360, +36754:34223, +36755:34295, +36756:35576, +36757:21161, +36758:21465, +36759:22899, +36760:24207, +36761:24464, +36762:24661, +36763:37604, +36764:38500, +36765:20663, +36766:20767, +36767:21213, +36768:21280, +36769:21319, +36770:21484, +36771:21736, +36772:21830, +36773:21809, +36774:22039, +36775:22888, +36776:22974, +36777:23100, +36778:23477, +36779:23558, +36780:23567, +36781:23569, +36782:23578, +36783:24196, +36784:24202, +36785:24288, +36786:24432, +36787:25215, +36788:25220, +36789:25307, +36790:25484, +36791:25463, +36792:26119, +36793:26124, +36794:26157, +36795:26230, +36796:26494, +36797:26786, +36798:27167, +36799:27189, +36800:27836, +36801:28040, +36802:28169, +36803:28248, +36804:28988, +36805:28966, +36806:29031, +36807:30151, +36808:30465, +36809:30813, +36810:30977, +36811:31077, +36812:31216, +36813:31456, +36814:31505, +36815:31911, +36816:32057, +36817:32918, +36818:33750, +36819:33931, +36820:34121, +36821:34909, +36822:35059, +36823:35359, +36824:35388, +36825:35412, +36826:35443, +36827:35937, +36828:36062, +36829:37284, +36830:37478, +36831:37758, +36832:37912, +36833:38556, +36834:38808, +36835:19978, +36836:19976, +36837:19998, +36838:20055, +36839:20887, +36840:21104, +36841:22478, +36842:22580, +36843:22732, +36844:23330, +36845:24120, +36846:24773, +36847:25854, +36848:26465, +36849:26454, +36850:27972, +36851:29366, +36852:30067, +36853:31331, +36854:33976, +36855:35698, +36856:37304, +36857:37664, +36858:22065, +36859:22516, +36860:39166, +36928:25325, +36929:26893, +36930:27542, +36931:29165, +36932:32340, +36933:32887, +36934:33394, +36935:35302, +36936:39135, +36937:34645, +36938:36785, +36939:23611, +36940:20280, +36941:20449, +36942:20405, +36943:21767, +36944:23072, +36945:23517, +36946:23529, +36947:24515, +36948:24910, +36949:25391, +36950:26032, +36951:26187, +36952:26862, +36953:27035, +36954:28024, +36955:28145, +36956:30003, +36957:30137, +36958:30495, +36959:31070, +36960:31206, +36961:32051, +36962:33251, +36963:33455, +36964:34218, +36965:35242, +36966:35386, +36967:36523, +36968:36763, +36969:36914, +36970:37341, +36971:38663, +36972:20154, +36973:20161, +36974:20995, +36975:22645, +36976:22764, +36977:23563, +36978:29978, +36979:23613, +36980:33102, +36981:35338, +36982:36805, +36983:38499, +36984:38765, +36985:31525, +36986:35535, +36987:38920, +36988:37218, +36989:22259, +36990:21416, +36992:36887, +36993:21561, +36994:22402, +36995:24101, +36996:25512, +36997:27700, +36998:28810, +36999:30561, +37000:31883, +37001:32736, +37002:34928, +37003:36930, +37004:37204, +37005:37648, +37006:37656, +37007:38543, +37008:29790, +37009:39620, +37010:23815, +37011:23913, +37012:25968, +37013:26530, +37014:36264, +37015:38619, +37016:25454, +37017:26441, +37018:26905, +37019:33733, +37020:38935, +37021:38592, +37022:35070, +37023:28548, +37024:25722, +37025:23544, +37026:19990, +37027:28716, +37028:30045, +37029:26159, +37030:20932, +37031:21046, +37032:21218, +37033:22995, +37034:24449, +37035:24615, +37036:25104, +37037:25919, +37038:25972, +37039:26143, +37040:26228, +37041:26866, +37042:26646, +37043:27491, +37044:28165, +37045:29298, +37046:29983, +37047:30427, +37048:31934, +37049:32854, +37050:22768, +37051:35069, +37052:35199, +37053:35488, +37054:35475, +37055:35531, +37056:36893, +37057:37266, +37058:38738, +37059:38745, +37060:25993, +37061:31246, +37062:33030, +37063:38587, +37064:24109, +37065:24796, +37066:25114, +37067:26021, +37068:26132, +37069:26512, +37070:30707, +37071:31309, +37072:31821, +37073:32318, +37074:33034, +37075:36012, +37076:36196, +37077:36321, +37078:36447, +37079:30889, +37080:20999, +37081:25305, +37082:25509, +37083:25666, +37084:25240, +37085:35373, +37086:31363, +37087:31680, +37088:35500, +37089:38634, +37090:32118, +37091:33292, +37092:34633, +37093:20185, +37094:20808, +37095:21315, +37096:21344, +37097:23459, +37098:23554, +37099:23574, +37100:24029, +37101:25126, +37102:25159, +37103:25776, +37104:26643, +37105:26676, +37106:27849, +37107:27973, +37108:27927, +37109:26579, +37110:28508, +37111:29006, +37112:29053, +37113:26059, +37114:31359, +37115:31661, +37116:32218, +37184:32330, +37185:32680, +37186:33146, +37187:33307, +37188:33337, +37189:34214, +37190:35438, +37191:36046, +37192:36341, +37193:36984, +37194:36983, +37195:37549, +37196:37521, +37197:38275, +37198:39854, +37199:21069, +37200:21892, +37201:28472, +37202:28982, +37203:20840, +37204:31109, +37205:32341, +37206:33203, +37207:31950, +37208:22092, +37209:22609, +37210:23720, +37211:25514, +37212:26366, +37213:26365, +37214:26970, +37215:29401, +37216:30095, +37217:30094, +37218:30990, +37219:31062, +37220:31199, +37221:31895, +37222:32032, +37223:32068, +37224:34311, +37225:35380, +37226:38459, +37227:36961, +37228:40736, +37229:20711, +37230:21109, +37231:21452, +37232:21474, +37233:20489, +37234:21930, +37235:22766, +37236:22863, +37237:29245, +37238:23435, +37239:23652, +37240:21277, +37241:24803, +37242:24819, +37243:25436, +37244:25475, +37245:25407, +37246:25531, +37248:25805, +37249:26089, +37250:26361, +37251:24035, +37252:27085, +37253:27133, +37254:28437, +37255:29157, +37256:20105, +37257:30185, +37258:30456, +37259:31379, +37260:31967, +37261:32207, +37262:32156, +37263:32865, +37264:33609, +37265:33624, +37266:33900, +37267:33980, +37268:34299, +37269:35013, +37270:36208, +37271:36865, +37272:36973, +37273:37783, +37274:38684, +37275:39442, +37276:20687, +37277:22679, +37278:24974, +37279:33235, +37280:34101, +37281:36104, +37282:36896, +37283:20419, +37284:20596, +37285:21063, +37286:21363, +37287:24687, +37288:25417, +37289:26463, +37290:28204, +37291:36275, +37292:36895, +37293:20439, +37294:23646, +37295:36042, +37296:26063, +37297:32154, +37298:21330, +37299:34966, +37300:20854, +37301:25539, +37302:23384, +37303:23403, +37304:23562, +37305:25613, +37306:26449, +37307:36956, +37308:20182, +37309:22810, +37310:22826, +37311:27760, +37312:35409, +37313:21822, +37314:22549, +37315:22949, +37316:24816, +37317:25171, +37318:26561, +37319:33333, +37320:26965, +37321:38464, +37322:39364, +37323:39464, +37324:20307, +37325:22534, +37326:23550, +37327:32784, +37328:23729, +37329:24111, +37330:24453, +37331:24608, +37332:24907, +37333:25140, +37334:26367, +37335:27888, +37336:28382, +37337:32974, +37338:33151, +37339:33492, +37340:34955, +37341:36024, +37342:36864, +37343:36910, +37344:38538, +37345:40667, +37346:39899, +37347:20195, +37348:21488, +37349:22823, +37350:31532, +37351:37261, +37352:38988, +37353:40441, +37354:28381, +37355:28711, +37356:21331, +37357:21828, +37358:23429, +37359:25176, +37360:25246, +37361:25299, +37362:27810, +37363:28655, +37364:29730, +37365:35351, +37366:37944, +37367:28609, +37368:35582, +37369:33592, +37370:20967, +37371:34552, +37372:21482, +37440:21481, +37441:20294, +37442:36948, +37443:36784, +37444:22890, +37445:33073, +37446:24061, +37447:31466, +37448:36799, +37449:26842, +37450:35895, +37451:29432, +37452:40008, +37453:27197, +37454:35504, +37455:20025, +37456:21336, +37457:22022, +37458:22374, +37459:25285, +37460:25506, +37461:26086, +37462:27470, +37463:28129, +37464:28251, +37465:28845, +37466:30701, +37467:31471, +37468:31658, +37469:32187, +37470:32829, +37471:32966, +37472:34507, +37473:35477, +37474:37723, +37475:22243, +37476:22727, +37477:24382, +37478:26029, +37479:26262, +37480:27264, +37481:27573, +37482:30007, +37483:35527, +37484:20516, +37485:30693, +37486:22320, +37487:24347, +37488:24677, +37489:26234, +37490:27744, +37491:30196, +37492:31258, +37493:32622, +37494:33268, +37495:34584, +37496:36933, +37497:39347, +37498:31689, +37499:30044, +37500:31481, +37501:31569, +37502:33988, +37504:36880, +37505:31209, +37506:31378, +37507:33590, +37508:23265, +37509:30528, +37510:20013, +37511:20210, +37512:23449, +37513:24544, +37514:25277, +37515:26172, +37516:26609, +37517:27880, +37518:34411, +37519:34935, +37520:35387, +37521:37198, +37522:37619, +37523:39376, +37524:27159, +37525:28710, +37526:29482, +37527:33511, +37528:33879, +37529:36015, +37530:19969, +37531:20806, +37532:20939, +37533:21899, +37534:23541, +37535:24086, +37536:24115, +37537:24193, +37538:24340, +37539:24373, +37540:24427, +37541:24500, +37542:25074, +37543:25361, +37544:26274, +37545:26397, +37546:28526, +37547:29266, +37548:30010, +37549:30522, +37550:32884, +37551:33081, +37552:33144, +37553:34678, +37554:35519, +37555:35548, +37556:36229, +37557:36339, +37558:37530, +37559:38263, +37560:38914, +37561:40165, +37562:21189, +37563:25431, +37564:30452, +37565:26389, +37566:27784, +37567:29645, +37568:36035, +37569:37806, +37570:38515, +37571:27941, +37572:22684, +37573:26894, +37574:27084, +37575:36861, +37576:37786, +37577:30171, +37578:36890, +37579:22618, +37580:26626, +37581:25524, +37582:27131, +37583:20291, +37584:28460, +37585:26584, +37586:36795, +37587:34086, +37588:32180, +37589:37716, +37590:26943, +37591:28528, +37592:22378, +37593:22775, +37594:23340, +37595:32044, +37596:29226, +37597:21514, +37598:37347, +37599:40372, +37600:20141, +37601:20302, +37602:20572, +37603:20597, +37604:21059, +37605:35998, +37606:21576, +37607:22564, +37608:23450, +37609:24093, +37610:24213, +37611:24237, +37612:24311, +37613:24351, +37614:24716, +37615:25269, +37616:25402, +37617:25552, +37618:26799, +37619:27712, +37620:30855, +37621:31118, +37622:31243, +37623:32224, +37624:33351, +37625:35330, +37626:35558, +37627:36420, +37628:36883, +37696:37048, +37697:37165, +37698:37336, +37699:40718, +37700:27877, +37701:25688, +37702:25826, +37703:25973, +37704:28404, +37705:30340, +37706:31515, +37707:36969, +37708:37841, +37709:28346, +37710:21746, +37711:24505, +37712:25764, +37713:36685, +37714:36845, +37715:37444, +37716:20856, +37717:22635, +37718:22825, +37719:23637, +37720:24215, +37721:28155, +37722:32399, +37723:29980, +37724:36028, +37725:36578, +37726:39003, +37727:28857, +37728:20253, +37729:27583, +37730:28593, +37731:30000, +37732:38651, +37733:20814, +37734:21520, +37735:22581, +37736:22615, +37737:22956, +37738:23648, +37739:24466, +37740:26007, +37741:26460, +37742:28193, +37743:30331, +37744:33759, +37745:36077, +37746:36884, +37747:37117, +37748:37709, +37749:30757, +37750:30778, +37751:21162, +37752:24230, +37753:22303, +37754:22900, +37755:24594, +37756:20498, +37757:20826, +37758:20908, +37760:20941, +37761:20992, +37762:21776, +37763:22612, +37764:22616, +37765:22871, +37766:23445, +37767:23798, +37768:23947, +37769:24764, +37770:25237, +37771:25645, +37772:26481, +37773:26691, +37774:26812, +37775:26847, +37776:30423, +37777:28120, +37778:28271, +37779:28059, +37780:28783, +37781:29128, +37782:24403, +37783:30168, +37784:31095, +37785:31561, +37786:31572, +37787:31570, +37788:31958, +37789:32113, +37790:21040, +37791:33891, +37792:34153, +37793:34276, +37794:35342, +37795:35588, +37796:35910, +37797:36367, +37798:36867, +37799:36879, +37800:37913, +37801:38518, +37802:38957, +37803:39472, +37804:38360, +37805:20685, +37806:21205, +37807:21516, +37808:22530, +37809:23566, +37810:24999, +37811:25758, +37812:27934, +37813:30643, +37814:31461, +37815:33012, +37816:33796, +37817:36947, +37818:37509, +37819:23776, +37820:40199, +37821:21311, +37822:24471, +37823:24499, +37824:28060, +37825:29305, +37826:30563, +37827:31167, +37828:31716, +37829:27602, +37830:29420, +37831:35501, +37832:26627, +37833:27233, +37834:20984, +37835:31361, +37836:26932, +37837:23626, +37838:40182, +37839:33515, +37840:23493, +37841:37193, +37842:28702, +37843:22136, +37844:23663, +37845:24775, +37846:25958, +37847:27788, +37848:35930, +37849:36929, +37850:38931, +37851:21585, +37852:26311, +37853:37389, +37854:22856, +37855:37027, +37856:20869, +37857:20045, +37858:20970, +37859:34201, +37860:35598, +37861:28760, +37862:25466, +37863:37707, +37864:26978, +37865:39348, +37866:32260, +37867:30071, +37868:21335, +37869:26976, +37870:36575, +37871:38627, +37872:27741, +37873:20108, +37874:23612, +37875:24336, +37876:36841, +37877:21250, +37878:36049, +37879:32905, +37880:34425, +37881:24319, +37882:26085, +37883:20083, +37884:20837, +37952:22914, +37953:23615, +37954:38894, +37955:20219, +37956:22922, +37957:24525, +37958:35469, +37959:28641, +37960:31152, +37961:31074, +37962:23527, +37963:33905, +37964:29483, +37965:29105, +37966:24180, +37967:24565, +37968:25467, +37969:25754, +37970:29123, +37971:31896, +37972:20035, +37973:24316, +37974:20043, +37975:22492, +37976:22178, +37977:24745, +37978:28611, +37979:32013, +37980:33021, +37981:33075, +37982:33215, +37983:36786, +37984:35223, +37985:34468, +37986:24052, +37987:25226, +37988:25773, +37989:35207, +37990:26487, +37991:27874, +37992:27966, +37993:29750, +37994:30772, +37995:23110, +37996:32629, +37997:33453, +37998:39340, +37999:20467, +38000:24259, +38001:25309, +38002:25490, +38003:25943, +38004:26479, +38005:30403, +38006:29260, +38007:32972, +38008:32954, +38009:36649, +38010:37197, +38011:20493, +38012:22521, +38013:23186, +38014:26757, +38016:26995, +38017:29028, +38018:29437, +38019:36023, +38020:22770, +38021:36064, +38022:38506, +38023:36889, +38024:34687, +38025:31204, +38026:30695, +38027:33833, +38028:20271, +38029:21093, +38030:21338, +38031:25293, +38032:26575, +38033:27850, +38034:30333, +38035:31636, +38036:31893, +38037:33334, +38038:34180, +38039:36843, +38040:26333, +38041:28448, +38042:29190, +38043:32283, +38044:33707, +38045:39361, +38046:40614, +38047:20989, +38048:31665, +38049:30834, +38050:31672, +38051:32903, +38052:31560, +38053:27368, +38054:24161, +38055:32908, +38056:30033, +38057:30048, +38058:20843, +38059:37474, +38060:28300, +38061:30330, +38062:37271, +38063:39658, +38064:20240, +38065:32624, +38066:25244, +38067:31567, +38068:38309, +38069:40169, +38070:22138, +38071:22617, +38072:34532, +38073:38588, +38074:20276, +38075:21028, +38076:21322, +38077:21453, +38078:21467, +38079:24070, +38080:25644, +38081:26001, +38082:26495, +38083:27710, +38084:27726, +38085:29256, +38086:29359, +38087:29677, +38088:30036, +38089:32321, +38090:33324, +38091:34281, +38092:36009, +38093:31684, +38094:37318, +38095:29033, +38096:38930, +38097:39151, +38098:25405, +38099:26217, +38100:30058, +38101:30436, +38102:30928, +38103:34115, +38104:34542, +38105:21290, +38106:21329, +38107:21542, +38108:22915, +38109:24199, +38110:24444, +38111:24754, +38112:25161, +38113:25209, +38114:25259, +38115:26000, +38116:27604, +38117:27852, +38118:30130, +38119:30382, +38120:30865, +38121:31192, +38122:32203, +38123:32631, +38124:32933, +38125:34987, +38126:35513, +38127:36027, +38128:36991, +38129:38750, +38130:39131, +38131:27147, +38132:31800, +38133:20633, +38134:23614, +38135:24494, +38136:26503, +38137:27608, +38138:29749, +38139:30473, +38140:32654, +38208:40763, +38209:26570, +38210:31255, +38211:21305, +38212:30091, +38213:39661, +38214:24422, +38215:33181, +38216:33777, +38217:32920, +38218:24380, +38219:24517, +38220:30050, +38221:31558, +38222:36924, +38223:26727, +38224:23019, +38225:23195, +38226:32016, +38227:30334, +38228:35628, +38229:20469, +38230:24426, +38231:27161, +38232:27703, +38233:28418, +38234:29922, +38235:31080, +38236:34920, +38237:35413, +38238:35961, +38239:24287, +38240:25551, +38241:30149, +38242:31186, +38243:33495, +38244:37672, +38245:37618, +38246:33948, +38247:34541, +38248:39981, +38249:21697, +38250:24428, +38251:25996, +38252:27996, +38253:28693, +38254:36007, +38255:36051, +38256:38971, +38257:25935, +38258:29942, +38259:19981, +38260:20184, +38261:22496, +38262:22827, +38263:23142, +38264:23500, +38265:20904, +38266:24067, +38267:24220, +38268:24598, +38269:25206, +38270:25975, +38272:26023, +38273:26222, +38274:28014, +38275:29238, +38276:31526, +38277:33104, +38278:33178, +38279:33433, +38280:35676, +38281:36000, +38282:36070, +38283:36212, +38284:38428, +38285:38468, +38286:20398, +38287:25771, +38288:27494, +38289:33310, +38290:33889, +38291:34154, +38292:37096, +38293:23553, +38294:26963, +38295:39080, +38296:33914, +38297:34135, +38298:20239, +38299:21103, +38300:24489, +38301:24133, +38302:26381, +38303:31119, +38304:33145, +38305:35079, +38306:35206, +38307:28149, +38308:24343, +38309:25173, +38310:27832, +38311:20175, +38312:29289, +38313:39826, +38314:20998, +38315:21563, +38316:22132, +38317:22707, +38318:24996, +38319:25198, +38320:28954, +38321:22894, +38322:31881, +38323:31966, +38324:32027, +38325:38640, +38326:25991, +38327:32862, +38328:19993, +38329:20341, +38330:20853, +38331:22592, +38332:24163, +38333:24179, +38334:24330, +38335:26564, +38336:20006, +38337:34109, +38338:38281, +38339:38491, +38340:31859, +38341:38913, +38342:20731, +38343:22721, +38344:30294, +38345:30887, +38346:21029, +38347:30629, +38348:34065, +38349:31622, +38350:20559, +38351:22793, +38352:29255, +38353:31687, +38354:32232, +38355:36794, +38356:36820, +38357:36941, +38358:20415, +38359:21193, +38360:23081, +38361:24321, +38362:38829, +38363:20445, +38364:33303, +38365:37610, +38366:22275, +38367:25429, +38368:27497, +38369:29995, +38370:35036, +38371:36628, +38372:31298, +38373:21215, +38374:22675, +38375:24917, +38376:25098, +38377:26286, +38378:27597, +38379:31807, +38380:33769, +38381:20515, +38382:20472, +38383:21253, +38384:21574, +38385:22577, +38386:22857, +38387:23453, +38388:23792, +38389:23791, +38390:23849, +38391:24214, +38392:25265, +38393:25447, +38394:25918, +38395:26041, +38396:26379, +38464:27861, +38465:27873, +38466:28921, +38467:30770, +38468:32299, +38469:32990, +38470:33459, +38471:33804, +38472:34028, +38473:34562, +38474:35090, +38475:35370, +38476:35914, +38477:37030, +38478:37586, +38479:39165, +38480:40179, +38481:40300, +38482:20047, +38483:20129, +38484:20621, +38485:21078, +38486:22346, +38487:22952, +38488:24125, +38489:24536, +38490:24537, +38491:25151, +38492:26292, +38493:26395, +38494:26576, +38495:26834, +38496:20882, +38497:32033, +38498:32938, +38499:33192, +38500:35584, +38501:35980, +38502:36031, +38503:37502, +38504:38450, +38505:21536, +38506:38956, +38507:21271, +38508:20693, +38509:21340, +38510:22696, +38511:25778, +38512:26420, +38513:29287, +38514:30566, +38515:31302, +38516:37350, +38517:21187, +38518:27809, +38519:27526, +38520:22528, +38521:24140, +38522:22868, +38523:26412, +38524:32763, +38525:20961, +38526:30406, +38528:25705, +38529:30952, +38530:39764, +38531:40635, +38532:22475, +38533:22969, +38534:26151, +38535:26522, +38536:27598, +38537:21737, +38538:27097, +38539:24149, +38540:33180, +38541:26517, +38542:39850, +38543:26622, +38544:40018, +38545:26717, +38546:20134, +38547:20451, +38548:21448, +38549:25273, +38550:26411, +38551:27819, +38552:36804, +38553:20397, +38554:32365, +38555:40639, +38556:19975, +38557:24930, +38558:28288, +38559:28459, +38560:34067, +38561:21619, +38562:26410, +38563:39749, +38564:24051, +38565:31637, +38566:23724, +38567:23494, +38568:34588, +38569:28234, +38570:34001, +38571:31252, +38572:33032, +38573:22937, +38574:31885, +38575:27665, +38576:30496, +38577:21209, +38578:22818, +38579:28961, +38580:29279, +38581:30683, +38582:38695, +38583:40289, +38584:26891, +38585:23167, +38586:23064, +38587:20901, +38588:21517, +38589:21629, +38590:26126, +38591:30431, +38592:36855, +38593:37528, +38594:40180, +38595:23018, +38596:29277, +38597:28357, +38598:20813, +38599:26825, +38600:32191, +38601:32236, +38602:38754, +38603:40634, +38604:25720, +38605:27169, +38606:33538, +38607:22916, +38608:23391, +38609:27611, +38610:29467, +38611:30450, +38612:32178, +38613:32791, +38614:33945, +38615:20786, +38616:26408, +38617:40665, +38618:30446, +38619:26466, +38620:21247, +38621:39173, +38622:23588, +38623:25147, +38624:31870, +38625:36016, +38626:21839, +38627:24758, +38628:32011, +38629:38272, +38630:21249, +38631:20063, +38632:20918, +38633:22812, +38634:29242, +38635:32822, +38636:37326, +38637:24357, +38638:30690, +38639:21380, +38640:24441, +38641:32004, +38642:34220, +38643:35379, +38644:36493, +38645:38742, +38646:26611, +38647:34222, +38648:37971, +38649:24841, +38650:24840, +38651:27833, +38652:30290, +38720:35565, +38721:36664, +38722:21807, +38723:20305, +38724:20778, +38725:21191, +38726:21451, +38727:23461, +38728:24189, +38729:24736, +38730:24962, +38731:25558, +38732:26377, +38733:26586, +38734:28263, +38735:28044, +38736:29494, +38737:29495, +38738:30001, +38739:31056, +38740:35029, +38741:35480, +38742:36938, +38743:37009, +38744:37109, +38745:38596, +38746:34701, +38747:22805, +38748:20104, +38749:20313, +38750:19982, +38751:35465, +38752:36671, +38753:38928, +38754:20653, +38755:24188, +38756:22934, +38757:23481, +38758:24248, +38759:25562, +38760:25594, +38761:25793, +38762:26332, +38763:26954, +38764:27096, +38765:27915, +38766:28342, +38767:29076, +38768:29992, +38769:31407, +38770:32650, +38771:32768, +38772:33865, +38773:33993, +38774:35201, +38775:35617, +38776:36362, +38777:36965, +38778:38525, +38779:39178, +38780:24958, +38781:25233, +38782:27442, +38784:27779, +38785:28020, +38786:32716, +38787:32764, +38788:28096, +38789:32645, +38790:34746, +38791:35064, +38792:26469, +38793:33713, +38794:38972, +38795:38647, +38796:27931, +38797:32097, +38798:33853, +38799:37226, +38800:20081, +38801:21365, +38802:23888, +38803:27396, +38804:28651, +38805:34253, +38806:34349, +38807:35239, +38808:21033, +38809:21519, +38810:23653, +38811:26446, +38812:26792, +38813:29702, +38814:29827, +38815:30178, +38816:35023, +38817:35041, +38818:37324, +38819:38626, +38820:38520, +38821:24459, +38822:29575, +38823:31435, +38824:33870, +38825:25504, +38826:30053, +38827:21129, +38828:27969, +38829:28316, +38830:29705, +38831:30041, +38832:30827, +38833:31890, +38834:38534, +38835:31452, +38836:40845, +38837:20406, +38838:24942, +38839:26053, +38840:34396, +38841:20102, +38842:20142, +38843:20698, +38844:20001, +38845:20940, +38846:23534, +38847:26009, +38848:26753, +38849:28092, +38850:29471, +38851:30274, +38852:30637, +38853:31260, +38854:31975, +38855:33391, +38856:35538, +38857:36988, +38858:37327, +38859:38517, +38860:38936, +38861:21147, +38862:32209, +38863:20523, +38864:21400, +38865:26519, +38866:28107, +38867:29136, +38868:29747, +38869:33256, +38870:36650, +38871:38563, +38872:40023, +38873:40607, +38874:29792, +38875:22593, +38876:28057, +38877:32047, +38878:39006, +38879:20196, +38880:20278, +38881:20363, +38882:20919, +38883:21169, +38884:23994, +38885:24604, +38886:29618, +38887:31036, +38888:33491, +38889:37428, +38890:38583, +38891:38646, +38892:38666, +38893:40599, +38894:40802, +38895:26278, +38896:27508, +38897:21015, +38898:21155, +38899:28872, +38900:35010, +38901:24265, +38902:24651, +38903:24976, +38904:28451, +38905:29001, +38906:31806, +38907:32244, +38908:32879, +38976:34030, +38977:36899, +38978:37676, +38979:21570, +38980:39791, +38981:27347, +38982:28809, +38983:36034, +38984:36335, +38985:38706, +38986:21172, +38987:23105, +38988:24266, +38989:24324, +38990:26391, +38991:27004, +38992:27028, +38993:28010, +38994:28431, +38995:29282, +38996:29436, +38997:31725, +38998:32769, +38999:32894, +39000:34635, +39001:37070, +39002:20845, +39003:40595, +39004:31108, +39005:32907, +39006:37682, +39007:35542, +39008:20525, +39009:21644, +39010:35441, +39011:27498, +39012:36036, +39013:33031, +39014:24785, +39015:26528, +39016:40434, +39017:20121, +39018:20120, +39019:39952, +39020:35435, +39021:34241, +39022:34152, +39023:26880, +39024:28286, +39025:30871, +39026:33109, +39071:24332, +39072:19984, +39073:19989, +39074:20010, +39075:20017, +39076:20022, +39077:20028, +39078:20031, +39079:20034, +39080:20054, +39081:20056, +39082:20098, +39083:20101, +39084:35947, +39085:20106, +39086:33298, +39087:24333, +39088:20110, +39089:20126, +39090:20127, +39091:20128, +39092:20130, +39093:20144, +39094:20147, +39095:20150, +39096:20174, +39097:20173, +39098:20164, +39099:20166, +39100:20162, +39101:20183, +39102:20190, +39103:20205, +39104:20191, +39105:20215, +39106:20233, +39107:20314, +39108:20272, +39109:20315, +39110:20317, +39111:20311, +39112:20295, +39113:20342, +39114:20360, +39115:20367, +39116:20376, +39117:20347, +39118:20329, +39119:20336, +39120:20369, +39121:20335, +39122:20358, +39123:20374, +39124:20760, +39125:20436, +39126:20447, +39127:20430, +39128:20440, +39129:20443, +39130:20433, +39131:20442, +39132:20432, +39133:20452, +39134:20453, +39135:20506, +39136:20520, +39137:20500, +39138:20522, +39139:20517, +39140:20485, +39141:20252, +39142:20470, +39143:20513, +39144:20521, +39145:20524, +39146:20478, +39147:20463, +39148:20497, +39149:20486, +39150:20547, +39151:20551, +39152:26371, +39153:20565, +39154:20560, +39155:20552, +39156:20570, +39157:20566, +39158:20588, +39159:20600, +39160:20608, +39161:20634, +39162:20613, +39163:20660, +39164:20658, +39232:20681, +39233:20682, +39234:20659, +39235:20674, +39236:20694, +39237:20702, +39238:20709, +39239:20717, +39240:20707, +39241:20718, +39242:20729, +39243:20725, +39244:20745, +39245:20737, +39246:20738, +39247:20758, +39248:20757, +39249:20756, +39250:20762, +39251:20769, +39252:20794, +39253:20791, +39254:20796, +39255:20795, +39256:20799, +39257:20800, +39258:20818, +39259:20812, +39260:20820, +39261:20834, +39262:31480, +39263:20841, +39264:20842, +39265:20846, +39266:20864, +39267:20866, +39268:22232, +39269:20876, +39270:20873, +39271:20879, +39272:20881, +39273:20883, +39274:20885, +39275:20886, +39276:20900, +39277:20902, +39278:20898, +39279:20905, +39280:20906, +39281:20907, +39282:20915, +39283:20913, +39284:20914, +39285:20912, +39286:20917, +39287:20925, +39288:20933, +39289:20937, +39290:20955, +39291:20960, +39292:34389, +39293:20969, +39294:20973, +39296:20976, +39297:20981, +39298:20990, +39299:20996, +39300:21003, +39301:21012, +39302:21006, +39303:21031, +39304:21034, +39305:21038, +39306:21043, +39307:21049, +39308:21071, +39309:21060, +39310:21067, +39311:21068, +39312:21086, +39313:21076, +39314:21098, +39315:21108, +39316:21097, +39317:21107, +39318:21119, +39319:21117, +39320:21133, +39321:21140, +39322:21138, +39323:21105, +39324:21128, +39325:21137, +39326:36776, +39327:36775, +39328:21164, +39329:21165, +39330:21180, +39331:21173, +39332:21185, +39333:21197, +39334:21207, +39335:21214, +39336:21219, +39337:21222, +39338:39149, +39339:21216, +39340:21235, +39341:21237, +39342:21240, +39343:21241, +39344:21254, +39345:21256, +39346:30008, +39347:21261, +39348:21264, +39349:21263, +39350:21269, +39351:21274, +39352:21283, +39353:21295, +39354:21297, +39355:21299, +39356:21304, +39357:21312, +39358:21318, +39359:21317, +39360:19991, +39361:21321, +39362:21325, +39363:20950, +39364:21342, +39365:21353, +39366:21358, +39367:22808, +39368:21371, +39369:21367, +39370:21378, +39371:21398, +39372:21408, +39373:21414, +39374:21413, +39375:21422, +39376:21424, +39377:21430, +39378:21443, +39379:31762, +39380:38617, +39381:21471, +39382:26364, +39383:29166, +39384:21486, +39385:21480, +39386:21485, +39387:21498, +39388:21505, +39389:21565, +39390:21568, +39391:21548, +39392:21549, +39393:21564, +39394:21550, +39395:21558, +39396:21545, +39397:21533, +39398:21582, +39399:21647, +39400:21621, +39401:21646, +39402:21599, +39403:21617, +39404:21623, +39405:21616, +39406:21650, +39407:21627, +39408:21632, +39409:21622, +39410:21636, +39411:21648, +39412:21638, +39413:21703, +39414:21666, +39415:21688, +39416:21669, +39417:21676, +39418:21700, +39419:21704, +39420:21672, +39488:21675, +39489:21698, +39490:21668, +39491:21694, +39492:21692, +39493:21720, +39494:21733, +39495:21734, +39496:21775, +39497:21780, +39498:21757, +39499:21742, +39500:21741, +39501:21754, +39502:21730, +39503:21817, +39504:21824, +39505:21859, +39506:21836, +39507:21806, +39508:21852, +39509:21829, +39510:21846, +39511:21847, +39512:21816, +39513:21811, +39514:21853, +39515:21913, +39516:21888, +39517:21679, +39518:21898, +39519:21919, +39520:21883, +39521:21886, +39522:21912, +39523:21918, +39524:21934, +39525:21884, +39526:21891, +39527:21929, +39528:21895, +39529:21928, +39530:21978, +39531:21957, +39532:21983, +39533:21956, +39534:21980, +39535:21988, +39536:21972, +39537:22036, +39538:22007, +39539:22038, +39540:22014, +39541:22013, +39542:22043, +39543:22009, +39544:22094, +39545:22096, +39546:29151, +39547:22068, +39548:22070, +39549:22066, +39550:22072, +39552:22123, +39553:22116, +39554:22063, +39555:22124, +39556:22122, +39557:22150, +39558:22144, +39559:22154, +39560:22176, +39561:22164, +39562:22159, +39563:22181, +39564:22190, +39565:22198, +39566:22196, +39567:22210, +39568:22204, +39569:22209, +39570:22211, +39571:22208, +39572:22216, +39573:22222, +39574:22225, +39575:22227, +39576:22231, +39577:22254, +39578:22265, +39579:22272, +39580:22271, +39581:22276, +39582:22281, +39583:22280, +39584:22283, +39585:22285, +39586:22291, +39587:22296, +39588:22294, +39589:21959, +39590:22300, +39591:22310, +39592:22327, +39593:22328, +39594:22350, +39595:22331, +39596:22336, +39597:22351, +39598:22377, +39599:22464, +39600:22408, +39601:22369, +39602:22399, +39603:22409, +39604:22419, +39605:22432, +39606:22451, +39607:22436, +39608:22442, +39609:22448, +39610:22467, +39611:22470, +39612:22484, +39613:22482, +39614:22483, +39615:22538, +39616:22486, +39617:22499, +39618:22539, +39619:22553, +39620:22557, +39621:22642, +39622:22561, +39623:22626, +39624:22603, +39625:22640, +39626:27584, +39627:22610, +39628:22589, +39629:22649, +39630:22661, +39631:22713, +39632:22687, +39633:22699, +39634:22714, +39635:22750, +39636:22715, +39637:22712, +39638:22702, +39639:22725, +39640:22739, +39641:22737, +39642:22743, +39643:22745, +39644:22744, +39645:22757, +39646:22748, +39647:22756, +39648:22751, +39649:22767, +39650:22778, +39651:22777, +39652:22779, +39653:22780, +39654:22781, +39655:22786, +39656:22794, +39657:22800, +39658:22811, +39659:26790, +39660:22821, +39661:22828, +39662:22829, +39663:22834, +39664:22840, +39665:22846, +39666:31442, +39667:22869, +39668:22864, +39669:22862, +39670:22874, +39671:22872, +39672:22882, +39673:22880, +39674:22887, +39675:22892, +39676:22889, +39744:22904, +39745:22913, +39746:22941, +39747:20318, +39748:20395, +39749:22947, +39750:22962, +39751:22982, +39752:23016, +39753:23004, +39754:22925, +39755:23001, +39756:23002, +39757:23077, +39758:23071, +39759:23057, +39760:23068, +39761:23049, +39762:23066, +39763:23104, +39764:23148, +39765:23113, +39766:23093, +39767:23094, +39768:23138, +39769:23146, +39770:23194, +39771:23228, +39772:23230, +39773:23243, +39774:23234, +39775:23229, +39776:23267, +39777:23255, +39778:23270, +39779:23273, +39780:23254, +39781:23290, +39782:23291, +39783:23308, +39784:23307, +39785:23318, +39786:23346, +39787:23248, +39788:23338, +39789:23350, +39790:23358, +39791:23363, +39792:23365, +39793:23360, +39794:23377, +39795:23381, +39796:23386, +39797:23387, +39798:23397, +39799:23401, +39800:23408, +39801:23411, +39802:23413, +39803:23416, +39804:25992, +39805:23418, +39806:23424, +39808:23427, +39809:23462, +39810:23480, +39811:23491, +39812:23495, +39813:23497, +39814:23508, +39815:23504, +39816:23524, +39817:23526, +39818:23522, +39819:23518, +39820:23525, +39821:23531, +39822:23536, +39823:23542, +39824:23539, +39825:23557, +39826:23559, +39827:23560, +39828:23565, +39829:23571, +39830:23584, +39831:23586, +39832:23592, +39833:23608, +39834:23609, +39835:23617, +39836:23622, +39837:23630, +39838:23635, +39839:23632, +39840:23631, +39841:23409, +39842:23660, +39843:23662, +39844:20066, +39845:23670, +39846:23673, +39847:23692, +39848:23697, +39849:23700, +39850:22939, +39851:23723, +39852:23739, +39853:23734, +39854:23740, +39855:23735, +39856:23749, +39857:23742, +39858:23751, +39859:23769, +39860:23785, +39861:23805, +39862:23802, +39863:23789, +39864:23948, +39865:23786, +39866:23819, +39867:23829, +39868:23831, +39869:23900, +39870:23839, +39871:23835, +39872:23825, +39873:23828, +39874:23842, +39875:23834, +39876:23833, +39877:23832, +39878:23884, +39879:23890, +39880:23886, +39881:23883, +39882:23916, +39883:23923, +39884:23926, +39885:23943, +39886:23940, +39887:23938, +39888:23970, +39889:23965, +39890:23980, +39891:23982, +39892:23997, +39893:23952, +39894:23991, +39895:23996, +39896:24009, +39897:24013, +39898:24019, +39899:24018, +39900:24022, +39901:24027, +39902:24043, +39903:24050, +39904:24053, +39905:24075, +39906:24090, +39907:24089, +39908:24081, +39909:24091, +39910:24118, +39911:24119, +39912:24132, +39913:24131, +39914:24128, +39915:24142, +39916:24151, +39917:24148, +39918:24159, +39919:24162, +39920:24164, +39921:24135, +39922:24181, +39923:24182, +39924:24186, +39925:40636, +39926:24191, +39927:24224, +39928:24257, +39929:24258, +39930:24264, +39931:24272, +39932:24271, +40000:24278, +40001:24291, +40002:24285, +40003:24282, +40004:24283, +40005:24290, +40006:24289, +40007:24296, +40008:24297, +40009:24300, +40010:24305, +40011:24307, +40012:24304, +40013:24308, +40014:24312, +40015:24318, +40016:24323, +40017:24329, +40018:24413, +40019:24412, +40020:24331, +40021:24337, +40022:24342, +40023:24361, +40024:24365, +40025:24376, +40026:24385, +40027:24392, +40028:24396, +40029:24398, +40030:24367, +40031:24401, +40032:24406, +40033:24407, +40034:24409, +40035:24417, +40036:24429, +40037:24435, +40038:24439, +40039:24451, +40040:24450, +40041:24447, +40042:24458, +40043:24456, +40044:24465, +40045:24455, +40046:24478, +40047:24473, +40048:24472, +40049:24480, +40050:24488, +40051:24493, +40052:24508, +40053:24534, +40054:24571, +40055:24548, +40056:24568, +40057:24561, +40058:24541, +40059:24755, +40060:24575, +40061:24609, +40062:24672, +40064:24601, +40065:24592, +40066:24617, +40067:24590, +40068:24625, +40069:24603, +40070:24597, +40071:24619, +40072:24614, +40073:24591, +40074:24634, +40075:24666, +40076:24641, +40077:24682, +40078:24695, +40079:24671, +40080:24650, +40081:24646, +40082:24653, +40083:24675, +40084:24643, +40085:24676, +40086:24642, +40087:24684, +40088:24683, +40089:24665, +40090:24705, +40091:24717, +40092:24807, +40093:24707, +40094:24730, +40095:24708, +40096:24731, +40097:24726, +40098:24727, +40099:24722, +40100:24743, +40101:24715, +40102:24801, +40103:24760, +40104:24800, +40105:24787, +40106:24756, +40107:24560, +40108:24765, +40109:24774, +40110:24757, +40111:24792, +40112:24909, +40113:24853, +40114:24838, +40115:24822, +40116:24823, +40117:24832, +40118:24820, +40119:24826, +40120:24835, +40121:24865, +40122:24827, +40123:24817, +40124:24845, +40125:24846, +40126:24903, +40127:24894, +40128:24872, +40129:24871, +40130:24906, +40131:24895, +40132:24892, +40133:24876, +40134:24884, +40135:24893, +40136:24898, +40137:24900, +40138:24947, +40139:24951, +40140:24920, +40141:24921, +40142:24922, +40143:24939, +40144:24948, +40145:24943, +40146:24933, +40147:24945, +40148:24927, +40149:24925, +40150:24915, +40151:24949, +40152:24985, +40153:24982, +40154:24967, +40155:25004, +40156:24980, +40157:24986, +40158:24970, +40159:24977, +40160:25003, +40161:25006, +40162:25036, +40163:25034, +40164:25033, +40165:25079, +40166:25032, +40167:25027, +40168:25030, +40169:25018, +40170:25035, +40171:32633, +40172:25037, +40173:25062, +40174:25059, +40175:25078, +40176:25082, +40177:25076, +40178:25087, +40179:25085, +40180:25084, +40181:25086, +40182:25088, +40183:25096, +40184:25097, +40185:25101, +40186:25100, +40187:25108, +40188:25115, +40256:25118, +40257:25121, +40258:25130, +40259:25134, +40260:25136, +40261:25138, +40262:25139, +40263:25153, +40264:25166, +40265:25182, +40266:25187, +40267:25179, +40268:25184, +40269:25192, +40270:25212, +40271:25218, +40272:25225, +40273:25214, +40274:25234, +40275:25235, +40276:25238, +40277:25300, +40278:25219, +40279:25236, +40280:25303, +40281:25297, +40282:25275, +40283:25295, +40284:25343, +40285:25286, +40286:25812, +40287:25288, +40288:25308, +40289:25292, +40290:25290, +40291:25282, +40292:25287, +40293:25243, +40294:25289, +40295:25356, +40296:25326, +40297:25329, +40298:25383, +40299:25346, +40300:25352, +40301:25327, +40302:25333, +40303:25424, +40304:25406, +40305:25421, +40306:25628, +40307:25423, +40308:25494, +40309:25486, +40310:25472, +40311:25515, +40312:25462, +40313:25507, +40314:25487, +40315:25481, +40316:25503, +40317:25525, +40318:25451, +40320:25449, +40321:25534, +40322:25577, +40323:25536, +40324:25542, +40325:25571, +40326:25545, +40327:25554, +40328:25590, +40329:25540, +40330:25622, +40331:25652, +40332:25606, +40333:25619, +40334:25638, +40335:25654, +40336:25885, +40337:25623, +40338:25640, +40339:25615, +40340:25703, +40341:25711, +40342:25718, +40343:25678, +40344:25898, +40345:25749, +40346:25747, +40347:25765, +40348:25769, +40349:25736, +40350:25788, +40351:25818, +40352:25810, +40353:25797, +40354:25799, +40355:25787, +40356:25816, +40357:25794, +40358:25841, +40359:25831, +40360:33289, +40361:25824, +40362:25825, +40363:25260, +40364:25827, +40365:25839, +40366:25900, +40367:25846, +40368:25844, +40369:25842, +40370:25850, +40371:25856, +40372:25853, +40373:25880, +40374:25884, +40375:25861, +40376:25892, +40377:25891, +40378:25899, +40379:25908, +40380:25909, +40381:25911, +40382:25910, +40383:25912, +40384:30027, +40385:25928, +40386:25942, +40387:25941, +40388:25933, +40389:25944, +40390:25950, +40391:25949, +40392:25970, +40393:25976, +40394:25986, +40395:25987, +40396:35722, +40397:26011, +40398:26015, +40399:26027, +40400:26039, +40401:26051, +40402:26054, +40403:26049, +40404:26052, +40405:26060, +40406:26066, +40407:26075, +40408:26073, +40409:26080, +40410:26081, +40411:26097, +40412:26482, +40413:26122, +40414:26115, +40415:26107, +40416:26483, +40417:26165, +40418:26166, +40419:26164, +40420:26140, +40421:26191, +40422:26180, +40423:26185, +40424:26177, +40425:26206, +40426:26205, +40427:26212, +40428:26215, +40429:26216, +40430:26207, +40431:26210, +40432:26224, +40433:26243, +40434:26248, +40435:26254, +40436:26249, +40437:26244, +40438:26264, +40439:26269, +40440:26305, +40441:26297, +40442:26313, +40443:26302, +40444:26300, +40512:26308, +40513:26296, +40514:26326, +40515:26330, +40516:26336, +40517:26175, +40518:26342, +40519:26345, +40520:26352, +40521:26357, +40522:26359, +40523:26383, +40524:26390, +40525:26398, +40526:26406, +40527:26407, +40528:38712, +40529:26414, +40530:26431, +40531:26422, +40532:26433, +40533:26424, +40534:26423, +40535:26438, +40536:26462, +40537:26464, +40538:26457, +40539:26467, +40540:26468, +40541:26505, +40542:26480, +40543:26537, +40544:26492, +40545:26474, +40546:26508, +40547:26507, +40548:26534, +40549:26529, +40550:26501, +40551:26551, +40552:26607, +40553:26548, +40554:26604, +40555:26547, +40556:26601, +40557:26552, +40558:26596, +40559:26590, +40560:26589, +40561:26594, +40562:26606, +40563:26553, +40564:26574, +40565:26566, +40566:26599, +40567:27292, +40568:26654, +40569:26694, +40570:26665, +40571:26688, +40572:26701, +40573:26674, +40574:26702, +40576:26803, +40577:26667, +40578:26713, +40579:26723, +40580:26743, +40581:26751, +40582:26783, +40583:26767, +40584:26797, +40585:26772, +40586:26781, +40587:26779, +40588:26755, +40589:27310, +40590:26809, +40591:26740, +40592:26805, +40593:26784, +40594:26810, +40595:26895, +40596:26765, +40597:26750, +40598:26881, +40599:26826, +40600:26888, +40601:26840, +40602:26914, +40603:26918, +40604:26849, +40605:26892, +40606:26829, +40607:26836, +40608:26855, +40609:26837, +40610:26934, +40611:26898, +40612:26884, +40613:26839, +40614:26851, +40615:26917, +40616:26873, +40617:26848, +40618:26863, +40619:26920, +40620:26922, +40621:26906, +40622:26915, +40623:26913, +40624:26822, +40625:27001, +40626:26999, +40627:26972, +40628:27000, +40629:26987, +40630:26964, +40631:27006, +40632:26990, +40633:26937, +40634:26996, +40635:26941, +40636:26969, +40637:26928, +40638:26977, +40639:26974, +40640:26973, +40641:27009, +40642:26986, +40643:27058, +40644:27054, +40645:27088, +40646:27071, +40647:27073, +40648:27091, +40649:27070, +40650:27086, +40651:23528, +40652:27082, +40653:27101, +40654:27067, +40655:27075, +40656:27047, +40657:27182, +40658:27025, +40659:27040, +40660:27036, +40661:27029, +40662:27060, +40663:27102, +40664:27112, +40665:27138, +40666:27163, +40667:27135, +40668:27402, +40669:27129, +40670:27122, +40671:27111, +40672:27141, +40673:27057, +40674:27166, +40675:27117, +40676:27156, +40677:27115, +40678:27146, +40679:27154, +40680:27329, +40681:27171, +40682:27155, +40683:27204, +40684:27148, +40685:27250, +40686:27190, +40687:27256, +40688:27207, +40689:27234, +40690:27225, +40691:27238, +40692:27208, +40693:27192, +40694:27170, +40695:27280, +40696:27277, +40697:27296, +40698:27268, +40699:27298, +40700:27299, +40768:27287, +40769:34327, +40770:27323, +40771:27331, +40772:27330, +40773:27320, +40774:27315, +40775:27308, +40776:27358, +40777:27345, +40778:27359, +40779:27306, +40780:27354, +40781:27370, +40782:27387, +40783:27397, +40784:34326, +40785:27386, +40786:27410, +40787:27414, +40788:39729, +40789:27423, +40790:27448, +40791:27447, +40792:30428, +40793:27449, +40794:39150, +40795:27463, +40796:27459, +40797:27465, +40798:27472, +40799:27481, +40800:27476, +40801:27483, +40802:27487, +40803:27489, +40804:27512, +40805:27513, +40806:27519, +40807:27520, +40808:27524, +40809:27523, +40810:27533, +40811:27544, +40812:27541, +40813:27550, +40814:27556, +40815:27562, +40816:27563, +40817:27567, +40818:27570, +40819:27569, +40820:27571, +40821:27575, +40822:27580, +40823:27590, +40824:27595, +40825:27603, +40826:27615, +40827:27628, +40828:27627, +40829:27635, +40830:27631, +40832:40638, +40833:27656, +40834:27667, +40835:27668, +40836:27675, +40837:27684, +40838:27683, +40839:27742, +40840:27733, +40841:27746, +40842:27754, +40843:27778, +40844:27789, +40845:27802, +40846:27777, +40847:27803, +40848:27774, +40849:27752, +40850:27763, +40851:27794, +40852:27792, +40853:27844, +40854:27889, +40855:27859, +40856:27837, +40857:27863, +40858:27845, +40859:27869, +40860:27822, +40861:27825, +40862:27838, +40863:27834, +40864:27867, +40865:27887, +40866:27865, +40867:27882, +40868:27935, +40869:34893, +40870:27958, +40871:27947, +40872:27965, +40873:27960, +40874:27929, +40875:27957, +40876:27955, +40877:27922, +40878:27916, +40879:28003, +40880:28051, +40881:28004, +40882:27994, +40883:28025, +40884:27993, +40885:28046, +40886:28053, +40887:28644, +40888:28037, +40889:28153, +40890:28181, +40891:28170, +40892:28085, +40893:28103, +40894:28134, +40895:28088, +40896:28102, +40897:28140, +40898:28126, +40899:28108, +40900:28136, +40901:28114, +40902:28101, +40903:28154, +40904:28121, +40905:28132, +40906:28117, +40907:28138, +40908:28142, +40909:28205, +40910:28270, +40911:28206, +40912:28185, +40913:28274, +40914:28255, +40915:28222, +40916:28195, +40917:28267, +40918:28203, +40919:28278, +40920:28237, +40921:28191, +40922:28227, +40923:28218, +40924:28238, +40925:28196, +40926:28415, +40927:28189, +40928:28216, +40929:28290, +40930:28330, +40931:28312, +40932:28361, +40933:28343, +40934:28371, +40935:28349, +40936:28335, +40937:28356, +40938:28338, +40939:28372, +40940:28373, +40941:28303, +40942:28325, +40943:28354, +40944:28319, +40945:28481, +40946:28433, +40947:28748, +40948:28396, +40949:28408, +40950:28414, +40951:28479, +40952:28402, +40953:28465, +40954:28399, +40955:28466, +40956:28364, +57408:28478, +57409:28435, +57410:28407, +57411:28550, +57412:28538, +57413:28536, +57414:28545, +57415:28544, +57416:28527, +57417:28507, +57418:28659, +57419:28525, +57420:28546, +57421:28540, +57422:28504, +57423:28558, +57424:28561, +57425:28610, +57426:28518, +57427:28595, +57428:28579, +57429:28577, +57430:28580, +57431:28601, +57432:28614, +57433:28586, +57434:28639, +57435:28629, +57436:28652, +57437:28628, +57438:28632, +57439:28657, +57440:28654, +57441:28635, +57442:28681, +57443:28683, +57444:28666, +57445:28689, +57446:28673, +57447:28687, +57448:28670, +57449:28699, +57450:28698, +57451:28532, +57452:28701, +57453:28696, +57454:28703, +57455:28720, +57456:28734, +57457:28722, +57458:28753, +57459:28771, +57460:28825, +57461:28818, +57462:28847, +57463:28913, +57464:28844, +57465:28856, +57466:28851, +57467:28846, +57468:28895, +57469:28875, +57470:28893, +57472:28889, +57473:28937, +57474:28925, +57475:28956, +57476:28953, +57477:29029, +57478:29013, +57479:29064, +57480:29030, +57481:29026, +57482:29004, +57483:29014, +57484:29036, +57485:29071, +57486:29179, +57487:29060, +57488:29077, +57489:29096, +57490:29100, +57491:29143, +57492:29113, +57493:29118, +57494:29138, +57495:29129, +57496:29140, +57497:29134, +57498:29152, +57499:29164, +57500:29159, +57501:29173, +57502:29180, +57503:29177, +57504:29183, +57505:29197, +57506:29200, +57507:29211, +57508:29224, +57509:29229, +57510:29228, +57511:29232, +57512:29234, +57513:29243, +57514:29244, +57515:29247, +57516:29248, +57517:29254, +57518:29259, +57519:29272, +57520:29300, +57521:29310, +57522:29314, +57523:29313, +57524:29319, +57525:29330, +57526:29334, +57527:29346, +57528:29351, +57529:29369, +57530:29362, +57531:29379, +57532:29382, +57533:29380, +57534:29390, +57535:29394, +57536:29410, +57537:29408, +57538:29409, +57539:29433, +57540:29431, +57541:20495, +57542:29463, +57543:29450, +57544:29468, +57545:29462, +57546:29469, +57547:29492, +57548:29487, +57549:29481, +57550:29477, +57551:29502, +57552:29518, +57553:29519, +57554:40664, +57555:29527, +57556:29546, +57557:29544, +57558:29552, +57559:29560, +57560:29557, +57561:29563, +57562:29562, +57563:29640, +57564:29619, +57565:29646, +57566:29627, +57567:29632, +57568:29669, +57569:29678, +57570:29662, +57571:29858, +57572:29701, +57573:29807, +57574:29733, +57575:29688, +57576:29746, +57577:29754, +57578:29781, +57579:29759, +57580:29791, +57581:29785, +57582:29761, +57583:29788, +57584:29801, +57585:29808, +57586:29795, +57587:29802, +57588:29814, +57589:29822, +57590:29835, +57591:29854, +57592:29863, +57593:29898, +57594:29903, +57595:29908, +57596:29681, +57664:29920, +57665:29923, +57666:29927, +57667:29929, +57668:29934, +57669:29938, +57670:29936, +57671:29937, +57672:29944, +57673:29943, +57674:29956, +57675:29955, +57676:29957, +57677:29964, +57678:29966, +57679:29965, +57680:29973, +57681:29971, +57682:29982, +57683:29990, +57684:29996, +57685:30012, +57686:30020, +57687:30029, +57688:30026, +57689:30025, +57690:30043, +57691:30022, +57692:30042, +57693:30057, +57694:30052, +57695:30055, +57696:30059, +57697:30061, +57698:30072, +57699:30070, +57700:30086, +57701:30087, +57702:30068, +57703:30090, +57704:30089, +57705:30082, +57706:30100, +57707:30106, +57708:30109, +57709:30117, +57710:30115, +57711:30146, +57712:30131, +57713:30147, +57714:30133, +57715:30141, +57716:30136, +57717:30140, +57718:30129, +57719:30157, +57720:30154, +57721:30162, +57722:30169, +57723:30179, +57724:30174, +57725:30206, +57726:30207, +57728:30204, +57729:30209, +57730:30192, +57731:30202, +57732:30194, +57733:30195, +57734:30219, +57735:30221, +57736:30217, +57737:30239, +57738:30247, +57739:30240, +57740:30241, +57741:30242, +57742:30244, +57743:30260, +57744:30256, +57745:30267, +57746:30279, +57747:30280, +57748:30278, +57749:30300, +57750:30296, +57751:30305, +57752:30306, +57753:30312, +57754:30313, +57755:30314, +57756:30311, +57757:30316, +57758:30320, +57759:30322, +57760:30326, +57761:30328, +57762:30332, +57763:30336, +57764:30339, +57765:30344, +57766:30347, +57767:30350, +57768:30358, +57769:30355, +57770:30361, +57771:30362, +57772:30384, +57773:30388, +57774:30392, +57775:30393, +57776:30394, +57777:30402, +57778:30413, +57779:30422, +57780:30418, +57781:30430, +57782:30433, +57783:30437, +57784:30439, +57785:30442, +57786:34351, +57787:30459, +57788:30472, +57789:30471, +57790:30468, +57791:30505, +57792:30500, +57793:30494, +57794:30501, +57795:30502, +57796:30491, +57797:30519, +57798:30520, +57799:30535, +57800:30554, +57801:30568, +57802:30571, +57803:30555, +57804:30565, +57805:30591, +57806:30590, +57807:30585, +57808:30606, +57809:30603, +57810:30609, +57811:30624, +57812:30622, +57813:30640, +57814:30646, +57815:30649, +57816:30655, +57817:30652, +57818:30653, +57819:30651, +57820:30663, +57821:30669, +57822:30679, +57823:30682, +57824:30684, +57825:30691, +57826:30702, +57827:30716, +57828:30732, +57829:30738, +57830:31014, +57831:30752, +57832:31018, +57833:30789, +57834:30862, +57835:30836, +57836:30854, +57837:30844, +57838:30874, +57839:30860, +57840:30883, +57841:30901, +57842:30890, +57843:30895, +57844:30929, +57845:30918, +57846:30923, +57847:30932, +57848:30910, +57849:30908, +57850:30917, +57851:30922, +57852:30956, +57920:30951, +57921:30938, +57922:30973, +57923:30964, +57924:30983, +57925:30994, +57926:30993, +57927:31001, +57928:31020, +57929:31019, +57930:31040, +57931:31072, +57932:31063, +57933:31071, +57934:31066, +57935:31061, +57936:31059, +57937:31098, +57938:31103, +57939:31114, +57940:31133, +57941:31143, +57942:40779, +57943:31146, +57944:31150, +57945:31155, +57946:31161, +57947:31162, +57948:31177, +57949:31189, +57950:31207, +57951:31212, +57952:31201, +57953:31203, +57954:31240, +57955:31245, +57956:31256, +57957:31257, +57958:31264, +57959:31263, +57960:31104, +57961:31281, +57962:31291, +57963:31294, +57964:31287, +57965:31299, +57966:31319, +57967:31305, +57968:31329, +57969:31330, +57970:31337, +57971:40861, +57972:31344, +57973:31353, +57974:31357, +57975:31368, +57976:31383, +57977:31381, +57978:31384, +57979:31382, +57980:31401, +57981:31432, +57982:31408, +57984:31414, +57985:31429, +57986:31428, +57987:31423, +57988:36995, +57989:31431, +57990:31434, +57991:31437, +57992:31439, +57993:31445, +57994:31443, +57995:31449, +57996:31450, +57997:31453, +57998:31457, +57999:31458, +58000:31462, +58001:31469, +58002:31472, +58003:31490, +58004:31503, +58005:31498, +58006:31494, +58007:31539, +58008:31512, +58009:31513, +58010:31518, +58011:31541, +58012:31528, +58013:31542, +58014:31568, +58015:31610, +58016:31492, +58017:31565, +58018:31499, +58019:31564, +58020:31557, +58021:31605, +58022:31589, +58023:31604, +58024:31591, +58025:31600, +58026:31601, +58027:31596, +58028:31598, +58029:31645, +58030:31640, +58031:31647, +58032:31629, +58033:31644, +58034:31642, +58035:31627, +58036:31634, +58037:31631, +58038:31581, +58039:31641, +58040:31691, +58041:31681, +58042:31692, +58043:31695, +58044:31668, +58045:31686, +58046:31709, +58047:31721, +58048:31761, +58049:31764, +58050:31718, +58051:31717, +58052:31840, +58053:31744, +58054:31751, +58055:31763, +58056:31731, +58057:31735, +58058:31767, +58059:31757, +58060:31734, +58061:31779, +58062:31783, +58063:31786, +58064:31775, +58065:31799, +58066:31787, +58067:31805, +58068:31820, +58069:31811, +58070:31828, +58071:31823, +58072:31808, +58073:31824, +58074:31832, +58075:31839, +58076:31844, +58077:31830, +58078:31845, +58079:31852, +58080:31861, +58081:31875, +58082:31888, +58083:31908, +58084:31917, +58085:31906, +58086:31915, +58087:31905, +58088:31912, +58089:31923, +58090:31922, +58091:31921, +58092:31918, +58093:31929, +58094:31933, +58095:31936, +58096:31941, +58097:31938, +58098:31960, +58099:31954, +58100:31964, +58101:31970, +58102:39739, +58103:31983, +58104:31986, +58105:31988, +58106:31990, +58107:31994, +58108:32006, +58176:32002, +58177:32028, +58178:32021, +58179:32010, +58180:32069, +58181:32075, +58182:32046, +58183:32050, +58184:32063, +58185:32053, +58186:32070, +58187:32115, +58188:32086, +58189:32078, +58190:32114, +58191:32104, +58192:32110, +58193:32079, +58194:32099, +58195:32147, +58196:32137, +58197:32091, +58198:32143, +58199:32125, +58200:32155, +58201:32186, +58202:32174, +58203:32163, +58204:32181, +58205:32199, +58206:32189, +58207:32171, +58208:32317, +58209:32162, +58210:32175, +58211:32220, +58212:32184, +58213:32159, +58214:32176, +58215:32216, +58216:32221, +58217:32228, +58218:32222, +58219:32251, +58220:32242, +58221:32225, +58222:32261, +58223:32266, +58224:32291, +58225:32289, +58226:32274, +58227:32305, +58228:32287, +58229:32265, +58230:32267, +58231:32290, +58232:32326, +58233:32358, +58234:32315, +58235:32309, +58236:32313, +58237:32323, +58238:32311, +58240:32306, +58241:32314, +58242:32359, +58243:32349, +58244:32342, +58245:32350, +58246:32345, +58247:32346, +58248:32377, +58249:32362, +58250:32361, +58251:32380, +58252:32379, +58253:32387, +58254:32213, +58255:32381, +58256:36782, +58257:32383, +58258:32392, +58259:32393, +58260:32396, +58261:32402, +58262:32400, +58263:32403, +58264:32404, +58265:32406, +58266:32398, +58267:32411, +58268:32412, +58269:32568, +58270:32570, +58271:32581, +58272:32588, +58273:32589, +58274:32590, +58275:32592, +58276:32593, +58277:32597, +58278:32596, +58279:32600, +58280:32607, +58281:32608, +58282:32616, +58283:32617, +58284:32615, +58285:32632, +58286:32642, +58287:32646, +58288:32643, +58289:32648, +58290:32647, +58291:32652, +58292:32660, +58293:32670, +58294:32669, +58295:32666, +58296:32675, +58297:32687, +58298:32690, +58299:32697, +58300:32686, +58301:32694, +58302:32696, +58303:35697, +58304:32709, +58305:32710, +58306:32714, +58307:32725, +58308:32724, +58309:32737, +58310:32742, +58311:32745, +58312:32755, +58313:32761, +58314:39132, +58315:32774, +58316:32772, +58317:32779, +58318:32786, +58319:32792, +58320:32793, +58321:32796, +58322:32801, +58323:32808, +58324:32831, +58325:32827, +58326:32842, +58327:32838, +58328:32850, +58329:32856, +58330:32858, +58331:32863, +58332:32866, +58333:32872, +58334:32883, +58335:32882, +58336:32880, +58337:32886, +58338:32889, +58339:32893, +58340:32895, +58341:32900, +58342:32902, +58343:32901, +58344:32923, +58345:32915, +58346:32922, +58347:32941, +58348:20880, +58349:32940, +58350:32987, +58351:32997, +58352:32985, +58353:32989, +58354:32964, +58355:32986, +58356:32982, +58357:33033, +58358:33007, +58359:33009, +58360:33051, +58361:33065, +58362:33059, +58363:33071, +58364:33099, +58432:38539, +58433:33094, +58434:33086, +58435:33107, +58436:33105, +58437:33020, +58438:33137, +58439:33134, +58440:33125, +58441:33126, +58442:33140, +58443:33155, +58444:33160, +58445:33162, +58446:33152, +58447:33154, +58448:33184, +58449:33173, +58450:33188, +58451:33187, +58452:33119, +58453:33171, +58454:33193, +58455:33200, +58456:33205, +58457:33214, +58458:33208, +58459:33213, +58460:33216, +58461:33218, +58462:33210, +58463:33225, +58464:33229, +58465:33233, +58466:33241, +58467:33240, +58468:33224, +58469:33242, +58470:33247, +58471:33248, +58472:33255, +58473:33274, +58474:33275, +58475:33278, +58476:33281, +58477:33282, +58478:33285, +58479:33287, +58480:33290, +58481:33293, +58482:33296, +58483:33302, +58484:33321, +58485:33323, +58486:33336, +58487:33331, +58488:33344, +58489:33369, +58490:33368, +58491:33373, +58492:33370, +58493:33375, +58494:33380, +58496:33378, +58497:33384, +58498:33386, +58499:33387, +58500:33326, +58501:33393, +58502:33399, +58503:33400, +58504:33406, +58505:33421, +58506:33426, +58507:33451, +58508:33439, +58509:33467, +58510:33452, +58511:33505, +58512:33507, +58513:33503, +58514:33490, +58515:33524, +58516:33523, +58517:33530, +58518:33683, +58519:33539, +58520:33531, +58521:33529, +58522:33502, +58523:33542, +58524:33500, +58525:33545, +58526:33497, +58527:33589, +58528:33588, +58529:33558, +58530:33586, +58531:33585, +58532:33600, +58533:33593, +58534:33616, +58535:33605, +58536:33583, +58537:33579, +58538:33559, +58539:33560, +58540:33669, +58541:33690, +58542:33706, +58543:33695, +58544:33698, +58545:33686, +58546:33571, +58547:33678, +58548:33671, +58549:33674, +58550:33660, +58551:33717, +58552:33651, +58553:33653, +58554:33696, +58555:33673, +58556:33704, +58557:33780, +58558:33811, +58559:33771, +58560:33742, +58561:33789, +58562:33795, +58563:33752, +58564:33803, +58565:33729, +58566:33783, +58567:33799, +58568:33760, +58569:33778, +58570:33805, +58571:33826, +58572:33824, +58573:33725, +58574:33848, +58575:34054, +58576:33787, +58577:33901, +58578:33834, +58579:33852, +58580:34138, +58581:33924, +58582:33911, +58583:33899, +58584:33965, +58585:33902, +58586:33922, +58587:33897, +58588:33862, +58589:33836, +58590:33903, +58591:33913, +58592:33845, +58593:33994, +58594:33890, +58595:33977, +58596:33983, +58597:33951, +58598:34009, +58599:33997, +58600:33979, +58601:34010, +58602:34000, +58603:33985, +58604:33990, +58605:34006, +58606:33953, +58607:34081, +58608:34047, +58609:34036, +58610:34071, +58611:34072, +58612:34092, +58613:34079, +58614:34069, +58615:34068, +58616:34044, +58617:34112, +58618:34147, +58619:34136, +58620:34120, +58688:34113, +58689:34306, +58690:34123, +58691:34133, +58692:34176, +58693:34212, +58694:34184, +58695:34193, +58696:34186, +58697:34216, +58698:34157, +58699:34196, +58700:34203, +58701:34282, +58702:34183, +58703:34204, +58704:34167, +58705:34174, +58706:34192, +58707:34249, +58708:34234, +58709:34255, +58710:34233, +58711:34256, +58712:34261, +58713:34269, +58714:34277, +58715:34268, +58716:34297, +58717:34314, +58718:34323, +58719:34315, +58720:34302, +58721:34298, +58722:34310, +58723:34338, +58724:34330, +58725:34352, +58726:34367, +58727:34381, +58728:20053, +58729:34388, +58730:34399, +58731:34407, +58732:34417, +58733:34451, +58734:34467, +58735:34473, +58736:34474, +58737:34443, +58738:34444, +58739:34486, +58740:34479, +58741:34500, +58742:34502, +58743:34480, +58744:34505, +58745:34851, +58746:34475, +58747:34516, +58748:34526, +58749:34537, +58750:34540, +58752:34527, +58753:34523, +58754:34543, +58755:34578, +58756:34566, +58757:34568, +58758:34560, +58759:34563, +58760:34555, +58761:34577, +58762:34569, +58763:34573, +58764:34553, +58765:34570, +58766:34612, +58767:34623, +58768:34615, +58769:34619, +58770:34597, +58771:34601, +58772:34586, +58773:34656, +58774:34655, +58775:34680, +58776:34636, +58777:34638, +58778:34676, +58779:34647, +58780:34664, +58781:34670, +58782:34649, +58783:34643, +58784:34659, +58785:34666, +58786:34821, +58787:34722, +58788:34719, +58789:34690, +58790:34735, +58791:34763, +58792:34749, +58793:34752, +58794:34768, +58795:38614, +58796:34731, +58797:34756, +58798:34739, +58799:34759, +58800:34758, +58801:34747, +58802:34799, +58803:34802, +58804:34784, +58805:34831, +58806:34829, +58807:34814, +58808:34806, +58809:34807, +58810:34830, +58811:34770, +58812:34833, +58813:34838, +58814:34837, +58815:34850, +58816:34849, +58817:34865, +58818:34870, +58819:34873, +58820:34855, +58821:34875, +58822:34884, +58823:34882, +58824:34898, +58825:34905, +58826:34910, +58827:34914, +58828:34923, +58829:34945, +58830:34942, +58831:34974, +58832:34933, +58833:34941, +58834:34997, +58835:34930, +58836:34946, +58837:34967, +58838:34962, +58839:34990, +58840:34969, +58841:34978, +58842:34957, +58843:34980, +58844:34992, +58845:35007, +58846:34993, +58847:35011, +58848:35012, +58849:35028, +58850:35032, +58851:35033, +58852:35037, +58853:35065, +58854:35074, +58855:35068, +58856:35060, +58857:35048, +58858:35058, +58859:35076, +58860:35084, +58861:35082, +58862:35091, +58863:35139, +58864:35102, +58865:35109, +58866:35114, +58867:35115, +58868:35137, +58869:35140, +58870:35131, +58871:35126, +58872:35128, +58873:35148, +58874:35101, +58875:35168, +58876:35166, +58944:35174, +58945:35172, +58946:35181, +58947:35178, +58948:35183, +58949:35188, +58950:35191, +58951:35198, +58952:35203, +58953:35208, +58954:35210, +58955:35219, +58956:35224, +58957:35233, +58958:35241, +58959:35238, +58960:35244, +58961:35247, +58962:35250, +58963:35258, +58964:35261, +58965:35263, +58966:35264, +58967:35290, +58968:35292, +58969:35293, +58970:35303, +58971:35316, +58972:35320, +58973:35331, +58974:35350, +58975:35344, +58976:35340, +58977:35355, +58978:35357, +58979:35365, +58980:35382, +58981:35393, +58982:35419, +58983:35410, +58984:35398, +58985:35400, +58986:35452, +58987:35437, +58988:35436, +58989:35426, +58990:35461, +58991:35458, +58992:35460, +58993:35496, +58994:35489, +58995:35473, +58996:35493, +58997:35494, +58998:35482, +58999:35491, +59000:35524, +59001:35533, +59002:35522, +59003:35546, +59004:35563, +59005:35571, +59006:35559, +59008:35556, +59009:35569, +59010:35604, +59011:35552, +59012:35554, +59013:35575, +59014:35550, +59015:35547, +59016:35596, +59017:35591, +59018:35610, +59019:35553, +59020:35606, +59021:35600, +59022:35607, +59023:35616, +59024:35635, +59025:38827, +59026:35622, +59027:35627, +59028:35646, +59029:35624, +59030:35649, +59031:35660, +59032:35663, +59033:35662, +59034:35657, +59035:35670, +59036:35675, +59037:35674, +59038:35691, +59039:35679, +59040:35692, +59041:35695, +59042:35700, +59043:35709, +59044:35712, +59045:35724, +59046:35726, +59047:35730, +59048:35731, +59049:35734, +59050:35737, +59051:35738, +59052:35898, +59053:35905, +59054:35903, +59055:35912, +59056:35916, +59057:35918, +59058:35920, +59059:35925, +59060:35938, +59061:35948, +59062:35960, +59063:35962, +59064:35970, +59065:35977, +59066:35973, +59067:35978, +59068:35981, +59069:35982, +59070:35988, +59071:35964, +59072:35992, +59073:25117, +59074:36013, +59075:36010, +59076:36029, +59077:36018, +59078:36019, +59079:36014, +59080:36022, +59081:36040, +59082:36033, +59083:36068, +59084:36067, +59085:36058, +59086:36093, +59087:36090, +59088:36091, +59089:36100, +59090:36101, +59091:36106, +59092:36103, +59093:36111, +59094:36109, +59095:36112, +59096:40782, +59097:36115, +59098:36045, +59099:36116, +59100:36118, +59101:36199, +59102:36205, +59103:36209, +59104:36211, +59105:36225, +59106:36249, +59107:36290, +59108:36286, +59109:36282, +59110:36303, +59111:36314, +59112:36310, +59113:36300, +59114:36315, +59115:36299, +59116:36330, +59117:36331, +59118:36319, +59119:36323, +59120:36348, +59121:36360, +59122:36361, +59123:36351, +59124:36381, +59125:36382, +59126:36368, +59127:36383, +59128:36418, +59129:36405, +59130:36400, +59131:36404, +59132:36426, +59200:36423, +59201:36425, +59202:36428, +59203:36432, +59204:36424, +59205:36441, +59206:36452, +59207:36448, +59208:36394, +59209:36451, +59210:36437, +59211:36470, +59212:36466, +59213:36476, +59214:36481, +59215:36487, +59216:36485, +59217:36484, +59218:36491, +59219:36490, +59220:36499, +59221:36497, +59222:36500, +59223:36505, +59224:36522, +59225:36513, +59226:36524, +59227:36528, +59228:36550, +59229:36529, +59230:36542, +59231:36549, +59232:36552, +59233:36555, +59234:36571, +59235:36579, +59236:36604, +59237:36603, +59238:36587, +59239:36606, +59240:36618, +59241:36613, +59242:36629, +59243:36626, +59244:36633, +59245:36627, +59246:36636, +59247:36639, +59248:36635, +59249:36620, +59250:36646, +59251:36659, +59252:36667, +59253:36665, +59254:36677, +59255:36674, +59256:36670, +59257:36684, +59258:36681, +59259:36678, +59260:36686, +59261:36695, +59262:36700, +59264:36706, +59265:36707, +59266:36708, +59267:36764, +59268:36767, +59269:36771, +59270:36781, +59271:36783, +59272:36791, +59273:36826, +59274:36837, +59275:36834, +59276:36842, +59277:36847, +59278:36999, +59279:36852, +59280:36869, +59281:36857, +59282:36858, +59283:36881, +59284:36885, +59285:36897, +59286:36877, +59287:36894, +59288:36886, +59289:36875, +59290:36903, +59291:36918, +59292:36917, +59293:36921, +59294:36856, +59295:36943, +59296:36944, +59297:36945, +59298:36946, +59299:36878, +59300:36937, +59301:36926, +59302:36950, +59303:36952, +59304:36958, +59305:36968, +59306:36975, +59307:36982, +59308:38568, +59309:36978, +59310:36994, +59311:36989, +59312:36993, +59313:36992, +59314:37002, +59315:37001, +59316:37007, +59317:37032, +59318:37039, +59319:37041, +59320:37045, +59321:37090, +59322:37092, +59323:25160, +59324:37083, +59325:37122, +59326:37138, +59327:37145, +59328:37170, +59329:37168, +59330:37194, +59331:37206, +59332:37208, +59333:37219, +59334:37221, +59335:37225, +59336:37235, +59337:37234, +59338:37259, +59339:37257, +59340:37250, +59341:37282, +59342:37291, +59343:37295, +59344:37290, +59345:37301, +59346:37300, +59347:37306, +59348:37312, +59349:37313, +59350:37321, +59351:37323, +59352:37328, +59353:37334, +59354:37343, +59355:37345, +59356:37339, +59357:37372, +59358:37365, +59359:37366, +59360:37406, +59361:37375, +59362:37396, +59363:37420, +59364:37397, +59365:37393, +59366:37470, +59367:37463, +59368:37445, +59369:37449, +59370:37476, +59371:37448, +59372:37525, +59373:37439, +59374:37451, +59375:37456, +59376:37532, +59377:37526, +59378:37523, +59379:37531, +59380:37466, +59381:37583, +59382:37561, +59383:37559, +59384:37609, +59385:37647, +59386:37626, +59387:37700, +59388:37678, +59456:37657, +59457:37666, +59458:37658, +59459:37667, +59460:37690, +59461:37685, +59462:37691, +59463:37724, +59464:37728, +59465:37756, +59466:37742, +59467:37718, +59468:37808, +59469:37804, +59470:37805, +59471:37780, +59472:37817, +59473:37846, +59474:37847, +59475:37864, +59476:37861, +59477:37848, +59478:37827, +59479:37853, +59480:37840, +59481:37832, +59482:37860, +59483:37914, +59484:37908, +59485:37907, +59486:37891, +59487:37895, +59488:37904, +59489:37942, +59490:37931, +59491:37941, +59492:37921, +59493:37946, +59494:37953, +59495:37970, +59496:37956, +59497:37979, +59498:37984, +59499:37986, +59500:37982, +59501:37994, +59502:37417, +59503:38000, +59504:38005, +59505:38007, +59506:38013, +59507:37978, +59508:38012, +59509:38014, +59510:38017, +59511:38015, +59512:38274, +59513:38279, +59514:38282, +59515:38292, +59516:38294, +59517:38296, +59518:38297, +59520:38304, +59521:38312, +59522:38311, +59523:38317, +59524:38332, +59525:38331, +59526:38329, +59527:38334, +59528:38346, +59529:28662, +59530:38339, +59531:38349, +59532:38348, +59533:38357, +59534:38356, +59535:38358, +59536:38364, +59537:38369, +59538:38373, +59539:38370, +59540:38433, +59541:38440, +59542:38446, +59543:38447, +59544:38466, +59545:38476, +59546:38479, +59547:38475, +59548:38519, +59549:38492, +59550:38494, +59551:38493, +59552:38495, +59553:38502, +59554:38514, +59555:38508, +59556:38541, +59557:38552, +59558:38549, +59559:38551, +59560:38570, +59561:38567, +59562:38577, +59563:38578, +59564:38576, +59565:38580, +59566:38582, +59567:38584, +59568:38585, +59569:38606, +59570:38603, +59571:38601, +59572:38605, +59573:35149, +59574:38620, +59575:38669, +59576:38613, +59577:38649, +59578:38660, +59579:38662, +59580:38664, +59581:38675, +59582:38670, +59583:38673, +59584:38671, +59585:38678, +59586:38681, +59587:38692, +59588:38698, +59589:38704, +59590:38713, +59591:38717, +59592:38718, +59593:38724, +59594:38726, +59595:38728, +59596:38722, +59597:38729, +59598:38748, +59599:38752, +59600:38756, +59601:38758, +59602:38760, +59603:21202, +59604:38763, +59605:38769, +59606:38777, +59607:38789, +59608:38780, +59609:38785, +59610:38778, +59611:38790, +59612:38795, +59613:38799, +59614:38800, +59615:38812, +59616:38824, +59617:38822, +59618:38819, +59619:38835, +59620:38836, +59621:38851, +59622:38854, +59623:38856, +59624:38859, +59625:38876, +59626:38893, +59627:40783, +59628:38898, +59629:31455, +59630:38902, +59631:38901, +59632:38927, +59633:38924, +59634:38968, +59635:38948, +59636:38945, +59637:38967, +59638:38973, +59639:38982, +59640:38991, +59641:38987, +59642:39019, +59643:39023, +59644:39024, +59712:39025, +59713:39028, +59714:39027, +59715:39082, +59716:39087, +59717:39089, +59718:39094, +59719:39108, +59720:39107, +59721:39110, +59722:39145, +59723:39147, +59724:39171, +59725:39177, +59726:39186, +59727:39188, +59728:39192, +59729:39201, +59730:39197, +59731:39198, +59732:39204, +59733:39200, +59734:39212, +59735:39214, +59736:39229, +59737:39230, +59738:39234, +59739:39241, +59740:39237, +59741:39248, +59742:39243, +59743:39249, +59744:39250, +59745:39244, +59746:39253, +59747:39319, +59748:39320, +59749:39333, +59750:39341, +59751:39342, +59752:39356, +59753:39391, +59754:39387, +59755:39389, +59756:39384, +59757:39377, +59758:39405, +59759:39406, +59760:39409, +59761:39410, +59762:39419, +59763:39416, +59764:39425, +59765:39439, +59766:39429, +59767:39394, +59768:39449, +59769:39467, +59770:39479, +59771:39493, +59772:39490, +59773:39488, +59774:39491, +59776:39486, +59777:39509, +59778:39501, +59779:39515, +59780:39511, +59781:39519, +59782:39522, +59783:39525, +59784:39524, +59785:39529, +59786:39531, +59787:39530, +59788:39597, +59789:39600, +59790:39612, +59791:39616, +59792:39631, +59793:39633, +59794:39635, +59795:39636, +59796:39646, +59797:39647, +59798:39650, +59799:39651, +59800:39654, +59801:39663, +59802:39659, +59803:39662, +59804:39668, +59805:39665, +59806:39671, +59807:39675, +59808:39686, +59809:39704, +59810:39706, +59811:39711, +59812:39714, +59813:39715, +59814:39717, +59815:39719, +59816:39720, +59817:39721, +59818:39722, +59819:39726, +59820:39727, +59821:39730, +59822:39748, +59823:39747, +59824:39759, +59825:39757, +59826:39758, +59827:39761, +59828:39768, +59829:39796, +59830:39827, +59831:39811, +59832:39825, +59833:39830, +59834:39831, +59835:39839, +59836:39840, +59837:39848, +59838:39860, +59839:39872, +59840:39882, +59841:39865, +59842:39878, +59843:39887, +59844:39889, +59845:39890, +59846:39907, +59847:39906, +59848:39908, +59849:39892, +59850:39905, +59851:39994, +59852:39922, +59853:39921, +59854:39920, +59855:39957, +59856:39956, +59857:39945, +59858:39955, +59859:39948, +59860:39942, +59861:39944, +59862:39954, +59863:39946, +59864:39940, +59865:39982, +59866:39963, +59867:39973, +59868:39972, +59869:39969, +59870:39984, +59871:40007, +59872:39986, +59873:40006, +59874:39998, +59875:40026, +59876:40032, +59877:40039, +59878:40054, +59879:40056, +59880:40167, +59881:40172, +59882:40176, +59883:40201, +59884:40200, +59885:40171, +59886:40195, +59887:40198, +59888:40234, +59889:40230, +59890:40367, +59891:40227, +59892:40223, +59893:40260, +59894:40213, +59895:40210, +59896:40257, +59897:40255, +59898:40254, +59899:40262, +59900:40264, +59968:40285, +59969:40286, +59970:40292, +59971:40273, +59972:40272, +59973:40281, +59974:40306, +59975:40329, +59976:40327, +59977:40363, +59978:40303, +59979:40314, +59980:40346, +59981:40356, +59982:40361, +59983:40370, +59984:40388, +59985:40385, +59986:40379, +59987:40376, +59988:40378, +59989:40390, +59990:40399, +59991:40386, +59992:40409, +59993:40403, +59994:40440, +59995:40422, +59996:40429, +59997:40431, +59998:40445, +59999:40474, +60000:40475, +60001:40478, +60002:40565, +60003:40569, +60004:40573, +60005:40577, +60006:40584, +60007:40587, +60008:40588, +60009:40594, +60010:40597, +60011:40593, +60012:40605, +60013:40613, +60014:40617, +60015:40632, +60016:40618, +60017:40621, +60018:38753, +60019:40652, +60020:40654, +60021:40655, +60022:40656, +60023:40660, +60024:40668, +60025:40670, +60026:40669, +60027:40672, +60028:40677, +60029:40680, +60030:40687, +60032:40692, +60033:40694, +60034:40695, +60035:40697, +60036:40699, +60037:40700, +60038:40701, +60039:40711, +60040:40712, +60041:30391, +60042:40725, +60043:40737, +60044:40748, +60045:40766, +60046:40778, +60047:40786, +60048:40788, +60049:40803, +60050:40799, +60051:40800, +60052:40801, +60053:40806, +60054:40807, +60055:40812, +60056:40810, +60057:40823, +60058:40818, +60059:40822, +60060:40853, +60061:40860, +60062:40864, +60063:22575, +60064:27079, +60065:36953, +60066:29796, +60067:20956, +60068:29081, +60736:32394, +60737:35100, +60738:37704, +60739:37512, +60740:34012, +60741:20425, +60742:28859, +60743:26161, +60744:26824, +60745:37625, +60746:26363, +60747:24389, +60748:20008, +60749:20193, +60750:20220, +60751:20224, +60752:20227, +60753:20281, +60754:20310, +60755:20370, +60756:20362, +60757:20378, +60758:20372, +60759:20429, +60760:20544, +60761:20514, +60762:20479, +60763:20510, +60764:20550, +60765:20592, +60766:20546, +60767:20628, +60768:20724, +60769:20696, +60770:20810, +60771:20836, +60772:20893, +60773:20926, +60774:20972, +60775:21013, +60776:21148, +60777:21158, +60778:21184, +60779:21211, +60780:21248, +60781:21255, +60782:21284, +60783:21362, +60784:21395, +60785:21426, +60786:21469, +60787:64014, +60788:21660, +60789:21642, +60790:21673, +60791:21759, +60792:21894, +60793:22361, +60794:22373, +60795:22444, +60796:22472, +60797:22471, +60798:64015, +60800:64016, +60801:22686, +60802:22706, +60803:22795, +60804:22867, +60805:22875, +60806:22877, +60807:22883, +60808:22948, +60809:22970, +60810:23382, +60811:23488, +60812:29999, +60813:23512, +60814:23532, +60815:23582, +60816:23718, +60817:23738, +60818:23797, +60819:23847, +60820:23891, +60821:64017, +60822:23874, +60823:23917, +60824:23992, +60825:23993, +60826:24016, +60827:24353, +60828:24372, +60829:24423, +60830:24503, +60831:24542, +60832:24669, +60833:24709, +60834:24714, +60835:24798, +60836:24789, +60837:24864, +60838:24818, +60839:24849, +60840:24887, +60841:24880, +60842:24984, +60843:25107, +60844:25254, +60845:25589, +60846:25696, +60847:25757, +60848:25806, +60849:25934, +60850:26112, +60851:26133, +60852:26171, +60853:26121, +60854:26158, +60855:26142, +60856:26148, +60857:26213, +60858:26199, +60859:26201, +60860:64018, +60861:26227, +60862:26265, +60863:26272, +60864:26290, +60865:26303, +60866:26362, +60867:26382, +60868:63785, +60869:26470, +60870:26555, +60871:26706, +60872:26560, +60873:26625, +60874:26692, +60875:26831, +60876:64019, +60877:26984, +60878:64020, +60879:27032, +60880:27106, +60881:27184, +60882:27243, +60883:27206, +60884:27251, +60885:27262, +60886:27362, +60887:27364, +60888:27606, +60889:27711, +60890:27740, +60891:27782, +60892:27759, +60893:27866, +60894:27908, +60895:28039, +60896:28015, +60897:28054, +60898:28076, +60899:28111, +60900:28152, +60901:28146, +60902:28156, +60903:28217, +60904:28252, +60905:28199, +60906:28220, +60907:28351, +60908:28552, +60909:28597, +60910:28661, +60911:28677, +60912:28679, +60913:28712, +60914:28805, +60915:28843, +60916:28943, +60917:28932, +60918:29020, +60919:28998, +60920:28999, +60921:64021, +60922:29121, +60923:29182, +60924:29361, +60992:29374, +60993:29476, +60994:64022, +60995:29559, +60996:29629, +60997:29641, +60998:29654, +60999:29667, +61000:29650, +61001:29703, +61002:29685, +61003:29734, +61004:29738, +61005:29737, +61006:29742, +61007:29794, +61008:29833, +61009:29855, +61010:29953, +61011:30063, +61012:30338, +61013:30364, +61014:30366, +61015:30363, +61016:30374, +61017:64023, +61018:30534, +61019:21167, +61020:30753, +61021:30798, +61022:30820, +61023:30842, +61024:31024, +61025:64024, +61026:64025, +61027:64026, +61028:31124, +61029:64027, +61030:31131, +61031:31441, +61032:31463, +61033:64028, +61034:31467, +61035:31646, +61036:64029, +61037:32072, +61038:32092, +61039:32183, +61040:32160, +61041:32214, +61042:32338, +61043:32583, +61044:32673, +61045:64030, +61046:33537, +61047:33634, +61048:33663, +61049:33735, +61050:33782, +61051:33864, +61052:33972, +61053:34131, +61054:34137, +61056:34155, +61057:64031, +61058:34224, +61059:64032, +61060:64033, +61061:34823, +61062:35061, +61063:35346, +61064:35383, +61065:35449, +61066:35495, +61067:35518, +61068:35551, +61069:64034, +61070:35574, +61071:35667, +61072:35711, +61073:36080, +61074:36084, +61075:36114, +61076:36214, +61077:64035, +61078:36559, +61079:64036, +61080:64037, +61081:36967, +61082:37086, +61083:64038, +61084:37141, +61085:37159, +61086:37338, +61087:37335, +61088:37342, +61089:37357, +61090:37358, +61091:37348, +61092:37349, +61093:37382, +61094:37392, +61095:37386, +61096:37434, +61097:37440, +61098:37436, +61099:37454, +61100:37465, +61101:37457, +61102:37433, +61103:37479, +61104:37543, +61105:37495, +61106:37496, +61107:37607, +61108:37591, +61109:37593, +61110:37584, +61111:64039, +61112:37589, +61113:37600, +61114:37587, +61115:37669, +61116:37665, +61117:37627, +61118:64040, +61119:37662, +61120:37631, +61121:37661, +61122:37634, +61123:37744, +61124:37719, +61125:37796, +61126:37830, +61127:37854, +61128:37880, +61129:37937, +61130:37957, +61131:37960, +61132:38290, +61133:63964, +61134:64041, +61135:38557, +61136:38575, +61137:38707, +61138:38715, +61139:38723, +61140:38733, +61141:38735, +61142:38737, +61143:38741, +61144:38999, +61145:39013, +61146:64042, +61147:64043, +61148:39207, +61149:64044, +61150:39326, +61151:39502, +61152:39641, +61153:39644, +61154:39797, +61155:39794, +61156:39823, +61157:39857, +61158:39867, +61159:39936, +61160:40304, +61161:40299, +61162:64045, +61163:40473, +61164:40657, +61167:8560, +61168:8561, +61169:8562, +61170:8563, +61171:8564, +61172:8565, +61173:8566, +61174:8567, +61175:8568, +61176:8569, +61177:65506, +61178:65508, +61179:65287, +61180:65282, +61504:57344, +61505:57345, +61506:57346, +61507:57347, +61508:57348, +61509:57349, +61510:57350, +61511:57351, +61512:57352, +61513:57353, +61514:57354, +61515:57355, +61516:57356, +61517:57357, +61518:57358, +61519:57359, +61520:57360, +61521:57361, +61522:57362, +61523:57363, +61524:57364, +61525:57365, +61526:57366, +61527:57367, +61528:57368, +61529:57369, +61530:57370, +61531:57371, +61532:57372, +61533:57373, +61534:57374, +61535:57375, +61536:57376, +61537:57377, +61538:57378, +61539:57379, +61540:57380, +61541:57381, +61542:57382, +61543:57383, +61544:57384, +61545:57385, +61546:57386, +61547:57387, +61548:57388, +61549:57389, +61550:57390, +61551:57391, +61552:57392, +61553:57393, +61554:57394, +61555:57395, +61556:57396, +61557:57397, +61558:57398, +61559:57399, +61560:57400, +61561:57401, +61562:57402, +61563:57403, +61564:57404, +61565:57405, +61566:57406, +61568:57407, +61569:57408, +61570:57409, +61571:57410, +61572:57411, +61573:57412, +61574:57413, +61575:57414, +61576:57415, +61577:57416, +61578:57417, +61579:57418, +61580:57419, +61581:57420, +61582:57421, +61583:57422, +61584:57423, +61585:57424, +61586:57425, +61587:57426, +61588:57427, +61589:57428, +61590:57429, +61591:57430, +61592:57431, +61593:57432, +61594:57433, +61595:57434, +61596:57435, +61597:57436, +61598:57437, +61599:57438, +61600:57439, +61601:57440, +61602:57441, +61603:57442, +61604:57443, +61605:57444, +61606:57445, +61607:57446, +61608:57447, +61609:57448, +61610:57449, +61611:57450, +61612:57451, +61613:57452, +61614:57453, +61615:57454, +61616:57455, +61617:57456, +61618:57457, +61619:57458, +61620:57459, +61621:57460, +61622:57461, +61623:57462, +61624:57463, +61625:57464, +61626:57465, +61627:57466, +61628:57467, +61629:57468, +61630:57469, +61631:57470, +61632:57471, +61633:57472, +61634:57473, +61635:57474, +61636:57475, +61637:57476, +61638:57477, +61639:57478, +61640:57479, +61641:57480, +61642:57481, +61643:57482, +61644:57483, +61645:57484, +61646:57485, +61647:57486, +61648:57487, +61649:57488, +61650:57489, +61651:57490, +61652:57491, +61653:57492, +61654:57493, +61655:57494, +61656:57495, +61657:57496, +61658:57497, +61659:57498, +61660:57499, +61661:57500, +61662:57501, +61663:57502, +61664:57503, +61665:57504, +61666:57505, +61667:57506, +61668:57507, +61669:57508, +61670:57509, +61671:57510, +61672:57511, +61673:57512, +61674:57513, +61675:57514, +61676:57515, +61677:57516, +61678:57517, +61679:57518, +61680:57519, +61681:57520, +61682:57521, +61683:57522, +61684:57523, +61685:57524, +61686:57525, +61687:57526, +61688:57527, +61689:57528, +61690:57529, +61691:57530, +61692:57531, +61760:57532, +61761:57533, +61762:57534, +61763:57535, +61764:57536, +61765:57537, +61766:57538, +61767:57539, +61768:57540, +61769:57541, +61770:57542, +61771:57543, +61772:57544, +61773:57545, +61774:57546, +61775:57547, +61776:57548, +61777:57549, +61778:57550, +61779:57551, +61780:57552, +61781:57553, +61782:57554, +61783:57555, +61784:57556, +61785:57557, +61786:57558, +61787:57559, +61788:57560, +61789:57561, +61790:57562, +61791:57563, +61792:57564, +61793:57565, +61794:57566, +61795:57567, +61796:57568, +61797:57569, +61798:57570, +61799:57571, +61800:57572, +61801:57573, +61802:57574, +61803:57575, +61804:57576, +61805:57577, +61806:57578, +61807:57579, +61808:57580, +61809:57581, +61810:57582, +61811:57583, +61812:57584, +61813:57585, +61814:57586, +61815:57587, +61816:57588, +61817:57589, +61818:57590, +61819:57591, +61820:57592, +61821:57593, +61822:57594, +61824:57595, +61825:57596, +61826:57597, +61827:57598, +61828:57599, +61829:57600, +61830:57601, +61831:57602, +61832:57603, +61833:57604, +61834:57605, +61835:57606, +61836:57607, +61837:57608, +61838:57609, +61839:57610, +61840:57611, +61841:57612, +61842:57613, +61843:57614, +61844:57615, +61845:57616, +61846:57617, +61847:57618, +61848:57619, +61849:57620, +61850:57621, +61851:57622, +61852:57623, +61853:57624, +61854:57625, +61855:57626, +61856:57627, +61857:57628, +61858:57629, +61859:57630, +61860:57631, +61861:57632, +61862:57633, +61863:57634, +61864:57635, +61865:57636, +61866:57637, +61867:57638, +61868:57639, +61869:57640, +61870:57641, +61871:57642, +61872:57643, +61873:57644, +61874:57645, +61875:57646, +61876:57647, +61877:57648, +61878:57649, +61879:57650, +61880:57651, +61881:57652, +61882:57653, +61883:57654, +61884:57655, +61885:57656, +61886:57657, +61887:57658, +61888:57659, +61889:57660, +61890:57661, +61891:57662, +61892:57663, +61893:57664, +61894:57665, +61895:57666, +61896:57667, +61897:57668, +61898:57669, +61899:57670, +61900:57671, +61901:57672, +61902:57673, +61903:57674, +61904:57675, +61905:57676, +61906:57677, +61907:57678, +61908:57679, +61909:57680, +61910:57681, +61911:57682, +61912:57683, +61913:57684, +61914:57685, +61915:57686, +61916:57687, +61917:57688, +61918:57689, +61919:57690, +61920:57691, +61921:57692, +61922:57693, +61923:57694, +61924:57695, +61925:57696, +61926:57697, +61927:57698, +61928:57699, +61929:57700, +61930:57701, +61931:57702, +61932:57703, +61933:57704, +61934:57705, +61935:57706, +61936:57707, +61937:57708, +61938:57709, +61939:57710, +61940:57711, +61941:57712, +61942:57713, +61943:57714, +61944:57715, +61945:57716, +61946:57717, +61947:57718, +61948:57719, +62016:57720, +62017:57721, +62018:57722, +62019:57723, +62020:57724, +62021:57725, +62022:57726, +62023:57727, +62024:57728, +62025:57729, +62026:57730, +62027:57731, +62028:57732, +62029:57733, +62030:57734, +62031:57735, +62032:57736, +62033:57737, +62034:57738, +62035:57739, +62036:57740, +62037:57741, +62038:57742, +62039:57743, +62040:57744, +62041:57745, +62042:57746, +62043:57747, +62044:57748, +62045:57749, +62046:57750, +62047:57751, +62048:57752, +62049:57753, +62050:57754, +62051:57755, +62052:57756, +62053:57757, +62054:57758, +62055:57759, +62056:57760, +62057:57761, +62058:57762, +62059:57763, +62060:57764, +62061:57765, +62062:57766, +62063:57767, +62064:57768, +62065:57769, +62066:57770, +62067:57771, +62068:57772, +62069:57773, +62070:57774, +62071:57775, +62072:57776, +62073:57777, +62074:57778, +62075:57779, +62076:57780, +62077:57781, +62078:57782, +62080:57783, +62081:57784, +62082:57785, +62083:57786, +62084:57787, +62085:57788, +62086:57789, +62087:57790, +62088:57791, +62089:57792, +62090:57793, +62091:57794, +62092:57795, +62093:57796, +62094:57797, +62095:57798, +62096:57799, +62097:57800, +62098:57801, +62099:57802, +62100:57803, +62101:57804, +62102:57805, +62103:57806, +62104:57807, +62105:57808, +62106:57809, +62107:57810, +62108:57811, +62109:57812, +62110:57813, +62111:57814, +62112:57815, +62113:57816, +62114:57817, +62115:57818, +62116:57819, +62117:57820, +62118:57821, +62119:57822, +62120:57823, +62121:57824, +62122:57825, +62123:57826, +62124:57827, +62125:57828, +62126:57829, +62127:57830, +62128:57831, +62129:57832, +62130:57833, +62131:57834, +62132:57835, +62133:57836, +62134:57837, +62135:57838, +62136:57839, +62137:57840, +62138:57841, +62139:57842, +62140:57843, +62141:57844, +62142:57845, +62143:57846, +62144:57847, +62145:57848, +62146:57849, +62147:57850, +62148:57851, +62149:57852, +62150:57853, +62151:57854, +62152:57855, +62153:57856, +62154:57857, +62155:57858, +62156:57859, +62157:57860, +62158:57861, +62159:57862, +62160:57863, +62161:57864, +62162:57865, +62163:57866, +62164:57867, +62165:57868, +62166:57869, +62167:57870, +62168:57871, +62169:57872, +62170:57873, +62171:57874, +62172:57875, +62173:57876, +62174:57877, +62175:57878, +62176:57879, +62177:57880, +62178:57881, +62179:57882, +62180:57883, +62181:57884, +62182:57885, +62183:57886, +62184:57887, +62185:57888, +62186:57889, +62187:57890, +62188:57891, +62189:57892, +62190:57893, +62191:57894, +62192:57895, +62193:57896, +62194:57897, +62195:57898, +62196:57899, +62197:57900, +62198:57901, +62199:57902, +62200:57903, +62201:57904, +62202:57905, +62203:57906, +62204:57907, +62272:57908, +62273:57909, +62274:57910, +62275:57911, +62276:57912, +62277:57913, +62278:57914, +62279:57915, +62280:57916, +62281:57917, +62282:57918, +62283:57919, +62284:57920, +62285:57921, +62286:57922, +62287:57923, +62288:57924, +62289:57925, +62290:57926, +62291:57927, +62292:57928, +62293:57929, +62294:57930, +62295:57931, +62296:57932, +62297:57933, +62298:57934, +62299:57935, +62300:57936, +62301:57937, +62302:57938, +62303:57939, +62304:57940, +62305:57941, +62306:57942, +62307:57943, +62308:57944, +62309:57945, +62310:57946, +62311:57947, +62312:57948, +62313:57949, +62314:57950, +62315:57951, +62316:57952, +62317:57953, +62318:57954, +62319:57955, +62320:57956, +62321:57957, +62322:57958, +62323:57959, +62324:57960, +62325:57961, +62326:57962, +62327:57963, +62328:57964, +62329:57965, +62330:57966, +62331:57967, +62332:57968, +62333:57969, +62334:57970, +62336:57971, +62337:57972, +62338:57973, +62339:57974, +62340:57975, +62341:57976, +62342:57977, +62343:57978, +62344:57979, +62345:57980, +62346:57981, +62347:57982, +62348:57983, +62349:57984, +62350:57985, +62351:57986, +62352:57987, +62353:57988, +62354:57989, +62355:57990, +62356:57991, +62357:57992, +62358:57993, +62359:57994, +62360:57995, +62361:57996, +62362:57997, +62363:57998, +62364:57999, +62365:58000, +62366:58001, +62367:58002, +62368:58003, +62369:58004, +62370:58005, +62371:58006, +62372:58007, +62373:58008, +62374:58009, +62375:58010, +62376:58011, +62377:58012, +62378:58013, +62379:58014, +62380:58015, +62381:58016, +62382:58017, +62383:58018, +62384:58019, +62385:58020, +62386:58021, +62387:58022, +62388:58023, +62389:58024, +62390:58025, +62391:58026, +62392:58027, +62393:58028, +62394:58029, +62395:58030, +62396:58031, +62397:58032, +62398:58033, +62399:58034, +62400:58035, +62401:58036, +62402:58037, +62403:58038, +62404:58039, +62405:58040, +62406:58041, +62407:58042, +62408:58043, +62409:58044, +62410:58045, +62411:58046, +62412:58047, +62413:58048, +62414:58049, +62415:58050, +62416:58051, +62417:58052, +62418:58053, +62419:58054, +62420:58055, +62421:58056, +62422:58057, +62423:58058, +62424:58059, +62425:58060, +62426:58061, +62427:58062, +62428:58063, +62429:58064, +62430:58065, +62431:58066, +62432:58067, +62433:58068, +62434:58069, +62435:58070, +62436:58071, +62437:58072, +62438:58073, +62439:58074, +62440:58075, +62441:58076, +62442:58077, +62443:58078, +62444:58079, +62445:58080, +62446:58081, +62447:58082, +62448:58083, +62449:58084, +62450:58085, +62451:58086, +62452:58087, +62453:58088, +62454:58089, +62455:58090, +62456:58091, +62457:58092, +62458:58093, +62459:58094, +62460:58095, +62528:58096, +62529:58097, +62530:58098, +62531:58099, +62532:58100, +62533:58101, +62534:58102, +62535:58103, +62536:58104, +62537:58105, +62538:58106, +62539:58107, +62540:58108, +62541:58109, +62542:58110, +62543:58111, +62544:58112, +62545:58113, +62546:58114, +62547:58115, +62548:58116, +62549:58117, +62550:58118, +62551:58119, +62552:58120, +62553:58121, +62554:58122, +62555:58123, +62556:58124, +62557:58125, +62558:58126, +62559:58127, +62560:58128, +62561:58129, +62562:58130, +62563:58131, +62564:58132, +62565:58133, +62566:58134, +62567:58135, +62568:58136, +62569:58137, +62570:58138, +62571:58139, +62572:58140, +62573:58141, +62574:58142, +62575:58143, +62576:58144, +62577:58145, +62578:58146, +62579:58147, +62580:58148, +62581:58149, +62582:58150, +62583:58151, +62584:58152, +62585:58153, +62586:58154, +62587:58155, +62588:58156, +62589:58157, +62590:58158, +62592:58159, +62593:58160, +62594:58161, +62595:58162, +62596:58163, +62597:58164, +62598:58165, +62599:58166, +62600:58167, +62601:58168, +62602:58169, +62603:58170, +62604:58171, +62605:58172, +62606:58173, +62607:58174, +62608:58175, +62609:58176, +62610:58177, +62611:58178, +62612:58179, +62613:58180, +62614:58181, +62615:58182, +62616:58183, +62617:58184, +62618:58185, +62619:58186, +62620:58187, +62621:58188, +62622:58189, +62623:58190, +62624:58191, +62625:58192, +62626:58193, +62627:58194, +62628:58195, +62629:58196, +62630:58197, +62631:58198, +62632:58199, +62633:58200, +62634:58201, +62635:58202, +62636:58203, +62637:58204, +62638:58205, +62639:58206, +62640:58207, +62641:58208, +62642:58209, +62643:58210, +62644:58211, +62645:58212, +62646:58213, +62647:58214, +62648:58215, +62649:58216, +62650:58217, +62651:58218, +62652:58219, +62653:58220, +62654:58221, +62655:58222, +62656:58223, +62657:58224, +62658:58225, +62659:58226, +62660:58227, +62661:58228, +62662:58229, +62663:58230, +62664:58231, +62665:58232, +62666:58233, +62667:58234, +62668:58235, +62669:58236, +62670:58237, +62671:58238, +62672:58239, +62673:58240, +62674:58241, +62675:58242, +62676:58243, +62677:58244, +62678:58245, +62679:58246, +62680:58247, +62681:58248, +62682:58249, +62683:58250, +62684:58251, +62685:58252, +62686:58253, +62687:58254, +62688:58255, +62689:58256, +62690:58257, +62691:58258, +62692:58259, +62693:58260, +62694:58261, +62695:58262, +62696:58263, +62697:58264, +62698:58265, +62699:58266, +62700:58267, +62701:58268, +62702:58269, +62703:58270, +62704:58271, +62705:58272, +62706:58273, +62707:58274, +62708:58275, +62709:58276, +62710:58277, +62711:58278, +62712:58279, +62713:58280, +62714:58281, +62715:58282, +62716:58283, +62784:58284, +62785:58285, +62786:58286, +62787:58287, +62788:58288, +62789:58289, +62790:58290, +62791:58291, +62792:58292, +62793:58293, +62794:58294, +62795:58295, +62796:58296, +62797:58297, +62798:58298, +62799:58299, +62800:58300, +62801:58301, +62802:58302, +62803:58303, +62804:58304, +62805:58305, +62806:58306, +62807:58307, +62808:58308, +62809:58309, +62810:58310, +62811:58311, +62812:58312, +62813:58313, +62814:58314, +62815:58315, +62816:58316, +62817:58317, +62818:58318, +62819:58319, +62820:58320, +62821:58321, +62822:58322, +62823:58323, +62824:58324, +62825:58325, +62826:58326, +62827:58327, +62828:58328, +62829:58329, +62830:58330, +62831:58331, +62832:58332, +62833:58333, +62834:58334, +62835:58335, +62836:58336, +62837:58337, +62838:58338, +62839:58339, +62840:58340, +62841:58341, +62842:58342, +62843:58343, +62844:58344, +62845:58345, +62846:58346, +62848:58347, +62849:58348, +62850:58349, +62851:58350, +62852:58351, +62853:58352, +62854:58353, +62855:58354, +62856:58355, +62857:58356, +62858:58357, +62859:58358, +62860:58359, +62861:58360, +62862:58361, +62863:58362, +62864:58363, +62865:58364, +62866:58365, +62867:58366, +62868:58367, +62869:58368, +62870:58369, +62871:58370, +62872:58371, +62873:58372, +62874:58373, +62875:58374, +62876:58375, +62877:58376, +62878:58377, +62879:58378, +62880:58379, +62881:58380, +62882:58381, +62883:58382, +62884:58383, +62885:58384, +62886:58385, +62887:58386, +62888:58387, +62889:58388, +62890:58389, +62891:58390, +62892:58391, +62893:58392, +62894:58393, +62895:58394, +62896:58395, +62897:58396, +62898:58397, +62899:58398, +62900:58399, +62901:58400, +62902:58401, +62903:58402, +62904:58403, +62905:58404, +62906:58405, +62907:58406, +62908:58407, +62909:58408, +62910:58409, +62911:58410, +62912:58411, +62913:58412, +62914:58413, +62915:58414, +62916:58415, +62917:58416, +62918:58417, +62919:58418, +62920:58419, +62921:58420, +62922:58421, +62923:58422, +62924:58423, +62925:58424, +62926:58425, +62927:58426, +62928:58427, +62929:58428, +62930:58429, +62931:58430, +62932:58431, +62933:58432, +62934:58433, +62935:58434, +62936:58435, +62937:58436, +62938:58437, +62939:58438, +62940:58439, +62941:58440, +62942:58441, +62943:58442, +62944:58443, +62945:58444, +62946:58445, +62947:58446, +62948:58447, +62949:58448, +62950:58449, +62951:58450, +62952:58451, +62953:58452, +62954:58453, +62955:58454, +62956:58455, +62957:58456, +62958:58457, +62959:58458, +62960:58459, +62961:58460, +62962:58461, +62963:58462, +62964:58463, +62965:58464, +62966:58465, +62967:58466, +62968:58467, +62969:58468, +62970:58469, +62971:58470, +62972:58471, +63040:58472, +63041:58473, +63042:58474, +63043:58475, +63044:58476, +63045:58477, +63046:58478, +63047:58479, +63048:58480, +63049:58481, +63050:58482, +63051:58483, +63052:58484, +63053:58485, +63054:58486, +63055:58487, +63056:58488, +63057:58489, +63058:58490, +63059:58491, +63060:58492, +63061:58493, +63062:58494, +63063:58495, +63064:58496, +63065:58497, +63066:58498, +63067:58499, +63068:58500, +63069:58501, +63070:58502, +63071:58503, +63072:58504, +63073:58505, +63074:58506, +63075:58507, +63076:58508, +63077:58509, +63078:58510, +63079:58511, +63080:58512, +63081:58513, +63082:58514, +63083:58515, +63084:58516, +63085:58517, +63086:58518, +63087:58519, +63088:58520, +63089:58521, +63090:58522, +63091:58523, +63092:58524, +63093:58525, +63094:58526, +63095:58527, +63096:58528, +63097:58529, +63098:58530, +63099:58531, +63100:58532, +63101:58533, +63102:58534, +63104:58535, +63105:58536, +63106:58537, +63107:58538, +63108:58539, +63109:58540, +63110:58541, +63111:58542, +63112:58543, +63113:58544, +63114:58545, +63115:58546, +63116:58547, +63117:58548, +63118:58549, +63119:58550, +63120:58551, +63121:58552, +63122:58553, +63123:58554, +63124:58555, +63125:58556, +63126:58557, +63127:58558, +63128:58559, +63129:58560, +63130:58561, +63131:58562, +63132:58563, +63133:58564, +63134:58565, +63135:58566, +63136:58567, +63137:58568, +63138:58569, +63139:58570, +63140:58571, +63141:58572, +63142:58573, +63143:58574, +63144:58575, +63145:58576, +63146:58577, +63147:58578, +63148:58579, +63149:58580, +63150:58581, +63151:58582, +63152:58583, +63153:58584, +63154:58585, +63155:58586, +63156:58587, +63157:58588, +63158:58589, +63159:58590, +63160:58591, +63161:58592, +63162:58593, +63163:58594, +63164:58595, +63165:58596, +63166:58597, +63167:58598, +63168:58599, +63169:58600, +63170:58601, +63171:58602, +63172:58603, +63173:58604, +63174:58605, +63175:58606, +63176:58607, +63177:58608, +63178:58609, +63179:58610, +63180:58611, +63181:58612, +63182:58613, +63183:58614, +63184:58615, +63185:58616, +63186:58617, +63187:58618, +63188:58619, +63189:58620, +63190:58621, +63191:58622, +63192:58623, +63193:58624, +63194:58625, +63195:58626, +63196:58627, +63197:58628, +63198:58629, +63199:58630, +63200:58631, +63201:58632, +63202:58633, +63203:58634, +63204:58635, +63205:58636, +63206:58637, +63207:58638, +63208:58639, +63209:58640, +63210:58641, +63211:58642, +63212:58643, +63213:58644, +63214:58645, +63215:58646, +63216:58647, +63217:58648, +63218:58649, +63219:58650, +63220:58651, +63221:58652, +63222:58653, +63223:58654, +63224:58655, +63225:58656, +63226:58657, +63227:58658, +63228:58659, +63296:58660, +63297:58661, +63298:58662, +63299:58663, +63300:58664, +63301:58665, +63302:58666, +63303:58667, +63304:58668, +63305:58669, +63306:58670, +63307:58671, +63308:58672, +63309:58673, +63310:58674, +63311:58675, +63312:58676, +63313:58677, +63314:58678, +63315:58679, +63316:58680, +63317:58681, +63318:58682, +63319:58683, +63320:58684, +63321:58685, +63322:58686, +63323:58687, +63324:58688, +63325:58689, +63326:58690, +63327:58691, +63328:58692, +63329:58693, +63330:58694, +63331:58695, +63332:58696, +63333:58697, +63334:58698, +63335:58699, +63336:58700, +63337:58701, +63338:58702, +63339:58703, +63340:58704, +63341:58705, +63342:58706, +63343:58707, +63344:58708, +63345:58709, +63346:58710, +63347:58711, +63348:58712, +63349:58713, +63350:58714, +63351:58715, +63352:58716, +63353:58717, +63354:58718, +63355:58719, +63356:58720, +63357:58721, +63358:58722, +63360:58723, +63361:58724, +63362:58725, +63363:58726, +63364:58727, +63365:58728, +63366:58729, +63367:58730, +63368:58731, +63369:58732, +63370:58733, +63371:58734, +63372:58735, +63373:58736, +63374:58737, +63375:58738, +63376:58739, +63377:58740, +63378:58741, +63379:58742, +63380:58743, +63381:58744, +63382:58745, +63383:58746, +63384:58747, +63385:58748, +63386:58749, +63387:58750, +63388:58751, +63389:58752, +63390:58753, +63391:58754, +63392:58755, +63393:58756, +63394:58757, +63395:58758, +63396:58759, +63397:58760, +63398:58761, +63399:58762, +63400:58763, +63401:58764, +63402:58765, +63403:58766, +63404:58767, +63405:58768, +63406:58769, +63407:58770, +63408:58771, +63409:58772, +63410:58773, +63411:58774, +63412:58775, +63413:58776, +63414:58777, +63415:58778, +63416:58779, +63417:58780, +63418:58781, +63419:58782, +63420:58783, +63421:58784, +63422:58785, +63423:58786, +63424:58787, +63425:58788, +63426:58789, +63427:58790, +63428:58791, +63429:58792, +63430:58793, +63431:58794, +63432:58795, +63433:58796, +63434:58797, +63435:58798, +63436:58799, +63437:58800, +63438:58801, +63439:58802, +63440:58803, +63441:58804, +63442:58805, +63443:58806, +63444:58807, +63445:58808, +63446:58809, +63447:58810, +63448:58811, +63449:58812, +63450:58813, +63451:58814, +63452:58815, +63453:58816, +63454:58817, +63455:58818, +63456:58819, +63457:58820, +63458:58821, +63459:58822, +63460:58823, +63461:58824, +63462:58825, +63463:58826, +63464:58827, +63465:58828, +63466:58829, +63467:58830, +63468:58831, +63469:58832, +63470:58833, +63471:58834, +63472:58835, +63473:58836, +63474:58837, +63475:58838, +63476:58839, +63477:58840, +63478:58841, +63479:58842, +63480:58843, +63481:58844, +63482:58845, +63483:58846, +63484:58847, +63552:58848, +63553:58849, +63554:58850, +63555:58851, +63556:58852, +63557:58853, +63558:58854, +63559:58855, +63560:58856, +63561:58857, +63562:58858, +63563:58859, +63564:58860, +63565:58861, +63566:58862, +63567:58863, +63568:58864, +63569:58865, +63570:58866, +63571:58867, +63572:58868, +63573:58869, +63574:58870, +63575:58871, +63576:58872, +63577:58873, +63578:58874, +63579:58875, +63580:58876, +63581:58877, +63582:58878, +63583:58879, +63584:58880, +63585:58881, +63586:58882, +63587:58883, +63588:58884, +63589:58885, +63590:58886, +63591:58887, +63592:58888, +63593:58889, +63594:58890, +63595:58891, +63596:58892, +63597:58893, +63598:58894, +63599:58895, +63600:58896, +63601:58897, +63602:58898, +63603:58899, +63604:58900, +63605:58901, +63606:58902, +63607:58903, +63608:58904, +63609:58905, +63610:58906, +63611:58907, +63612:58908, +63613:58909, +63614:58910, +63616:58911, +63617:58912, +63618:58913, +63619:58914, +63620:58915, +63621:58916, +63622:58917, +63623:58918, +63624:58919, +63625:58920, +63626:58921, +63627:58922, +63628:58923, +63629:58924, +63630:58925, +63631:58926, +63632:58927, +63633:58928, +63634:58929, +63635:58930, +63636:58931, +63637:58932, +63638:58933, +63639:58934, +63640:58935, +63641:58936, +63642:58937, +63643:58938, +63644:58939, +63645:58940, +63646:58941, +63647:58942, +63648:58943, +63649:58944, +63650:58945, +63651:58946, +63652:58947, +63653:58948, +63654:58949, +63655:58950, +63656:58951, +63657:58952, +63658:58953, +63659:58954, +63660:58955, +63661:58956, +63662:58957, +63663:58958, +63664:58959, +63665:58960, +63666:58961, +63667:58962, +63668:58963, +63669:58964, +63670:58965, +63671:58966, +63672:58967, +63673:58968, +63674:58969, +63675:58970, +63676:58971, +63677:58972, +63678:58973, +63679:58974, +63680:58975, +63681:58976, +63682:58977, +63683:58978, +63684:58979, +63685:58980, +63686:58981, +63687:58982, +63688:58983, +63689:58984, +63690:58985, +63691:58986, +63692:58987, +63693:58988, +63694:58989, +63695:58990, +63696:58991, +63697:58992, +63698:58993, +63699:58994, +63700:58995, +63701:58996, +63702:58997, +63703:58998, +63704:58999, +63705:59000, +63706:59001, +63707:59002, +63708:59003, +63709:59004, +63710:59005, +63711:59006, +63712:59007, +63713:59008, +63714:59009, +63715:59010, +63716:59011, +63717:59012, +63718:59013, +63719:59014, +63720:59015, +63721:59016, +63722:59017, +63723:59018, +63724:59019, +63725:59020, +63726:59021, +63727:59022, +63728:59023, +63729:59024, +63730:59025, +63731:59026, +63732:59027, +63733:59028, +63734:59029, +63735:59030, +63736:59031, +63737:59032, +63738:59033, +63739:59034, +63740:59035, +64064:8560, +64065:8561, +64066:8562, +64067:8563, +64068:8564, +64069:8565, +64070:8566, +64071:8567, +64072:8568, +64073:8569, +64074:8544, +64075:8545, +64076:8546, +64077:8547, +64078:8548, +64079:8549, +64080:8550, +64081:8551, +64082:8552, +64083:8553, +64084:65506, +64085:65508, +64086:65287, +64087:65282, +64088:12849, +64089:8470, +64090:8481, +64091:8757, +64092:32394, +64093:35100, +64094:37704, +64095:37512, +64096:34012, +64097:20425, +64098:28859, +64099:26161, +64100:26824, +64101:37625, +64102:26363, +64103:24389, +64104:20008, +64105:20193, +64106:20220, +64107:20224, +64108:20227, +64109:20281, +64110:20310, +64111:20370, +64112:20362, +64113:20378, +64114:20372, +64115:20429, +64116:20544, +64117:20514, +64118:20479, +64119:20510, +64120:20550, +64121:20592, +64122:20546, +64123:20628, +64124:20724, +64125:20696, +64126:20810, +64128:20836, +64129:20893, +64130:20926, +64131:20972, +64132:21013, +64133:21148, +64134:21158, +64135:21184, +64136:21211, +64137:21248, +64138:21255, +64139:21284, +64140:21362, +64141:21395, +64142:21426, +64143:21469, +64144:64014, +64145:21660, +64146:21642, +64147:21673, +64148:21759, +64149:21894, +64150:22361, +64151:22373, +64152:22444, +64153:22472, +64154:22471, +64155:64015, +64156:64016, +64157:22686, +64158:22706, +64159:22795, +64160:22867, +64161:22875, +64162:22877, +64163:22883, +64164:22948, +64165:22970, +64166:23382, +64167:23488, +64168:29999, +64169:23512, +64170:23532, +64171:23582, +64172:23718, +64173:23738, +64174:23797, +64175:23847, +64176:23891, +64177:64017, +64178:23874, +64179:23917, +64180:23992, +64181:23993, +64182:24016, +64183:24353, +64184:24372, +64185:24423, +64186:24503, +64187:24542, +64188:24669, +64189:24709, +64190:24714, +64191:24798, +64192:24789, +64193:24864, +64194:24818, +64195:24849, +64196:24887, +64197:24880, +64198:24984, +64199:25107, +64200:25254, +64201:25589, +64202:25696, +64203:25757, +64204:25806, +64205:25934, +64206:26112, +64207:26133, +64208:26171, +64209:26121, +64210:26158, +64211:26142, +64212:26148, +64213:26213, +64214:26199, +64215:26201, +64216:64018, +64217:26227, +64218:26265, +64219:26272, +64220:26290, +64221:26303, +64222:26362, +64223:26382, +64224:63785, +64225:26470, +64226:26555, +64227:26706, +64228:26560, +64229:26625, +64230:26692, +64231:26831, +64232:64019, +64233:26984, +64234:64020, +64235:27032, +64236:27106, +64237:27184, +64238:27243, +64239:27206, +64240:27251, +64241:27262, +64242:27362, +64243:27364, +64244:27606, +64245:27711, +64246:27740, +64247:27782, +64248:27759, +64249:27866, +64250:27908, +64251:28039, +64252:28015, +64320:28054, +64321:28076, +64322:28111, +64323:28152, +64324:28146, +64325:28156, +64326:28217, +64327:28252, +64328:28199, +64329:28220, +64330:28351, +64331:28552, +64332:28597, +64333:28661, +64334:28677, +64335:28679, +64336:28712, +64337:28805, +64338:28843, +64339:28943, +64340:28932, +64341:29020, +64342:28998, +64343:28999, +64344:64021, +64345:29121, +64346:29182, +64347:29361, +64348:29374, +64349:29476, +64350:64022, +64351:29559, +64352:29629, +64353:29641, +64354:29654, +64355:29667, +64356:29650, +64357:29703, +64358:29685, +64359:29734, +64360:29738, +64361:29737, +64362:29742, +64363:29794, +64364:29833, +64365:29855, +64366:29953, +64367:30063, +64368:30338, +64369:30364, +64370:30366, +64371:30363, +64372:30374, +64373:64023, +64374:30534, +64375:21167, +64376:30753, +64377:30798, +64378:30820, +64379:30842, +64380:31024, +64381:64024, +64382:64025, +64384:64026, +64385:31124, +64386:64027, +64387:31131, +64388:31441, +64389:31463, +64390:64028, +64391:31467, +64392:31646, +64393:64029, +64394:32072, +64395:32092, +64396:32183, +64397:32160, +64398:32214, +64399:32338, +64400:32583, +64401:32673, +64402:64030, +64403:33537, +64404:33634, +64405:33663, +64406:33735, +64407:33782, +64408:33864, +64409:33972, +64410:34131, +64411:34137, +64412:34155, +64413:64031, +64414:34224, +64415:64032, +64416:64033, +64417:34823, +64418:35061, +64419:35346, +64420:35383, +64421:35449, +64422:35495, +64423:35518, +64424:35551, +64425:64034, +64426:35574, +64427:35667, +64428:35711, +64429:36080, +64430:36084, +64431:36114, +64432:36214, +64433:64035, +64434:36559, +64435:64036, +64436:64037, +64437:36967, +64438:37086, +64439:64038, +64440:37141, +64441:37159, +64442:37338, +64443:37335, +64444:37342, +64445:37357, +64446:37358, +64447:37348, +64448:37349, +64449:37382, +64450:37392, +64451:37386, +64452:37434, +64453:37440, +64454:37436, +64455:37454, +64456:37465, +64457:37457, +64458:37433, +64459:37479, +64460:37543, +64461:37495, +64462:37496, +64463:37607, +64464:37591, +64465:37593, +64466:37584, +64467:64039, +64468:37589, +64469:37600, +64470:37587, +64471:37669, +64472:37665, +64473:37627, +64474:64040, +64475:37662, +64476:37631, +64477:37661, +64478:37634, +64479:37744, +64480:37719, +64481:37796, +64482:37830, +64483:37854, +64484:37880, +64485:37937, +64486:37957, +64487:37960, +64488:38290, +64489:63964, +64490:64041, +64491:38557, +64492:38575, +64493:38707, +64494:38715, +64495:38723, +64496:38733, +64497:38735, +64498:38737, +64499:38741, +64500:38999, +64501:39013, +64502:64042, +64503:64043, +64504:39207, +64505:64044, +64506:39326, +64507:39502, +64508:39641, +64576:39644, +64577:39797, +64578:39794, +64579:39823, +64580:39857, +64581:39867, +64582:39936, +64583:40304, +64584:40299, +64585:64045, +64586:40473, +64587:40657 +}; + +/** + * @author takahiro / https://github.com/takahirox + */ + +function DataViewEx ( buffer, littleEndian ) { + + this.dv = new DataView( buffer ); + this.offset = 0; + this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true; + this.encoder = new CharsetEncoder(); + +} + +DataViewEx.prototype = { + + constructor: DataViewEx, + + getInt8: function () { + + var value = this.dv.getInt8( this.offset ); + this.offset += 1; + return value; + + }, + + getInt8Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getInt8() ); + + } + + return a; + + }, + + getUint8: function () { + + var value = this.dv.getUint8( this.offset ); + this.offset += 1; + return value; + + }, + + getUint8Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getUint8() ); + + } + + return a; + + }, + + + getInt16: function () { + + var value = this.dv.getInt16( this.offset, this.littleEndian ); + this.offset += 2; + return value; + + }, + + getInt16Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getInt16() ); + + } + + return a; + + }, + + getUint16: function () { + + var value = this.dv.getUint16( this.offset, this.littleEndian ); + this.offset += 2; + return value; + + }, + + getUint16Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getUint16() ); + + } + + return a; + + }, + + getInt32: function () { + + var value = this.dv.getInt32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + }, + + getInt32Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getInt32() ); + + } + + return a; + + }, + + getUint32: function () { + + var value = this.dv.getUint32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + }, + + getUint32Array: function ( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getUint32() ); + + } + + return a; + + }, + + getFloat32: function () { + + var value = this.dv.getFloat32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + }, + + getFloat32Array: function( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getFloat32() ); + + } + + return a; + + }, + + getFloat64: function () { + + var value = this.dv.getFloat64( this.offset, this.littleEndian ); + this.offset += 8; + return value; + + }, + + getFloat64Array: function( size ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getFloat64() ); + + } + + return a; + + }, + + getIndex: function ( type, isUnsigned ) { + + switch ( type ) { + + case 1: + return ( isUnsigned === true ) ? this.getUint8() : this.getInt8(); + + case 2: + return ( isUnsigned === true ) ? this.getUint16() : this.getInt16(); + + case 4: + return this.getInt32(); // No Uint32 + + default: + throw 'unknown number type ' + type + ' exception.'; + + } + + }, + + getIndexArray: function ( type, size, isUnsigned ) { + + var a = []; + + for ( var i = 0; i < size; i++ ) { + + a.push( this.getIndex( type, isUnsigned ) ); + + } + + return a; + + }, + + getChars: function ( size ) { + + var str = ''; + + while ( size > 0 ) { + + var value = this.getUint8(); + size--; + + if ( value === 0 ) { + + break; + + } + + str += String.fromCharCode( value ); + + } + + while ( size > 0 ) { + + this.getUint8(); + size--; + + } + + return str; + + }, + + getSjisStringsAsUnicode: function ( size ) { + + var a = []; + + while ( size > 0 ) { + + var value = this.getUint8(); + size--; + + if ( value === 0 ) { + + break; + + } + + a.push( value ); + + } + + while ( size > 0 ) { + + this.getUint8(); + size--; + + } + + return this.encoder.s2u( new Uint8Array( a ) ); + + }, + + getUnicodeStrings: function ( size ) { + + var str = ''; + + while ( size > 0 ) { + + var value = this.getUint16(); + size -= 2; + + if ( value === 0 ) { + + break; + + } + + str += String.fromCharCode( value ); + + } + + while ( size > 0 ) { + + this.getUint8(); + size--; + + } + + return str; + + }, + + getTextBuffer: function () { + + var size = this.getUint32(); + return this.getUnicodeStrings( size ); + + } + +}; + +/** + * @author takahiro / https://github.com/takahirox + */ + +function DataCreationHelper () { +} + +DataCreationHelper.prototype = { + + constructor: DataCreationHelper, + + leftToRightVector3: function ( v ) { + + v[ 2 ] = -v[ 2 ]; + + }, + + leftToRightQuaternion: function ( q ) { + + q[ 0 ] = -q[ 0 ]; + q[ 1 ] = -q[ 1 ]; + + }, + + leftToRightEuler: function ( r ) { + + r[ 0 ] = -r[ 0 ]; + r[ 1 ] = -r[ 1 ]; + + }, + + leftToRightIndexOrder: function ( p ) { + + var tmp = p[ 2 ]; + p[ 2 ] = p[ 0 ]; + p[ 0 ] = tmp; + + }, + + leftToRightVector3Range: function ( v1, v2 ) { + + var tmp = -v2[ 2 ]; + v2[ 2 ] = -v1[ 2 ]; + v1[ 2 ] = tmp; + + }, + + leftToRightEulerRange: function ( r1, r2 ) { + + var tmp1 = -r2[ 0 ]; + var tmp2 = -r2[ 1 ]; + r2[ 0 ] = -r1[ 0 ]; + r2[ 1 ] = -r1[ 1 ]; + r1[ 0 ] = tmp1; + r1[ 1 ] = tmp2; + + } + +}; + +/** + * @author takahiro / https://github.com/takahirox + */ + +function Parser() { +} + +Parser.prototype.parsePmd = function ( buffer, leftToRight ) { + + var pmd = {}; + var dv = new DataViewEx( buffer ); + + pmd.metadata = {}; + pmd.metadata.format = 'pmd'; + pmd.metadata.coordinateSystem = 'left'; + + var parseHeader = function () { + + var metadata = pmd.metadata; + metadata.magic = dv.getChars( 3 ); + + if ( metadata.magic !== 'Pmd' ) { + + throw 'PMD file magic is not Pmd, but ' + metadata.magic; + + } + + metadata.version = dv.getFloat32(); + metadata.modelName = dv.getSjisStringsAsUnicode( 20 ); + metadata.comment = dv.getSjisStringsAsUnicode( 256 ); + + }; + + var parseVertices = function () { + + var parseVertex = function () { + + var p = {}; + p.position = dv.getFloat32Array( 3 ); + p.normal = dv.getFloat32Array( 3 ); + p.uv = dv.getFloat32Array( 2 ); + p.skinIndices = dv.getUint16Array( 2 ); + p.skinWeights = [ dv.getUint8() / 100 ]; + p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); + p.edgeFlag = dv.getUint8(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.vertexCount = dv.getUint32(); + + pmd.vertices = []; + + for ( var i = 0; i < metadata.vertexCount; i++ ) { + + pmd.vertices.push( parseVertex() ); + + } + + }; + + var parseFaces = function () { + + var parseFace = function () { + + var p = {}; + p.indices = dv.getUint16Array( 3 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.faceCount = dv.getUint32() / 3; + + pmd.faces = []; + + for ( var i = 0; i < metadata.faceCount; i++ ) { + + pmd.faces.push( parseFace() ); + + } + + }; + + var parseMaterials = function () { + + var parseMaterial = function () { + + var p = {}; + p.diffuse = dv.getFloat32Array( 4 ); + p.shininess = dv.getFloat32(); + p.specular = dv.getFloat32Array( 3 ); + p.ambient = dv.getFloat32Array( 3 ); + p.toonIndex = dv.getInt8(); + p.edgeFlag = dv.getUint8(); + p.faceCount = dv.getUint32() / 3; + p.fileName = dv.getSjisStringsAsUnicode( 20 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.materialCount = dv.getUint32(); + + pmd.materials = []; + + for ( var i = 0; i < metadata.materialCount; i++ ) { + + pmd.materials.push( parseMaterial() ); + + } + + }; + + var parseBones = function () { + + var parseBone = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.parentIndex = dv.getInt16(); + p.tailIndex = dv.getInt16(); + p.type = dv.getUint8(); + p.ikIndex = dv.getInt16(); + p.position = dv.getFloat32Array( 3 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.boneCount = dv.getUint16(); + + pmd.bones = []; + + for ( var i = 0; i < metadata.boneCount; i++ ) { + + pmd.bones.push( parseBone() ); + + } + + }; + + var parseIks = function () { + + var parseIk = function () { + + var p = {}; + p.target = dv.getUint16(); + p.effector = dv.getUint16(); + p.linkCount = dv.getUint8(); + p.iteration = dv.getUint16(); + p.maxAngle = dv.getFloat32(); + + p.links = []; + for ( var i = 0; i < p.linkCount; i++ ) { + + var link = {}; + link.index = dv.getUint16(); + p.links.push( link ); + + } + + return p; + + }; + + var metadata = pmd.metadata; + metadata.ikCount = dv.getUint16(); + + pmd.iks = []; + + for ( var i = 0; i < metadata.ikCount; i++ ) { + + pmd.iks.push( parseIk() ); + + } + + }; + + var parseMorphs = function () { + + var parseMorph = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.elementCount = dv.getUint32(); + p.type = dv.getUint8(); + + p.elements = []; + for ( var i = 0; i < p.elementCount; i++ ) { + + p.elements.push( { + index: dv.getUint32(), + position: dv.getFloat32Array( 3 ) + } ) ; + + } + + return p; + + }; + + var metadata = pmd.metadata; + metadata.morphCount = dv.getUint16(); + + pmd.morphs = []; + + for ( var i = 0; i < metadata.morphCount; i++ ) { + + pmd.morphs.push( parseMorph() ); + + } + + + }; + + var parseMorphFrames = function () { + + var parseMorphFrame = function () { + + var p = {}; + p.index = dv.getUint16(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.morphFrameCount = dv.getUint8(); + + pmd.morphFrames = []; + + for ( var i = 0; i < metadata.morphFrameCount; i++ ) { + + pmd.morphFrames.push( parseMorphFrame() ); + + } + + }; + + var parseBoneFrameNames = function () { + + var parseBoneFrameName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 50 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.boneFrameNameCount = dv.getUint8(); + + pmd.boneFrameNames = []; + + for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { + + pmd.boneFrameNames.push( parseBoneFrameName() ); + + } + + }; + + var parseBoneFrames = function () { + + var parseBoneFrame = function () { + + var p = {}; + p.boneIndex = dv.getInt16(); + p.frameIndex = dv.getUint8(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.boneFrameCount = dv.getUint32(); + + pmd.boneFrames = []; + + for ( var i = 0; i < metadata.boneFrameCount; i++ ) { + + pmd.boneFrames.push( parseBoneFrame() ); + + } + + }; + + var parseEnglishHeader = function () { + + var metadata = pmd.metadata; + metadata.englishCompatibility = dv.getUint8(); + + if ( metadata.englishCompatibility > 0 ) { + + metadata.englishModelName = dv.getSjisStringsAsUnicode( 20 ); + metadata.englishComment = dv.getSjisStringsAsUnicode( 256 ); + + } + + }; + + var parseEnglishBoneNames = function () { + + var parseEnglishBoneName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + return p; + + }; + + var metadata = pmd.metadata; + + if ( metadata.englishCompatibility === 0 ) { + + return; + + } + + pmd.englishBoneNames = []; + + for ( var i = 0; i < metadata.boneCount; i++ ) { + + pmd.englishBoneNames.push( parseEnglishBoneName() ); + + } + + }; + + var parseEnglishMorphNames = function () { + + var parseEnglishMorphName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + return p; + + }; + + var metadata = pmd.metadata; + + if ( metadata.englishCompatibility === 0 ) { + + return; + + } + + pmd.englishMorphNames = []; + + for ( var i = 0; i < metadata.morphCount - 1; i++ ) { + + pmd.englishMorphNames.push( parseEnglishMorphName() ); + + } + + }; + + var parseEnglishBoneFrameNames = function () { + + var parseEnglishBoneFrameName = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 50 ); + return p; + + }; + + var metadata = pmd.metadata; + + if ( metadata.englishCompatibility === 0 ) { + + return; + + } + + pmd.englishBoneFrameNames = []; + + for ( var i = 0; i < metadata.boneFrameNameCount; i++ ) { + + pmd.englishBoneFrameNames.push( parseEnglishBoneFrameName() ); + + } + + }; + + var parseToonTextures = function () { + + var parseToonTexture = function () { + + var p = {}; + p.fileName = dv.getSjisStringsAsUnicode( 100 ); + return p; + + }; + + pmd.toonTextures = []; + + for ( var i = 0; i < 10; i++ ) { + + pmd.toonTextures.push( parseToonTexture() ); + + } + + }; + + var parseRigidBodies = function () { + + var parseRigidBody = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.boneIndex = dv.getInt16(); + p.groupIndex = dv.getUint8(); + p.groupTarget = dv.getUint16(); + p.shapeType = dv.getUint8(); + p.width = dv.getFloat32(); + p.height = dv.getFloat32(); + p.depth = dv.getFloat32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.weight = dv.getFloat32(); + p.positionDamping = dv.getFloat32(); + p.rotationDamping = dv.getFloat32(); + p.restitution = dv.getFloat32(); + p.friction = dv.getFloat32(); + p.type = dv.getUint8(); + return p; + + }; + + var metadata = pmd.metadata; + metadata.rigidBodyCount = dv.getUint32(); + + pmd.rigidBodies = []; + + for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { + + pmd.rigidBodies.push( parseRigidBody() ); + + } + + }; + + var parseConstraints = function () { + + var parseConstraint = function () { + + var p = {}; + p.name = dv.getSjisStringsAsUnicode( 20 ); + p.rigidBodyIndex1 = dv.getUint32(); + p.rigidBodyIndex2 = dv.getUint32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.translationLimitation1 = dv.getFloat32Array( 3 ); + p.translationLimitation2 = dv.getFloat32Array( 3 ); + p.rotationLimitation1 = dv.getFloat32Array( 3 ); + p.rotationLimitation2 = dv.getFloat32Array( 3 ); + p.springPosition = dv.getFloat32Array( 3 ); + p.springRotation = dv.getFloat32Array( 3 ); + return p; + + }; + + var metadata = pmd.metadata; + metadata.constraintCount = dv.getUint32(); + + pmd.constraints = []; + + for ( var i = 0; i < metadata.constraintCount; i++ ) { + + pmd.constraints.push( parseConstraint() ); + + } + + }; + + parseHeader(); + parseVertices(); + parseFaces(); + parseMaterials(); + parseBones(); + parseIks(); + parseMorphs(); + parseMorphFrames(); + parseBoneFrameNames(); + parseBoneFrames(); + parseEnglishHeader(); + parseEnglishBoneNames(); + parseEnglishMorphNames(); + parseEnglishBoneFrameNames(); + parseToonTextures(); + parseRigidBodies(); + parseConstraints(); + + if ( leftToRight === true ) this.leftToRightModel( pmd ); + + // console.log( pmd ); // for console debug + + return pmd; + +}; + +Parser.prototype.parsePmx = function ( buffer, leftToRight ) { + + var pmx = {}; + var dv = new DataViewEx( buffer ); + + pmx.metadata = {}; + pmx.metadata.format = 'pmx'; + pmx.metadata.coordinateSystem = 'left'; + + var parseHeader = function () { + + var metadata = pmx.metadata; + metadata.magic = dv.getChars( 4 ); + + // Note: don't remove the last blank space. + if ( metadata.magic !== 'PMX ' ) { + + throw 'PMX file magic is not PMX , but ' + metadata.magic; + + } + + metadata.version = dv.getFloat32(); + + if ( metadata.version !== 2.0 && metadata.version !== 2.1 ) { + + throw 'PMX version ' + metadata.version + ' is not supported.'; + + } + + metadata.headerSize = dv.getUint8(); + metadata.encoding = dv.getUint8(); + metadata.additionalUvNum = dv.getUint8(); + metadata.vertexIndexSize = dv.getUint8(); + metadata.textureIndexSize = dv.getUint8(); + metadata.materialIndexSize = dv.getUint8(); + metadata.boneIndexSize = dv.getUint8(); + metadata.morphIndexSize = dv.getUint8(); + metadata.rigidBodyIndexSize = dv.getUint8(); + metadata.modelName = dv.getTextBuffer(); + metadata.englishModelName = dv.getTextBuffer(); + metadata.comment = dv.getTextBuffer(); + metadata.englishComment = dv.getTextBuffer(); + + }; + + var parseVertices = function () { + + var parseVertex = function () { + + var p = {}; + p.position = dv.getFloat32Array( 3 ); + p.normal = dv.getFloat32Array( 3 ); + p.uv = dv.getFloat32Array( 2 ); + + p.auvs = []; + + for ( var i = 0; i < pmx.metadata.additionalUvNum; i++ ) { + + p.auvs.push( dv.getFloat32Array( 4 ) ); + + } + + p.type = dv.getUint8(); + + var indexSize = metadata.boneIndexSize; + + if ( p.type === 0 ) { // BDEF1 + + p.skinIndices = dv.getIndexArray( indexSize, 1 ); + p.skinWeights = [ 1.0 ]; + + } else if ( p.type === 1 ) { // BDEF2 + + p.skinIndices = dv.getIndexArray( indexSize, 2 ); + p.skinWeights = dv.getFloat32Array( 1 ); + p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); + + } else if ( p.type === 2 ) { // BDEF4 + + p.skinIndices = dv.getIndexArray( indexSize, 4 ); + p.skinWeights = dv.getFloat32Array( 4 ); + + } else if ( p.type === 3 ) { // SDEF + + p.skinIndices = dv.getIndexArray( indexSize, 2 ); + p.skinWeights = dv.getFloat32Array( 1 ); + p.skinWeights.push( 1.0 - p.skinWeights[ 0 ] ); + + p.skinC = dv.getFloat32Array( 3 ); + p.skinR0 = dv.getFloat32Array( 3 ); + p.skinR1 = dv.getFloat32Array( 3 ); + + // SDEF is not supported yet and is handled as BDEF2 so far. + // TODO: SDEF support + p.type = 1; + + } else { + + throw 'unsupport bone type ' + p.type + ' exception.'; + + } + + p.edgeRatio = dv.getFloat32(); + return p; + + }; + + var metadata = pmx.metadata; + metadata.vertexCount = dv.getUint32(); + + pmx.vertices = []; + + for ( var i = 0; i < metadata.vertexCount; i++ ) { + + pmx.vertices.push( parseVertex() ); + + } + + }; + + var parseFaces = function () { + + var parseFace = function () { + + var p = {}; + p.indices = dv.getIndexArray( metadata.vertexIndexSize, 3, true ); + return p; + + }; + + var metadata = pmx.metadata; + metadata.faceCount = dv.getUint32() / 3; + + pmx.faces = []; + + for ( var i = 0; i < metadata.faceCount; i++ ) { + + pmx.faces.push( parseFace() ); + + } + + }; + + var parseTextures = function () { + + var parseTexture = function () { + + return dv.getTextBuffer(); + + }; + + var metadata = pmx.metadata; + metadata.textureCount = dv.getUint32(); + + pmx.textures = []; + + for ( var i = 0; i < metadata.textureCount; i++ ) { + + pmx.textures.push( parseTexture() ); + + } + + }; + + var parseMaterials = function () { + + var parseMaterial = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.diffuse = dv.getFloat32Array( 4 ); + p.specular = dv.getFloat32Array( 3 ); + p.shininess = dv.getFloat32(); + p.ambient = dv.getFloat32Array( 3 ); + p.flag = dv.getUint8(); + p.edgeColor = dv.getFloat32Array( 4 ); + p.edgeSize = dv.getFloat32(); + p.textureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); + p.envTextureIndex = dv.getIndex( pmx.metadata.textureIndexSize ); + p.envFlag = dv.getUint8(); + p.toonFlag = dv.getUint8(); + + if ( p.toonFlag === 0 ) { + + p.toonIndex = dv.getIndex( pmx.metadata.textureIndexSize ); + + } else if ( p.toonFlag === 1 ) { + + p.toonIndex = dv.getInt8(); + + } else { + + throw 'unknown toon flag ' + p.toonFlag + ' exception.'; + + } + + p.comment = dv.getTextBuffer(); + p.faceCount = dv.getUint32() / 3; + return p; + + }; + + var metadata = pmx.metadata; + metadata.materialCount = dv.getUint32(); + + pmx.materials = []; + + for ( var i = 0; i < metadata.materialCount; i++ ) { + + pmx.materials.push( parseMaterial() ); + + } + + }; + + var parseBones = function () { + + var parseBone = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.position = dv.getFloat32Array( 3 ); + p.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + p.transformationClass = dv.getUint32(); + p.flag = dv.getUint16(); + + if ( p.flag & 0x1 ) { + + p.connectIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + + } else { + + p.offsetPosition = dv.getFloat32Array( 3 ); + + } + + if ( p.flag & 0x100 || p.flag & 0x200 ) { + + // Note: I don't think Grant is an appropriate name + // but I found that some English translated MMD tools use this term + // so I've named it Grant so far. + // I'd rename to more appropriate name from Grant later. + var grant = {}; + + grant.isLocal = ( p.flag & 0x80 ) !== 0 ? true : false; + grant.affectRotation = ( p.flag & 0x100 ) !== 0 ? true : false; + grant.affectPosition = ( p.flag & 0x200 ) !== 0 ? true : false; + grant.parentIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + grant.ratio = dv.getFloat32(); + + p.grant = grant; + + } + + if ( p.flag & 0x400 ) { + + p.fixAxis = dv.getFloat32Array( 3 ); + + } + + if ( p.flag & 0x800 ) { + + p.localXVector = dv.getFloat32Array( 3 ); + p.localZVector = dv.getFloat32Array( 3 ); + + } + + if ( p.flag & 0x2000 ) { + + p.key = dv.getUint32(); + + } + + if ( p.flag & 0x20 ) { + + var ik = {}; + + ik.effector = dv.getIndex( pmx.metadata.boneIndexSize ); + ik.target = null; + ik.iteration = dv.getUint32(); + ik.maxAngle = dv.getFloat32(); + ik.linkCount = dv.getUint32(); + ik.links = []; + + for ( var i = 0; i < ik.linkCount; i++ ) { + + var link = {}; + link.index = dv.getIndex( pmx.metadata.boneIndexSize ); + link.angleLimitation = dv.getUint8(); + + if ( link.angleLimitation === 1 ) { + + link.lowerLimitationAngle = dv.getFloat32Array( 3 ); + link.upperLimitationAngle = dv.getFloat32Array( 3 ); + + } + + ik.links.push( link ); + + } + + p.ik = ik; + } + + return p; + + }; + + var metadata = pmx.metadata; + metadata.boneCount = dv.getUint32(); + + pmx.bones = []; + + for ( var i = 0; i < metadata.boneCount; i++ ) { + + pmx.bones.push( parseBone() ); + + } + + }; + + var parseMorphs = function () { + + var parseMorph = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.panel = dv.getUint8(); + p.type = dv.getUint8(); + p.elementCount = dv.getUint32(); + p.elements = []; + + for ( var i = 0; i < p.elementCount; i++ ) { + + if ( p.type === 0 ) { // group morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.morphIndexSize ); + m.ratio = dv.getFloat32(); + p.elements.push( m ); + + } else if ( p.type === 1 ) { // vertex morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); + m.position = dv.getFloat32Array( 3 ); + p.elements.push( m ); + + } else if ( p.type === 2 ) { // bone morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.boneIndexSize ); + m.position = dv.getFloat32Array( 3 ); + m.rotation = dv.getFloat32Array( 4 ); + p.elements.push( m ); + + } else if ( p.type === 3 ) { // uv morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.vertexIndexSize, true ); + m.uv = dv.getFloat32Array( 4 ); + p.elements.push( m ); + + } else if ( p.type === 4 ) { // additional uv1 + + // TODO: implement + + } else if ( p.type === 5 ) { // additional uv2 + + // TODO: implement + + } else if ( p.type === 6 ) { // additional uv3 + + // TODO: implement + + } else if ( p.type === 7 ) { // additional uv4 + + // TODO: implement + + } else if ( p.type === 8 ) { // material morph + + var m = {}; + m.index = dv.getIndex( pmx.metadata.materialIndexSize ); + m.type = dv.getUint8(); + m.diffuse = dv.getFloat32Array( 4 ); + m.specular = dv.getFloat32Array( 3 ); + m.shininess = dv.getFloat32(); + m.ambient = dv.getFloat32Array( 3 ); + m.edgeColor = dv.getFloat32Array( 4 ); + m.edgeSize = dv.getFloat32(); + m.textureColor = dv.getFloat32Array( 4 ); + m.sphereTextureColor = dv.getFloat32Array( 4 ); + m.toonColor = dv.getFloat32Array( 4 ); + p.elements.push( m ); + + } + + } + + return p; + + }; + + var metadata = pmx.metadata; + metadata.morphCount = dv.getUint32(); + + pmx.morphs = []; + + for ( var i = 0; i < metadata.morphCount; i++ ) { + + pmx.morphs.push( parseMorph() ); + + } + + }; + + var parseFrames = function () { + + var parseFrame = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.type = dv.getUint8(); + p.elementCount = dv.getUint32(); + p.elements = []; + + for ( var i = 0; i < p.elementCount; i++ ) { + + var e = {}; + e.target = dv.getUint8(); + e.index = ( e.target === 0 ) ? dv.getIndex( pmx.metadata.boneIndexSize ) : dv.getIndex( pmx.metadata.morphIndexSize ); + p.elements.push( e ); + + } + + return p; + + }; + + var metadata = pmx.metadata; + metadata.frameCount = dv.getUint32(); + + pmx.frames = []; + + for ( var i = 0; i < metadata.frameCount; i++ ) { + + pmx.frames.push( parseFrame() ); + + } + + }; + + var parseRigidBodies = function () { + + var parseRigidBody = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.boneIndex = dv.getIndex( pmx.metadata.boneIndexSize ); + p.groupIndex = dv.getUint8(); + p.groupTarget = dv.getUint16(); + p.shapeType = dv.getUint8(); + p.width = dv.getFloat32(); + p.height = dv.getFloat32(); + p.depth = dv.getFloat32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.weight = dv.getFloat32(); + p.positionDamping = dv.getFloat32(); + p.rotationDamping = dv.getFloat32(); + p.restitution = dv.getFloat32(); + p.friction = dv.getFloat32(); + p.type = dv.getUint8(); + return p; + + }; + + var metadata = pmx.metadata; + metadata.rigidBodyCount = dv.getUint32(); + + pmx.rigidBodies = []; + + for ( var i = 0; i < metadata.rigidBodyCount; i++ ) { + + pmx.rigidBodies.push( parseRigidBody() ); + + } + + }; + + var parseConstraints = function () { + + var parseConstraint = function () { + + var p = {}; + p.name = dv.getTextBuffer(); + p.englishName = dv.getTextBuffer(); + p.type = dv.getUint8(); + p.rigidBodyIndex1 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); + p.rigidBodyIndex2 = dv.getIndex( pmx.metadata.rigidBodyIndexSize ); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.translationLimitation1 = dv.getFloat32Array( 3 ); + p.translationLimitation2 = dv.getFloat32Array( 3 ); + p.rotationLimitation1 = dv.getFloat32Array( 3 ); + p.rotationLimitation2 = dv.getFloat32Array( 3 ); + p.springPosition = dv.getFloat32Array( 3 ); + p.springRotation = dv.getFloat32Array( 3 ); + return p; + + }; + + var metadata = pmx.metadata; + metadata.constraintCount = dv.getUint32(); + + pmx.constraints = []; + + for ( var i = 0; i < metadata.constraintCount; i++ ) { + + pmx.constraints.push( parseConstraint() ); + + } + + }; + + parseHeader(); + parseVertices(); + parseFaces(); + parseTextures(); + parseMaterials(); + parseBones(); + parseMorphs(); + parseFrames(); + parseRigidBodies(); + parseConstraints(); + + if ( leftToRight === true ) this.leftToRightModel( pmx ); + + // console.log( pmx ); // for console debug + + return pmx; + +}; + +Parser.prototype.parseVmd = function ( buffer, leftToRight ) { + + var vmd = {}; + var dv = new DataViewEx( buffer ); + + vmd.metadata = {}; + vmd.metadata.coordinateSystem = 'left'; + + var parseHeader = function () { + + var metadata = vmd.metadata; + metadata.magic = dv.getChars( 30 ); + + if ( metadata.magic !== 'Vocaloid Motion Data 0002' ) { + + throw 'VMD file magic is not Vocaloid Motion Data 0002, but ' + metadata.magic; + + } + + metadata.name = dv.getSjisStringsAsUnicode( 20 ); + + }; + + var parseMotions = function () { + + var parseMotion = function () { + + var p = {}; + p.boneName = dv.getSjisStringsAsUnicode( 15 ); + p.frameNum = dv.getUint32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 4 ); + p.interpolation = dv.getUint8Array( 64 ); + return p; + + }; + + var metadata = vmd.metadata; + metadata.motionCount = dv.getUint32(); + + vmd.motions = []; + for ( var i = 0; i < metadata.motionCount; i++ ) { + + vmd.motions.push( parseMotion() ); + + } + + }; + + var parseMorphs = function () { + + var parseMorph = function () { + + var p = {}; + p.morphName = dv.getSjisStringsAsUnicode( 15 ); + p.frameNum = dv.getUint32(); + p.weight = dv.getFloat32(); + return p; + + }; + + var metadata = vmd.metadata; + metadata.morphCount = dv.getUint32(); + + vmd.morphs = []; + for ( var i = 0; i < metadata.morphCount; i++ ) { + + vmd.morphs.push( parseMorph() ); + + } + + }; + + var parseCameras = function () { + + var parseCamera = function () { + + var p = {}; + p.frameNum = dv.getUint32(); + p.distance = dv.getFloat32(); + p.position = dv.getFloat32Array( 3 ); + p.rotation = dv.getFloat32Array( 3 ); + p.interpolation = dv.getUint8Array( 24 ); + p.fov = dv.getUint32(); + p.perspective = dv.getUint8(); + return p; + + }; + + var metadata = vmd.metadata; + metadata.cameraCount = dv.getUint32(); + + vmd.cameras = []; + for ( var i = 0; i < metadata.cameraCount; i++ ) { + + vmd.cameras.push( parseCamera() ); + + } + + }; + + parseHeader(); + parseMotions(); + parseMorphs(); + parseCameras(); + + if ( leftToRight === true ) this.leftToRightVmd( vmd ); + + // console.log( vmd ); // for console debug + + return vmd; + +}; + +Parser.prototype.parseVpd = function ( text, leftToRight ) { + + var vpd = {}; + + vpd.metadata = {}; + vpd.metadata.coordinateSystem = 'left'; + + vpd.bones = []; + + var commentPatternG = /\/\/\w*(\r|\n|\r\n)/g; + var newlinePattern = /\r|\n|\r\n/; + + var lines = text.replace( commentPatternG, '' ).split( newlinePattern ); + + function throwError () { + + throw 'the file seems not vpd file.'; + + } + + function checkMagic () { + + if ( lines[ 0 ] !== 'Vocaloid Pose Data file' ) { + + throwError(); + + } + + } + + function parseHeader () { + + if ( lines.length < 4 ) { + + throwError(); + + } + + vpd.metadata.parentFile = lines[ 2 ]; + vpd.metadata.boneCount = parseInt( lines[ 3 ] ); + + } + + function parseBones () { + + var boneHeaderPattern = /^\s*(Bone[0-9]+)\s*\{\s*(.*)$/; + var boneVectorPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/; + var boneQuaternionPattern = /^\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*,\s*(-?[0-9]+\.[0-9]+)\s*;/; + var boneFooterPattern = /^\s*}/; + + var bones = vpd.bones; + var n = null; + var v = null; + var q = null; + + for ( var i = 4; i < lines.length; i++ ) { + + var line = lines[ i ]; + + var result; + + result = line.match( boneHeaderPattern ); + + if ( result !== null ) { + + if ( n !== null ) { + + throwError(); + + } + + n = result[ 2 ]; + + } + + result = line.match( boneVectorPattern ); + + if ( result !== null ) { + + if ( v !== null ) { + + throwError(); + + } + + v = [ + + parseFloat( result[ 1 ] ), + parseFloat( result[ 2 ] ), + parseFloat( result[ 3 ] ) + + ]; + + } + + result = line.match( boneQuaternionPattern ); + + if ( result !== null ) { + + if ( q !== null ) { + + throwError(); + + } + + q = [ + + parseFloat( result[ 1 ] ), + parseFloat( result[ 2 ] ), + parseFloat( result[ 3 ] ), + parseFloat( result[ 4 ] ) + + ]; + + + } + + result = line.match( boneFooterPattern ); + + if ( result !== null ) { + + if ( n === null || v === null || q === null ) { + + throwError(); + + } + + bones.push( { + + name: n, + translation: v, + quaternion: q + + } ); + + n = null; + v = null; + q = null; + + } + + } + + if ( n !== null || v !== null || q !== null ) { + + throwError(); + + } + + } + + checkMagic(); + parseHeader(); + parseBones(); + + if ( leftToRight === true ) this.leftToRightVpd( vpd ); + + // console.log( vpd ); // for console debug + + return vpd; + +}; + +Parser.prototype.mergeVmds = function ( vmds ) { + + var v = {}; + v.metadata = {}; + v.metadata.name = vmds[ 0 ].metadata.name; + v.metadata.coordinateSystem = vmds[ 0 ].metadata.coordinateSystem; + v.metadata.motionCount = 0; + v.metadata.morphCount = 0; + v.metadata.cameraCount = 0; + v.motions = []; + v.morphs = []; + v.cameras = []; + + for ( var i = 0; i < vmds.length; i++ ) { + + var v2 = vmds[ i ]; + + v.metadata.motionCount += v2.metadata.motionCount; + v.metadata.morphCount += v2.metadata.morphCount; + v.metadata.cameraCount += v2.metadata.cameraCount; + + for ( var j = 0; j < v2.metadata.motionCount; j++ ) { + + v.motions.push( v2.motions[ j ] ); + + } + + for ( var j = 0; j < v2.metadata.morphCount; j++ ) { + + v.morphs.push( v2.morphs[ j ] ); + + } + + for ( var j = 0; j < v2.metadata.cameraCount; j++ ) { + + v.cameras.push( v2.cameras[ j ] ); + + } + + } + + return v; + +}; + +Parser.prototype.leftToRightModel = function ( model ) { + + if ( model.metadata.coordinateSystem === 'right' ) { + + return; + + } + + model.metadata.coordinateSystem = 'right'; + + var helper = new DataCreationHelper(); + + for ( var i = 0; i < model.metadata.vertexCount; i++ ) { + + helper.leftToRightVector3( model.vertices[ i ].position ); + helper.leftToRightVector3( model.vertices[ i ].normal ); + + } + + for ( var i = 0; i < model.metadata.faceCount; i++ ) { + + helper.leftToRightIndexOrder( model.faces[ i ].indices ); + + } + + for ( var i = 0; i < model.metadata.boneCount; i++ ) { + + helper.leftToRightVector3( model.bones[ i ].position ); + + } + + // TODO: support other morph for PMX + for ( var i = 0; i < model.metadata.morphCount; i++ ) { + + var m = model.morphs[ i ]; + + if ( model.metadata.format === 'pmx' && m.type !== 1 ) { + + // TODO: implement + continue; + + } + + for ( var j = 0; j < m.elements.length; j++ ) { + + helper.leftToRightVector3( m.elements[ j ].position ); + + } + + } + + for ( var i = 0; i < model.metadata.rigidBodyCount; i++ ) { + + helper.leftToRightVector3( model.rigidBodies[ i ].position ); + helper.leftToRightEuler( model.rigidBodies[ i ].rotation ); + + } + + for ( var i = 0; i < model.metadata.constraintCount; i++ ) { + + helper.leftToRightVector3( model.constraints[ i ].position ); + helper.leftToRightEuler( model.constraints[ i ].rotation ); + helper.leftToRightVector3Range( model.constraints[ i ].translationLimitation1, model.constraints[ i ].translationLimitation2 ); + helper.leftToRightEulerRange( model.constraints[ i ].rotationLimitation1, model.constraints[ i ].rotationLimitation2 ); + + } + +}; + +Parser.prototype.leftToRightVmd = function ( vmd ) { + + if ( vmd.metadata.coordinateSystem === 'right' ) { + + return; + + } + + vmd.metadata.coordinateSystem = 'right'; + + var helper = new DataCreationHelper(); + + for ( var i = 0; i < vmd.metadata.motionCount; i++ ) { + + helper.leftToRightVector3( vmd.motions[ i ].position ); + helper.leftToRightQuaternion( vmd.motions[ i ].rotation ); + + } + + for ( var i = 0; i < vmd.metadata.cameraCount; i++ ) { + + helper.leftToRightVector3( vmd.cameras[ i ].position ); + helper.leftToRightEuler( vmd.cameras[ i ].rotation ); + + } + +}; + +Parser.prototype.leftToRightVpd = function ( vpd ) { + + if ( vpd.metadata.coordinateSystem === 'right' ) { + + return; + + } + + vpd.metadata.coordinateSystem = 'right'; + + var helper = new DataCreationHelper(); + + for ( var i = 0; i < vpd.bones.length; i++ ) { + + helper.leftToRightVector3( vpd.bones[ i ].translation ); + helper.leftToRightQuaternion( vpd.bones[ i ].quaternion ); + + } + +}; + +var MMDParser = { + CharsetEncoder: CharsetEncoder, + Parser: Parser +}; + +export { MMDParser, CharsetEncoder, Parser }; diff --git a/jsm/libs/motion-controllers.module.js b/jsm/libs/motion-controllers.module.js new file mode 100644 index 0000000..9b2caae --- /dev/null +++ b/jsm/libs/motion-controllers.module.js @@ -0,0 +1,397 @@ +/** + * @webxr-input-profiles/motion-controllers 1.0.0 https://github.com/immersive-web/webxr-input-profiles + */ + +const Constants = { + Handedness: Object.freeze({ + NONE: 'none', + LEFT: 'left', + RIGHT: 'right' + }), + + ComponentState: Object.freeze({ + DEFAULT: 'default', + TOUCHED: 'touched', + PRESSED: 'pressed' + }), + + ComponentProperty: Object.freeze({ + BUTTON: 'button', + X_AXIS: 'xAxis', + Y_AXIS: 'yAxis', + STATE: 'state' + }), + + ComponentType: Object.freeze({ + TRIGGER: 'trigger', + SQUEEZE: 'squeeze', + TOUCHPAD: 'touchpad', + THUMBSTICK: 'thumbstick', + BUTTON: 'button' + }), + + ButtonTouchThreshold: 0.05, + + AxisTouchThreshold: 0.1, + + VisualResponseProperty: Object.freeze({ + TRANSFORM: 'transform', + VISIBILITY: 'visibility' + }) +}; + +/** + * @description Static helper function to fetch a JSON file and turn it into a JS object + * @param {string} path - Path to JSON file to be fetched + */ +async function fetchJsonFile(path) { + const response = await fetch(path); + if (!response.ok) { + throw new Error(response.statusText); + } else { + return response.json(); + } +} + +async function fetchProfilesList(basePath) { + if (!basePath) { + throw new Error('No basePath supplied'); + } + + const profileListFileName = 'profilesList.json'; + const profilesList = await fetchJsonFile(`${basePath}/${profileListFileName}`); + return profilesList; +} + +async function fetchProfile(xrInputSource, basePath, defaultProfile = null, getAssetPath = true) { + if (!xrInputSource) { + throw new Error('No xrInputSource supplied'); + } + + if (!basePath) { + throw new Error('No basePath supplied'); + } + + // Get the list of profiles + const supportedProfilesList = await fetchProfilesList(basePath); + + // Find the relative path to the first requested profile that is recognized + let match; + xrInputSource.profiles.some((profileId) => { + const supportedProfile = supportedProfilesList[profileId]; + if (supportedProfile) { + match = { + profileId, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !!supportedProfile.deprecated + }; + } + return !!match; + }); + + if (!match) { + if (!defaultProfile) { + throw new Error('No matching profile name found'); + } + + const supportedProfile = supportedProfilesList[defaultProfile]; + if (!supportedProfile) { + throw new Error(`No matching profile name found and default profile "${defaultProfile}" missing.`); + } + + match = { + profileId: defaultProfile, + profilePath: `${basePath}/${supportedProfile.path}`, + deprecated: !!supportedProfile.deprecated + }; + } + + const profile = await fetchJsonFile(match.profilePath); + + let assetPath; + if (getAssetPath) { + let layout; + if (xrInputSource.handedness === 'any') { + layout = profile.layouts[Object.keys(profile.layouts)[0]]; + } else { + layout = profile.layouts[xrInputSource.handedness]; + } + if (!layout) { + throw new Error( + `No matching handedness, ${xrInputSource.handedness}, in profile ${match.profileId}` + ); + } + + if (layout.assetPath) { + assetPath = match.profilePath.replace('profile.json', layout.assetPath); + } + } + + return { profile, assetPath }; +} + +/** @constant {Object} */ +const defaultComponentValues = { + xAxis: 0, + yAxis: 0, + button: 0, + state: Constants.ComponentState.DEFAULT +}; + +/** + * @description Converts an X, Y coordinate from the range -1 to 1 (as reported by the Gamepad + * API) to the range 0 to 1 (for interpolation). Also caps the X, Y values to be bounded within + * a circle. This ensures that thumbsticks are not animated outside the bounds of their physical + * range of motion and touchpads do not report touch locations off their physical bounds. + * @param {number} x The original x coordinate in the range -1 to 1 + * @param {number} y The original y coordinate in the range -1 to 1 + */ +function normalizeAxes(x = 0, y = 0) { + let xAxis = x; + let yAxis = y; + + // Determine if the point is outside the bounds of the circle + // and, if so, place it on the edge of the circle + const hypotenuse = Math.sqrt((x * x) + (y * y)); + if (hypotenuse > 1) { + const theta = Math.atan2(y, x); + xAxis = Math.cos(theta); + yAxis = Math.sin(theta); + } + + // Scale and move the circle so values are in the interpolation range. The circle's origin moves + // from (0, 0) to (0.5, 0.5). The circle's radius scales from 1 to be 0.5. + const result = { + normalizedXAxis: (xAxis * 0.5) + 0.5, + normalizedYAxis: (yAxis * 0.5) + 0.5 + }; + return result; +} + +/** + * Contains the description of how the 3D model should visually respond to a specific user input. + * This is accomplished by initializing the object with the name of a node in the 3D model and + * property that need to be modified in response to user input, the name of the nodes representing + * the allowable range of motion, and the name of the input which triggers the change. In response + * to the named input changing, this object computes the appropriate weighting to use for + * interpolating between the range of motion nodes. + */ +class VisualResponse { + constructor(visualResponseDescription) { + this.componentProperty = visualResponseDescription.componentProperty; + this.states = visualResponseDescription.states; + this.valueNodeName = visualResponseDescription.valueNodeName; + this.valueNodeProperty = visualResponseDescription.valueNodeProperty; + + if (this.valueNodeProperty === Constants.VisualResponseProperty.TRANSFORM) { + this.minNodeName = visualResponseDescription.minNodeName; + this.maxNodeName = visualResponseDescription.maxNodeName; + } + + // Initializes the response's current value based on default data + this.value = 0; + this.updateFromComponent(defaultComponentValues); + } + + /** + * Computes the visual response's interpolation weight based on component state + * @param {Object} componentValues - The component from which to update + * @param {number} xAxis - The reported X axis value of the component + * @param {number} yAxis - The reported Y axis value of the component + * @param {number} button - The reported value of the component's button + * @param {string} state - The component's active state + */ + updateFromComponent({ + xAxis, yAxis, button, state + }) { + const { normalizedXAxis, normalizedYAxis } = normalizeAxes(xAxis, yAxis); + switch (this.componentProperty) { + case Constants.ComponentProperty.X_AXIS: + this.value = (this.states.includes(state)) ? normalizedXAxis : 0.5; + break; + case Constants.ComponentProperty.Y_AXIS: + this.value = (this.states.includes(state)) ? normalizedYAxis : 0.5; + break; + case Constants.ComponentProperty.BUTTON: + this.value = (this.states.includes(state)) ? button : 0; + break; + case Constants.ComponentProperty.STATE: + if (this.valueNodeProperty === Constants.VisualResponseProperty.VISIBILITY) { + this.value = (this.states.includes(state)); + } else { + this.value = this.states.includes(state) ? 1.0 : 0.0; + } + break; + default: + throw new Error(`Unexpected visualResponse componentProperty ${this.componentProperty}`); + } + } +} + +class Component { + /** + * @param {Object} componentId - Id of the component + * @param {Object} componentDescription - Description of the component to be created + */ + constructor(componentId, componentDescription) { + if (!componentId + || !componentDescription + || !componentDescription.visualResponses + || !componentDescription.gamepadIndices + || Object.keys(componentDescription.gamepadIndices).length === 0) { + throw new Error('Invalid arguments supplied'); + } + + this.id = componentId; + this.type = componentDescription.type; + this.rootNodeName = componentDescription.rootNodeName; + this.touchPointNodeName = componentDescription.touchPointNodeName; + + // Build all the visual responses for this component + this.visualResponses = {}; + Object.keys(componentDescription.visualResponses).forEach((responseName) => { + const visualResponse = new VisualResponse(componentDescription.visualResponses[responseName]); + this.visualResponses[responseName] = visualResponse; + }); + + // Set default values + this.gamepadIndices = Object.assign({}, componentDescription.gamepadIndices); + + this.values = { + state: Constants.ComponentState.DEFAULT, + button: (this.gamepadIndices.button !== undefined) ? 0 : undefined, + xAxis: (this.gamepadIndices.xAxis !== undefined) ? 0 : undefined, + yAxis: (this.gamepadIndices.yAxis !== undefined) ? 0 : undefined + }; + } + + get data() { + const data = { id: this.id, ...this.values }; + return data; + } + + /** + * @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) { + // Set the state to default before processing other data sources + this.values.state = Constants.ComponentState.DEFAULT; + + // Get and normalize button + 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; + + // Set the state based on the button + if (gamepadButton.pressed || this.values.button === 1) { + this.values.state = Constants.ComponentState.PRESSED; + } else if (gamepadButton.touched || this.values.button > Constants.ButtonTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // 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; + + // If the state is still default, check if the xAxis makes it touched + if (this.values.state === Constants.ComponentState.DEFAULT + && Math.abs(this.values.xAxis) > Constants.AxisTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // 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; + + // If the state is still default, check if the yAxis makes it touched + if (this.values.state === Constants.ComponentState.DEFAULT + && Math.abs(this.values.yAxis) > Constants.AxisTouchThreshold) { + this.values.state = Constants.ComponentState.TOUCHED; + } + } + + // Update the visual response weights based on the current component data + Object.values(this.visualResponses).forEach((visualResponse) => { + visualResponse.updateFromComponent(this.values); + }); + } +} + +/** + * @description Builds a motion controller with components and visual responses based on the + * supplied profile description. Data is polled from the xrInputSource's gamepad. + * @author Nell Waliczek / https://github.com/NellWaliczek +*/ +class MotionController { + /** + * @param {Object} xrInputSource - The XRInputSource to build the MotionController around + * @param {Object} profile - The best matched profile description for the supplied xrInputSource + * @param {Object} assetUrl + */ + constructor(xrInputSource, profile, assetUrl) { + if (!xrInputSource) { + throw new Error('No xrInputSource supplied'); + } + + if (!profile) { + throw new Error('No profile supplied'); + } + + this.xrInputSource = xrInputSource; + this.assetUrl = assetUrl; + this.id = profile.profileId; + + // Build child components as described in the profile description + this.layoutDescription = profile.layouts[xrInputSource.handedness]; + this.components = {}; + Object.keys(this.layoutDescription.components).forEach((componentId) => { + const componentDescription = this.layoutDescription.components[componentId]; + this.components[componentId] = new Component(componentId, componentDescription); + }); + + // Initialize components based on current gamepad state + this.updateFromGamepad(); + } + + get gripSpace() { + return this.xrInputSource.gripSpace; + } + + get targetRaySpace() { + return this.xrInputSource.targetRaySpace; + } + + /** + * @description Returns a subset of component data for simplified debugging + */ + get data() { + const data = []; + Object.values(this.components).forEach((component) => { + data.push(component.data); + }); + return data; + } + + /** + * @description Poll for updated data based on current gamepad state + */ + updateFromGamepad() { + Object.values(this.components).forEach((component) => { + component.updateFromGamepad(this.xrInputSource.gamepad); + }); + } +} + +export { Constants, MotionController, fetchProfile, fetchProfilesList }; diff --git a/jsm/libs/opentype.module.js b/jsm/libs/opentype.module.js new file mode 100644 index 0000000..7103287 --- /dev/null +++ b/jsm/libs/opentype.module.js @@ -0,0 +1,14568 @@ +/** + * https://opentype.js.org v1.3.4 | (c) Frederik De Bleser and other contributors | MIT License | Uses tiny-inflate by Devon Govett and string.prototype.codepointat polyfill by Mathias Bynens + */ + +/*! https://mths.be/codepointat v0.2.0 by @mathias */ +if (!String.prototype.codePointAt) { + (function() { + var defineProperty = (function() { + // IE 8 only supports `Object.defineProperty` on DOM elements + try { + var object = {}; + var $defineProperty = Object.defineProperty; + var result = $defineProperty(object, object, object) && $defineProperty; + } catch(error) {} + return result; + }()); + var codePointAt = function(position) { + if (this == null) { + throw TypeError(); + } + var string = String(this); + var size = string.length; + // `ToInteger` + var index = position ? Number(position) : 0; + if (index != index) { // better `isNaN` + index = 0; + } + // Account for out-of-bounds indices: + if (index < 0 || index >= size) { + return undefined; + } + // Get the first code unit + var first = string.charCodeAt(index); + var second; + if ( // check if it’s the start of a surrogate pair + first >= 0xD800 && first <= 0xDBFF && // high surrogate + size > index + 1 // there is a next code unit + ) { + second = string.charCodeAt(index + 1); + if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate + // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae + return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000; + } + } + return first; + }; + if (defineProperty) { + defineProperty(String.prototype, 'codePointAt', { + 'value': codePointAt, + 'configurable': true, + 'writable': true + }); + } else { + String.prototype.codePointAt = codePointAt; + } + }()); +} + +var TINF_OK = 0; +var TINF_DATA_ERROR = -3; + +function Tree() { + this.table = new Uint16Array(16); /* table of code length counts */ + this.trans = new Uint16Array(288); /* code -> symbol translation table */ +} + +function Data(source, dest) { + this.source = source; + this.sourceIndex = 0; + this.tag = 0; + this.bitcount = 0; + + this.dest = dest; + this.destLen = 0; + + this.ltree = new Tree(); /* dynamic length/symbol tree */ + this.dtree = new Tree(); /* dynamic distance tree */ +} + +/* --------------------------------------------------- * + * -- uninitialized global data (static structures) -- * + * --------------------------------------------------- */ + +var sltree = new Tree(); +var sdtree = new Tree(); + +/* extra bits and base tables for length codes */ +var length_bits = new Uint8Array(30); +var length_base = new Uint16Array(30); + +/* extra bits and base tables for distance codes */ +var dist_bits = new Uint8Array(30); +var dist_base = new Uint16Array(30); + +/* special ordering of code length codes */ +var clcidx = new Uint8Array([ + 16, 17, 18, 0, 8, 7, 9, 6, + 10, 5, 11, 4, 12, 3, 13, 2, + 14, 1, 15 +]); + +/* used by tinf_decode_trees, avoids allocations every call */ +var code_tree = new Tree(); +var lengths = new Uint8Array(288 + 32); + +/* ----------------------- * + * -- utility functions -- * + * ----------------------- */ + +/* build extra bits and base tables */ +function tinf_build_bits_base(bits, base, delta, first) { + var i, sum; + + /* build bits table */ + for (i = 0; i < delta; ++i) { bits[i] = 0; } + for (i = 0; i < 30 - delta; ++i) { bits[i + delta] = i / delta | 0; } + + /* build base table */ + for (sum = first, i = 0; i < 30; ++i) { + base[i] = sum; + sum += 1 << bits[i]; + } +} + +/* build the fixed huffman trees */ +function tinf_build_fixed_trees(lt, dt) { + var i; + + /* build fixed length tree */ + for (i = 0; i < 7; ++i) { lt.table[i] = 0; } + + lt.table[7] = 24; + lt.table[8] = 152; + lt.table[9] = 112; + + for (i = 0; i < 24; ++i) { lt.trans[i] = 256 + i; } + for (i = 0; i < 144; ++i) { lt.trans[24 + i] = i; } + for (i = 0; i < 8; ++i) { lt.trans[24 + 144 + i] = 280 + i; } + for (i = 0; i < 112; ++i) { lt.trans[24 + 144 + 8 + i] = 144 + i; } + + /* build fixed distance tree */ + for (i = 0; i < 5; ++i) { dt.table[i] = 0; } + + dt.table[5] = 32; + + for (i = 0; i < 32; ++i) { dt.trans[i] = i; } +} + +/* given an array of code lengths, build a tree */ +var offs = new Uint16Array(16); + +function tinf_build_tree(t, lengths, off, num) { + var i, sum; + + /* clear code length count table */ + for (i = 0; i < 16; ++i) { t.table[i] = 0; } + + /* scan symbol lengths, and sum code length counts */ + for (i = 0; i < num; ++i) { t.table[lengths[off + i]]++; } + + t.table[0] = 0; + + /* compute offset table for distribution sort */ + for (sum = 0, i = 0; i < 16; ++i) { + offs[i] = sum; + sum += t.table[i]; + } + + /* create code->symbol translation table (symbols sorted by code) */ + for (i = 0; i < num; ++i) { + if (lengths[off + i]) { t.trans[offs[lengths[off + i]]++] = i; } + } +} + +/* ---------------------- * + * -- decode functions -- * + * ---------------------- */ + +/* get one bit from source stream */ +function tinf_getbit(d) { + /* check if tag is empty */ + if (!d.bitcount--) { + /* load next tag */ + d.tag = d.source[d.sourceIndex++]; + d.bitcount = 7; + } + + /* shift bit out of tag */ + var bit = d.tag & 1; + d.tag >>>= 1; + + return bit; +} + +/* read a num bit value from a stream and add base */ +function tinf_read_bits(d, num, base) { + if (!num) + { return base; } + + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var val = d.tag & (0xffff >>> (16 - num)); + d.tag >>>= num; + d.bitcount -= num; + return val + base; +} + +/* given a data stream and a tree, decode a symbol */ +function tinf_decode_symbol(d, t) { + while (d.bitcount < 24) { + d.tag |= d.source[d.sourceIndex++] << d.bitcount; + d.bitcount += 8; + } + + var sum = 0, cur = 0, len = 0; + var tag = d.tag; + + /* get more bits while code value is above sum */ + do { + cur = 2 * cur + (tag & 1); + tag >>>= 1; + ++len; + + sum += t.table[len]; + cur -= t.table[len]; + } while (cur >= 0); + + d.tag = tag; + d.bitcount -= len; + + return t.trans[sum + cur]; +} + +/* given a data stream, decode dynamic trees from it */ +function tinf_decode_trees(d, lt, dt) { + var hlit, hdist, hclen; + var i, num, length; + + /* get 5 bits HLIT (257-286) */ + hlit = tinf_read_bits(d, 5, 257); + + /* get 5 bits HDIST (1-32) */ + hdist = tinf_read_bits(d, 5, 1); + + /* get 4 bits HCLEN (4-19) */ + hclen = tinf_read_bits(d, 4, 4); + + for (i = 0; i < 19; ++i) { lengths[i] = 0; } + + /* read code lengths for code length alphabet */ + for (i = 0; i < hclen; ++i) { + /* get 3 bits code length (0-7) */ + var clen = tinf_read_bits(d, 3, 0); + lengths[clcidx[i]] = clen; + } + + /* build code length tree */ + tinf_build_tree(code_tree, lengths, 0, 19); + + /* decode code lengths for the dynamic trees */ + for (num = 0; num < hlit + hdist;) { + var sym = tinf_decode_symbol(d, code_tree); + + switch (sym) { + case 16: + /* copy previous code length 3-6 times (read 2 bits) */ + var prev = lengths[num - 1]; + for (length = tinf_read_bits(d, 2, 3); length; --length) { + lengths[num++] = prev; + } + break; + case 17: + /* repeat code length 0 for 3-10 times (read 3 bits) */ + for (length = tinf_read_bits(d, 3, 3); length; --length) { + lengths[num++] = 0; + } + break; + case 18: + /* repeat code length 0 for 11-138 times (read 7 bits) */ + for (length = tinf_read_bits(d, 7, 11); length; --length) { + lengths[num++] = 0; + } + break; + default: + /* values 0-15 represent the actual code lengths */ + lengths[num++] = sym; + break; + } + } + + /* build dynamic trees */ + tinf_build_tree(lt, lengths, 0, hlit); + tinf_build_tree(dt, lengths, hlit, hdist); +} + +/* ----------------------------- * + * -- block inflate functions -- * + * ----------------------------- */ + +/* given a stream and two trees, inflate a block of data */ +function tinf_inflate_block_data(d, lt, dt) { + while (1) { + var sym = tinf_decode_symbol(d, lt); + + /* check for end of block */ + if (sym === 256) { + return TINF_OK; + } + + if (sym < 256) { + d.dest[d.destLen++] = sym; + } else { + var length, dist, offs; + var i; + + sym -= 257; + + /* possibly get more bits from length code */ + length = tinf_read_bits(d, length_bits[sym], length_base[sym]); + + dist = tinf_decode_symbol(d, dt); + + /* possibly get more bits from distance code */ + offs = d.destLen - tinf_read_bits(d, dist_bits[dist], dist_base[dist]); + + /* copy match */ + for (i = offs; i < offs + length; ++i) { + d.dest[d.destLen++] = d.dest[i]; + } + } + } +} + +/* inflate an uncompressed block of data */ +function tinf_inflate_uncompressed_block(d) { + var length, invlength; + var i; + + /* unread from bitbuffer */ + while (d.bitcount > 8) { + d.sourceIndex--; + d.bitcount -= 8; + } + + /* get length */ + length = d.source[d.sourceIndex + 1]; + length = 256 * length + d.source[d.sourceIndex]; + + /* get one's complement of length */ + invlength = d.source[d.sourceIndex + 3]; + invlength = 256 * invlength + d.source[d.sourceIndex + 2]; + + /* check length */ + if (length !== (~invlength & 0x0000ffff)) + { return TINF_DATA_ERROR; } + + d.sourceIndex += 4; + + /* copy block */ + for (i = length; i; --i) + { d.dest[d.destLen++] = d.source[d.sourceIndex++]; } + + /* make sure we start next block on a byte boundary */ + d.bitcount = 0; + + return TINF_OK; +} + +/* inflate stream from source to dest */ +function tinf_uncompress(source, dest) { + var d = new Data(source, dest); + var bfinal, btype, res; + + do { + /* read final block flag */ + bfinal = tinf_getbit(d); + + /* read block type (2 bits) */ + btype = tinf_read_bits(d, 2, 0); + + /* decompress block */ + switch (btype) { + case 0: + /* decompress uncompressed block */ + res = tinf_inflate_uncompressed_block(d); + break; + case 1: + /* decompress block with fixed huffman trees */ + res = tinf_inflate_block_data(d, sltree, sdtree); + break; + case 2: + /* decompress block with dynamic huffman trees */ + tinf_decode_trees(d, d.ltree, d.dtree); + res = tinf_inflate_block_data(d, d.ltree, d.dtree); + break; + default: + res = TINF_DATA_ERROR; + } + + if (res !== TINF_OK) + { throw new Error('Data error'); } + + } while (!bfinal); + + if (d.destLen < d.dest.length) { + if (typeof d.dest.slice === 'function') + { return d.dest.slice(0, d.destLen); } + else + { return d.dest.subarray(0, d.destLen); } + } + + return d.dest; +} + +/* -------------------- * + * -- initialization -- * + * -------------------- */ + +/* build fixed huffman trees */ +tinf_build_fixed_trees(sltree, sdtree); + +/* build extra bits and base tables */ +tinf_build_bits_base(length_bits, length_base, 4, 3); +tinf_build_bits_base(dist_bits, dist_base, 2, 1); + +/* fix a special case */ +length_bits[28] = 0; +length_base[28] = 258; + +var tinyInflate = tinf_uncompress; + +// The Bounding Box object + +function derive(v0, v1, v2, v3, t) { + return Math.pow(1 - t, 3) * v0 + + 3 * Math.pow(1 - t, 2) * t * v1 + + 3 * (1 - t) * Math.pow(t, 2) * v2 + + Math.pow(t, 3) * v3; +} +/** + * A bounding box is an enclosing box that describes the smallest measure within which all the points lie. + * It is used to calculate the bounding box of a glyph or text path. + * + * On initialization, x1/y1/x2/y2 will be NaN. Check if the bounding box is empty using `isEmpty()`. + * + * @exports opentype.BoundingBox + * @class + * @constructor + */ +function BoundingBox() { + this.x1 = Number.NaN; + this.y1 = Number.NaN; + this.x2 = Number.NaN; + this.y2 = Number.NaN; +} + +/** + * Returns true if the bounding box is empty, that is, no points have been added to the box yet. + */ +BoundingBox.prototype.isEmpty = function() { + return isNaN(this.x1) || isNaN(this.y1) || isNaN(this.x2) || isNaN(this.y2); +}; + +/** + * Add the point to the bounding box. + * The x1/y1/x2/y2 coordinates of the bounding box will now encompass the given point. + * @param {number} x - The X coordinate of the point. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addPoint = function(x, y) { + if (typeof x === 'number') { + if (isNaN(this.x1) || isNaN(this.x2)) { + this.x1 = x; + this.x2 = x; + } + if (x < this.x1) { + this.x1 = x; + } + if (x > this.x2) { + this.x2 = x; + } + } + if (typeof y === 'number') { + if (isNaN(this.y1) || isNaN(this.y2)) { + this.y1 = y; + this.y2 = y; + } + if (y < this.y1) { + this.y1 = y; + } + if (y > this.y2) { + this.y2 = y; + } + } +}; + +/** + * Add a X coordinate to the bounding box. + * This extends the bounding box to include the X coordinate. + * This function is used internally inside of addBezier. + * @param {number} x - The X coordinate of the point. + */ +BoundingBox.prototype.addX = function(x) { + this.addPoint(x, null); +}; + +/** + * Add a Y coordinate to the bounding box. + * This extends the bounding box to include the Y coordinate. + * This function is used internally inside of addBezier. + * @param {number} y - The Y coordinate of the point. + */ +BoundingBox.prototype.addY = function(y) { + this.addPoint(null, y); +}; + +/** + * Add a Bézier curve to the bounding box. + * This extends the bounding box to include the entire Bézier. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the first control point. + * @param {number} y1 - The Y coordinate of the first control point. + * @param {number} x2 - The X coordinate of the second control point. + * @param {number} y2 - The Y coordinate of the second control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addBezier = function(x0, y0, x1, y1, x2, y2, x, y) { + // This code is based on http://nishiohirokazu.blogspot.com/2009/06/how-to-calculate-bezier-curves-bounding.html + // and https://github.com/icons8/svg-path-bounding-box + + var p0 = [x0, y0]; + var p1 = [x1, y1]; + var p2 = [x2, y2]; + var p3 = [x, y]; + + this.addPoint(x0, y0); + this.addPoint(x, y); + + for (var i = 0; i <= 1; i++) { + var b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i]; + var a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i]; + var c = 3 * p1[i] - 3 * p0[i]; + + if (a === 0) { + if (b === 0) { continue; } + var t = -c / b; + if (0 < t && t < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t)); } + } + continue; + } + + var b2ac = Math.pow(b, 2) - 4 * c * a; + if (b2ac < 0) { continue; } + var t1 = (-b + Math.sqrt(b2ac)) / (2 * a); + if (0 < t1 && t1 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t1)); } + } + var t2 = (-b - Math.sqrt(b2ac)) / (2 * a); + if (0 < t2 && t2 < 1) { + if (i === 0) { this.addX(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + if (i === 1) { this.addY(derive(p0[i], p1[i], p2[i], p3[i], t2)); } + } + } +}; + +/** + * Add a quadratic curve to the bounding box. + * This extends the bounding box to include the entire quadratic curve. + * @param {number} x0 - The starting X coordinate. + * @param {number} y0 - The starting Y coordinate. + * @param {number} x1 - The X coordinate of the control point. + * @param {number} y1 - The Y coordinate of the control point. + * @param {number} x - The ending X coordinate. + * @param {number} y - The ending Y coordinate. + */ +BoundingBox.prototype.addQuad = function(x0, y0, x1, y1, x, y) { + var cp1x = x0 + 2 / 3 * (x1 - x0); + var cp1y = y0 + 2 / 3 * (y1 - y0); + var cp2x = cp1x + 1 / 3 * (x - x0); + var cp2y = cp1y + 1 / 3 * (y - y0); + this.addBezier(x0, y0, cp1x, cp1y, cp2x, cp2y, x, y); +}; + +// Geometric objects + +/** + * A bézier path containing a set of path commands similar to a SVG path. + * Paths can be drawn on a context using `draw`. + * @exports opentype.Path + * @class + * @constructor + */ +function Path() { + this.commands = []; + this.fill = 'black'; + this.stroke = null; + this.strokeWidth = 1; +} + +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.moveTo = function(x, y) { + this.commands.push({ + type: 'M', + x: x, + y: y + }); +}; + +/** + * @param {number} x + * @param {number} y + */ +Path.prototype.lineTo = function(x, y) { + this.commands.push({ + type: 'L', + x: x, + y: y + }); +}; + +/** + * Draws cubic curve + * @function + * curveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + +/** + * Draws cubic curve + * @function + * bezierCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control 1 + * @param {number} y1 - y of control 1 + * @param {number} x2 - x of control 2 + * @param {number} y2 - y of control 2 + * @param {number} x - x of path point + * @param {number} y - y of path point + * @see curveTo + */ +Path.prototype.curveTo = Path.prototype.bezierCurveTo = function(x1, y1, x2, y2, x, y) { + this.commands.push({ + type: 'C', + x1: x1, + y1: y1, + x2: x2, + y2: y2, + x: x, + y: y + }); +}; + +/** + * Draws quadratic curve + * @function + * quadraticCurveTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ + +/** + * Draws quadratic curve + * @function + * quadTo + * @memberof opentype.Path.prototype + * @param {number} x1 - x of control + * @param {number} y1 - y of control + * @param {number} x - x of path point + * @param {number} y - y of path point + */ +Path.prototype.quadTo = Path.prototype.quadraticCurveTo = function(x1, y1, x, y) { + this.commands.push({ + type: 'Q', + x1: x1, + y1: y1, + x: x, + y: y + }); +}; + +/** + * Closes the path + * @function closePath + * @memberof opentype.Path.prototype + */ + +/** + * Close the path + * @function close + * @memberof opentype.Path.prototype + */ +Path.prototype.close = Path.prototype.closePath = function() { + this.commands.push({ + type: 'Z' + }); +}; + +/** + * Add the given path or list of commands to the commands of this path. + * @param {Array} pathOrCommands - another opentype.Path, an opentype.BoundingBox, or an array of commands. + */ +Path.prototype.extend = function(pathOrCommands) { + if (pathOrCommands.commands) { + pathOrCommands = pathOrCommands.commands; + } else if (pathOrCommands instanceof BoundingBox) { + var box = pathOrCommands; + this.moveTo(box.x1, box.y1); + this.lineTo(box.x2, box.y1); + this.lineTo(box.x2, box.y2); + this.lineTo(box.x1, box.y2); + this.close(); + return; + } + + Array.prototype.push.apply(this.commands, pathOrCommands); +}; + +/** + * Calculate the bounding box of the path. + * @returns {opentype.BoundingBox} + */ +Path.prototype.getBoundingBox = function() { + var box = new BoundingBox(); + + var startX = 0; + var startY = 0; + var prevX = 0; + var prevY = 0; + for (var i = 0; i < this.commands.length; i++) { + var cmd = this.commands[i]; + switch (cmd.type) { + case 'M': + box.addPoint(cmd.x, cmd.y); + startX = prevX = cmd.x; + startY = prevY = cmd.y; + break; + case 'L': + box.addPoint(cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Q': + box.addQuad(prevX, prevY, cmd.x1, cmd.y1, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'C': + box.addBezier(prevX, prevY, cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + prevX = cmd.x; + prevY = cmd.y; + break; + case 'Z': + prevX = startX; + prevY = startY; + break; + default: + throw new Error('Unexpected path command ' + cmd.type); + } + } + if (box.isEmpty()) { + box.addPoint(0, 0); + } + return box; +}; + +/** + * Draw the path to a 2D context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context. + */ +Path.prototype.draw = function(ctx) { + ctx.beginPath(); + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + ctx.moveTo(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + ctx.lineTo(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + ctx.bezierCurveTo(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + ctx.quadraticCurveTo(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + ctx.closePath(); + } + } + + if (this.fill) { + ctx.fillStyle = this.fill; + ctx.fill(); + } + + if (this.stroke) { + ctx.strokeStyle = this.stroke; + ctx.lineWidth = this.strokeWidth; + ctx.stroke(); + } +}; + +/** + * Convert the Path to a string of path data instructions + * See http://www.w3.org/TR/SVG/paths.html#PathData + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toPathData = function(decimalPlaces) { + decimalPlaces = decimalPlaces !== undefined ? decimalPlaces : 2; + + function floatToString(v) { + if (Math.round(v) === v) { + return '' + Math.round(v); + } else { + return v.toFixed(decimalPlaces); + } + } + + function packValues() { + var arguments$1 = arguments; + + var s = ''; + for (var i = 0; i < arguments.length; i += 1) { + var v = arguments$1[i]; + if (v >= 0 && i > 0) { + s += ' '; + } + + s += floatToString(v); + } + + return s; + } + + var d = ''; + for (var i = 0; i < this.commands.length; i += 1) { + var cmd = this.commands[i]; + if (cmd.type === 'M') { + d += 'M' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'L') { + d += 'L' + packValues(cmd.x, cmd.y); + } else if (cmd.type === 'C') { + d += 'C' + packValues(cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y); + } else if (cmd.type === 'Q') { + d += 'Q' + packValues(cmd.x1, cmd.y1, cmd.x, cmd.y); + } else if (cmd.type === 'Z') { + d += 'Z'; + } + } + + return d; +}; + +/** + * Convert the path to an SVG element, as a string. + * @param {number} [decimalPlaces=2] - The amount of decimal places for floating-point values + * @return {string} + */ +Path.prototype.toSVG = function(decimalPlaces) { + var svg = '= 0 && v <= 255, 'Byte value should be between 0 and 255.'); + return [v]; +}; +/** + * @constant + * @type {number} + */ +sizeOf.BYTE = constant(1); + +/** + * Convert a 8-bit signed integer to a list of 1 byte. + * @param {string} + * @returns {Array} + */ +encode.CHAR = function(v) { + return [v.charCodeAt(0)]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.CHAR = constant(1); + +/** + * Convert an ASCII string to a list of bytes. + * @param {string} + * @returns {Array} + */ +encode.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + v = ''; + console.warn('Undefined CHARARRAY encountered and treated as an empty string. This is probably caused by a missing glyph name.'); + } + var b = []; + for (var i = 0; i < v.length; i += 1) { + b[i] = v.charCodeAt(i); + } + + return b; +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARARRAY = function(v) { + if (typeof v === 'undefined') { + return 0; + } + return v.length; +}; + +/** + * Convert a 16-bit unsigned integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.USHORT = function(v) { + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.USHORT = constant(2); + +/** + * Convert a 16-bit signed integer to a list of 2 bytes. + * @param {number} + * @returns {Array} + */ +encode.SHORT = function(v) { + // Two's complement + if (v >= LIMIT16) { + v = -(2 * LIMIT16 - v); + } + + return [(v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.SHORT = constant(2); + +/** + * Convert a 24-bit unsigned integer to a list of 3 bytes. + * @param {number} + * @returns {Array} + */ +encode.UINT24 = function(v) { + return [(v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.UINT24 = constant(3); + +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.ULONG = function(v) { + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.ULONG = constant(4); + +/** + * Convert a 32-bit unsigned integer to a list of 4 bytes. + * @param {number} + * @returns {Array} + */ +encode.LONG = function(v) { + // Two's complement + if (v >= LIMIT32) { + v = -(2 * LIMIT32 - v); + } + + return [(v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.LONG = constant(4); + +encode.FIXED = encode.ULONG; +sizeOf.FIXED = sizeOf.ULONG; + +encode.FWORD = encode.SHORT; +sizeOf.FWORD = sizeOf.SHORT; + +encode.UFWORD = encode.USHORT; +sizeOf.UFWORD = sizeOf.USHORT; + +/** + * Convert a 32-bit Apple Mac timestamp integer to a list of 8 bytes, 64-bit timestamp. + * @param {number} + * @returns {Array} + */ +encode.LONGDATETIME = function(v) { + return [0, 0, 0, 0, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.LONGDATETIME = constant(8); + +/** + * Convert a 4-char tag to a list of 4 bytes. + * @param {string} + * @returns {Array} + */ +encode.TAG = function(v) { + check.argument(v.length === 4, 'Tag should be exactly 4 ASCII characters.'); + return [v.charCodeAt(0), + v.charCodeAt(1), + v.charCodeAt(2), + v.charCodeAt(3)]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.TAG = constant(4); + +// CFF data types /////////////////////////////////////////////////////////// + +encode.Card8 = encode.BYTE; +sizeOf.Card8 = sizeOf.BYTE; + +encode.Card16 = encode.USHORT; +sizeOf.Card16 = sizeOf.USHORT; + +encode.OffSize = encode.BYTE; +sizeOf.OffSize = sizeOf.BYTE; + +encode.SID = encode.USHORT; +sizeOf.SID = sizeOf.USHORT; + +// Convert a numeric operand or charstring number to a variable-size list of bytes. +/** + * Convert a numeric operand or charstring number to a variable-size list of bytes. + * @param {number} + * @returns {Array} + */ +encode.NUMBER = function(v) { + if (v >= -107 && v <= 107) { + return [v + 139]; + } else if (v >= 108 && v <= 1131) { + v = v - 108; + return [(v >> 8) + 247, v & 0xFF]; + } else if (v >= -1131 && v <= -108) { + v = -v - 108; + return [(v >> 8) + 251, v & 0xFF]; + } else if (v >= -32768 && v <= 32767) { + return encode.NUMBER16(v); + } else { + return encode.NUMBER32(v); + } +}; + +/** + * @param {number} + * @returns {number} + */ +sizeOf.NUMBER = function(v) { + return encode.NUMBER(v).length; +}; + +/** + * Convert a signed number between -32768 and +32767 to a three-byte value. + * This ensures we always use three bytes, but is not the most compact format. + * @param {number} + * @returns {Array} + */ +encode.NUMBER16 = function(v) { + return [28, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER16 = constant(3); + +/** + * Convert a signed number between -(2^31) and +(2^31-1) to a five-byte value. + * This is useful if you want to be sure you always use four bytes, + * at the expense of wasting a few bytes for smaller numbers. + * @param {number} + * @returns {Array} + */ +encode.NUMBER32 = function(v) { + return [29, (v >> 24) & 0xFF, (v >> 16) & 0xFF, (v >> 8) & 0xFF, v & 0xFF]; +}; + +/** + * @constant + * @type {number} + */ +sizeOf.NUMBER32 = constant(5); + +/** + * @param {number} + * @returns {Array} + */ +encode.REAL = function(v) { + var value = v.toString(); + + // Some numbers use an epsilon to encode the value. (e.g. JavaScript will store 0.0000001 as 1e-7) + // This code converts it back to a number without the epsilon. + var m = /\.(\d*?)(?:9{5,20}|0{5,20})\d{0,2}(?:e(.+)|$)/.exec(value); + if (m) { + var epsilon = parseFloat('1e' + ((m[2] ? +m[2] : 0) + m[1].length)); + value = (Math.round(v * epsilon) / epsilon).toString(); + } + + var nibbles = ''; + for (var i = 0, ii = value.length; i < ii; i += 1) { + var c = value[i]; + if (c === 'e') { + nibbles += value[++i] === '-' ? 'c' : 'b'; + } else if (c === '.') { + nibbles += 'a'; + } else if (c === '-') { + nibbles += 'e'; + } else { + nibbles += c; + } + } + + nibbles += (nibbles.length & 1) ? 'f' : 'ff'; + var out = [30]; + for (var i$1 = 0, ii$1 = nibbles.length; i$1 < ii$1; i$1 += 2) { + out.push(parseInt(nibbles.substr(i$1, 2), 16)); + } + + return out; +}; + +/** + * @param {number} + * @returns {number} + */ +sizeOf.REAL = function(v) { + return encode.REAL(v).length; +}; + +encode.NAME = encode.CHARARRAY; +sizeOf.NAME = sizeOf.CHARARRAY; + +encode.STRING = encode.CHARARRAY; +sizeOf.STRING = sizeOf.CHARARRAY; + +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF8 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes; + for (var j = 0; j < numChars; j++, offset += 1) { + codePoints[j] = data.getUint8(offset); + } + + return String.fromCharCode.apply(null, codePoints); +}; + +/** + * @param {DataView} data + * @param {number} offset + * @param {number} numBytes + * @returns {string} + */ +decode.UTF16 = function(data, offset, numBytes) { + var codePoints = []; + var numChars = numBytes / 2; + for (var j = 0; j < numChars; j++, offset += 2) { + codePoints[j] = data.getUint16(offset); + } + + return String.fromCharCode.apply(null, codePoints); +}; + +/** + * Convert a JavaScript string to UTF16-BE. + * @param {string} + * @returns {Array} + */ +encode.UTF16 = function(v) { + var b = []; + for (var i = 0; i < v.length; i += 1) { + var codepoint = v.charCodeAt(i); + b[b.length] = (codepoint >> 8) & 0xFF; + b[b.length] = codepoint & 0xFF; + } + + return b; +}; + +/** + * @param {string} + * @returns {number} + */ +sizeOf.UTF16 = function(v) { + return v.length * 2; +}; + +// Data for converting old eight-bit Macintosh encodings to Unicode. +// This representation is optimized for decoding; encoding is slower +// and needs more memory. The assumption is that all opentype.js users +// want to open fonts, but saving a font will be comparatively rare +// so it can be more expensive. Keyed by IANA character set name. +// +// Python script for generating these strings: +// +// s = u''.join([chr(c).decode('mac_greek') for c in range(128, 256)]) +// print(s.encode('utf-8')) +/** + * @private + */ +var eightBitMacEncodings = { + 'x-mac-croatian': // Python: 'mac_croatian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®Š™´¨≠ŽØ∞±≤≥∆µ∂∑∏š∫ªºΩžø' + + '¿¡¬√ƒ≈ƫȅ ÀÃÕŒœĐ—“”‘’÷◊©⁄€‹›Æ»–·‚„‰ÂćÁčÈÍÎÏÌÓÔđÒÚÛÙıˆ˜¯πË˚¸Êæˇ', + 'x-mac-cyrillic': // Python: 'mac_cyrillic' + 'АБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ†°Ґ£§•¶І®©™Ђђ≠Ѓѓ∞±≤≥іµґЈЄєЇїЉљЊњ' + + 'јЅ¬√ƒ≈∆«»… ЋћЌќѕ–—“”‘’÷„ЎўЏџ№Ёёяабвгдежзийклмнопрстуфхцчшщъыьэю', + 'x-mac-gaelic': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/GAELIC.TXT + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØḂ±≤≥ḃĊċḊḋḞḟĠġṀæø' + + 'ṁṖṗɼƒſṠ«»… ÀÃÕŒœ–—“”‘’ṡẛÿŸṪ€‹›Ŷŷṫ·Ỳỳ⁊ÂÊÁËÈÍÎÏÌÓÔ♣ÒÚÛÙıÝýŴŵẄẅẀẁẂẃ', + 'x-mac-greek': // Python: 'mac_greek' + 'Ĺ²É³ÖÜ΅àâä΄¨çéèê룙î‰ôö¦€ùûü†ΓΔΘΛΞΠß®©ΣΪ§≠°·Α±≤≥¥ΒΕΖΗΙΚΜΦΫΨΩ' + + 'άΝ¬ΟΡ≈Τ«»… ΥΧΆΈœ–―“”‘’÷ΉΊΌΎέήίόΏύαβψδεφγηιξκλμνοπώρστθωςχυζϊϋΐΰ\u00AD', + 'x-mac-icelandic': // Python: 'mac_iceland' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûüݰ¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€ÐðÞþý·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-inuit': // http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/INUIT.TXT + 'ᐃᐄᐅᐆᐊᐋᐱᐲᐳᐴᐸᐹᑉᑎᑏᑐᑑᑕᑖᑦᑭᑮᑯᑰᑲᑳᒃᒋᒌᒍᒎᒐᒑ°ᒡᒥᒦ•¶ᒧ®©™ᒨᒪᒫᒻᓂᓃᓄᓅᓇᓈᓐᓯᓰᓱᓲᓴᓵᔅᓕᓖᓗ' + + 'ᓘᓚᓛᓪᔨᔩᔪᔫᔭ… ᔮᔾᕕᕖᕗ–—“”‘’ᕘᕙᕚᕝᕆᕇᕈᕉᕋᕌᕐᕿᖀᖁᖂᖃᖄᖅᖏᖐᖑᖒᖓᖔᖕᙱᙲᙳᙴᙵᙶᖖᖠᖡᖢᖣᖤᖥᖦᕼŁł', + 'x-mac-ce': // Python: 'mac_latin2' + 'ÄĀāÉĄÖÜáąČäčĆć鏟ĎíďĒēĖóėôöõúĚěü†°Ę£§•¶ß®©™ę¨≠ģĮįĪ≤≥īĶ∂∑łĻļĽľĹĺŅ' + + 'ņѬ√ńŇ∆«»… ňŐÕőŌ–—“”‘’÷◊ōŔŕŘ‹›řŖŗŠ‚„šŚśÁŤťÍŽžŪÓÔūŮÚůŰűŲųÝýķŻŁżĢˇ', + macintosh: // Python: 'mac_roman' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›fifl‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-romanian': // Python: 'mac_romanian' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ĂȘ∞±≤≥¥µ∂∑∏π∫ªºΩăș' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸ⁄€‹›Țț‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙıˆ˜¯˘˙˚¸˝˛ˇ', + 'x-mac-turkish': // Python: 'mac_turkish' + 'ÄÅÇÉÑÖÜáàâäãåçéèêëíìîïñóòôöõúùûü†°¢£§•¶ß®©™´¨≠ÆØ∞±≤≥¥µ∂∑∏π∫ªºΩæø' + + '¿¡¬√ƒ≈∆«»… ÀÃÕŒœ–—“”‘’÷◊ÿŸĞğİıŞş‡·‚„‰ÂÊÁËÈÍÎÏÌÓÔÒÚÛÙˆ˜¯˘˙˚¸˝˛ˇ' +}; + +/** + * Decodes an old-style Macintosh string. Returns either a Unicode JavaScript + * string, or 'undefined' if the encoding is unsupported. For example, we do + * not support Chinese, Japanese or Korean because these would need large + * mapping tables. + * @param {DataView} dataView + * @param {number} offset + * @param {number} dataLength + * @param {string} encoding + * @returns {string} + */ +decode.MACSTRING = function(dataView, offset, dataLength, encoding) { + var table = eightBitMacEncodings[encoding]; + if (table === undefined) { + return undefined; + } + + var result = ''; + for (var i = 0; i < dataLength; i++) { + var c = dataView.getUint8(offset + i); + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c <= 0x7F) { + result += String.fromCharCode(c); + } else { + result += table[c & 0x7F]; + } + } + + return result; +}; + +// Helper function for encode.MACSTRING. Returns a dictionary for mapping +// Unicode character codes to their 8-bit MacOS equivalent. This table +// is not exactly a super cheap data structure, but we do not care because +// encoding Macintosh strings is only rarely needed in typical applications. +var macEncodingTableCache = typeof WeakMap === 'function' && new WeakMap(); +var macEncodingCacheKeys; +var getMacEncodingTable = function (encoding) { + // Since we use encoding as a cache key for WeakMap, it has to be + // a String object and not a literal. And at least on NodeJS 2.10.1, + // WeakMap requires that the same String instance is passed for cache hits. + if (!macEncodingCacheKeys) { + macEncodingCacheKeys = {}; + for (var e in eightBitMacEncodings) { + /*jshint -W053 */ // Suppress "Do not use String as a constructor." + macEncodingCacheKeys[e] = new String(e); + } + } + + var cacheKey = macEncodingCacheKeys[encoding]; + if (cacheKey === undefined) { + return undefined; + } + + // We can't do "if (cache.has(key)) {return cache.get(key)}" here: + // since garbage collection may run at any time, it could also kick in + // between the calls to cache.has() and cache.get(). In that case, + // we would return 'undefined' even though we do support the encoding. + if (macEncodingTableCache) { + var cachedTable = macEncodingTableCache.get(cacheKey); + if (cachedTable !== undefined) { + return cachedTable; + } + } + + var decodingTable = eightBitMacEncodings[encoding]; + if (decodingTable === undefined) { + return undefined; + } + + var encodingTable = {}; + for (var i = 0; i < decodingTable.length; i++) { + encodingTable[decodingTable.charCodeAt(i)] = i + 0x80; + } + + if (macEncodingTableCache) { + macEncodingTableCache.set(cacheKey, encodingTable); + } + + return encodingTable; +}; + +/** + * Encodes an old-style Macintosh string. Returns a byte array upon success. + * If the requested encoding is unsupported, or if the input string contains + * a character that cannot be expressed in the encoding, the function returns + * 'undefined'. + * @param {string} str + * @param {string} encoding + * @returns {Array} + */ +encode.MACSTRING = function(str, encoding) { + var table = getMacEncodingTable(encoding); + if (table === undefined) { + return undefined; + } + + var result = []; + for (var i = 0; i < str.length; i++) { + var c = str.charCodeAt(i); + + // In all eight-bit Mac encodings, the characters 0x00..0x7F are + // mapped to U+0000..U+007F; we only need to look up the others. + if (c >= 0x80) { + c = table[c]; + if (c === undefined) { + // str contains a Unicode character that cannot be encoded + // in the requested encoding. + return undefined; + } + } + result[i] = c; + // result.push(c); + } + + return result; +}; + +/** + * @param {string} str + * @param {string} encoding + * @returns {number} + */ +sizeOf.MACSTRING = function(str, encoding) { + var b = encode.MACSTRING(str, encoding); + if (b !== undefined) { + return b.length; + } else { + return 0; + } +}; + +// Helper for encode.VARDELTAS +function isByteEncodable(value) { + return value >= -128 && value <= 127; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsZeroes(deltas, pos, result) { + var runLength = 0; + var numDeltas = deltas.length; + while (pos < numDeltas && runLength < 64 && deltas[pos] === 0) { + ++pos; + ++runLength; + } + result.push(0x80 | (runLength - 1)); + return pos; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsBytes(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + if (!isByteEncodable(value)) { + break; + } + + // Within a byte-encoded run of deltas, a single zero is best + // stored literally as 0x00 value. However, if we have two or + // more zeroes in a sequence, it is better to start a new run. + // Fore example, the sequence of deltas [15, 15, 0, 15, 15] + // becomes 6 bytes (04 0F 0F 00 0F 0F) when storing the zero + // within the current run, but 7 bytes (01 0F 0F 80 01 0F 0F) + // when starting a new run. + if (value === 0 && pos + 1 < numDeltas && deltas[pos + 1] === 0) { + break; + } + + ++pos; + ++runLength; + } + result.push(runLength - 1); + for (var i = offset; i < pos; ++i) { + result.push((deltas[i] + 256) & 0xff); + } + return pos; +} + +// Helper for encode.VARDELTAS +function encodeVarDeltaRunAsWords(deltas, offset, result) { + var runLength = 0; + var numDeltas = deltas.length; + var pos = offset; + while (pos < numDeltas && runLength < 64) { + var value = deltas[pos]; + + // Within a word-encoded run of deltas, it is easiest to start + // a new run (with a different encoding) whenever we encounter + // a zero value. For example, the sequence [0x6666, 0, 0x7777] + // needs 7 bytes when storing the zero inside the current run + // (42 66 66 00 00 77 77), and equally 7 bytes when starting a + // new run (40 66 66 80 40 77 77). + if (value === 0) { + break; + } + + // Within a word-encoded run of deltas, a single value in the + // range (-128..127) should be encoded within the current run + // because it is more compact. For example, the sequence + // [0x6666, 2, 0x7777] becomes 7 bytes when storing the value + // literally (42 66 66 00 02 77 77), but 8 bytes when starting + // a new run (40 66 66 00 02 40 77 77). + if (isByteEncodable(value) && pos + 1 < numDeltas && isByteEncodable(deltas[pos + 1])) { + break; + } + + ++pos; + ++runLength; + } + result.push(0x40 | (runLength - 1)); + for (var i = offset; i < pos; ++i) { + var val = deltas[i]; + result.push(((val + 0x10000) >> 8) & 0xff, (val + 0x100) & 0xff); + } + return pos; +} + +/** + * Encode a list of variation adjustment deltas. + * + * Variation adjustment deltas are used in ‘gvar’ and ‘cvar’ tables. + * They indicate how points (in ‘gvar’) or values (in ‘cvar’) get adjusted + * when generating instances of variation fonts. + * + * @see https://www.microsoft.com/typography/otspec/gvar.htm + * @see https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html + * @param {Array} + * @return {Array} + */ +encode.VARDELTAS = function(deltas) { + var pos = 0; + var result = []; + while (pos < deltas.length) { + var value = deltas[pos]; + if (value === 0) { + pos = encodeVarDeltaRunAsZeroes(deltas, pos, result); + } else if (value >= -128 && value <= 127) { + pos = encodeVarDeltaRunAsBytes(deltas, pos, result); + } else { + pos = encodeVarDeltaRunAsWords(deltas, pos, result); + } + } + return result; +}; + +// Convert a list of values to a CFF INDEX structure. +// The values should be objects containing name / type / value. +/** + * @param {Array} l + * @returns {Array} + */ +encode.INDEX = function(l) { + //var offset, offsets, offsetEncoder, encodedOffsets, encodedOffset, data, + // i, v; + // Because we have to know which data type to use to encode the offsets, + // we have to go through the values twice: once to encode the data and + // calculate the offsets, then again to encode the offsets using the fitting data type. + var offset = 1; // First offset is always 1. + var offsets = [offset]; + var data = []; + for (var i = 0; i < l.length; i += 1) { + var v = encode.OBJECT(l[i]); + Array.prototype.push.apply(data, v); + offset += v.length; + offsets.push(offset); + } + + if (data.length === 0) { + return [0, 0]; + } + + var encodedOffsets = []; + var offSize = (1 + Math.floor(Math.log(offset) / Math.log(2)) / 8) | 0; + var offsetEncoder = [undefined, encode.BYTE, encode.USHORT, encode.UINT24, encode.ULONG][offSize]; + for (var i$1 = 0; i$1 < offsets.length; i$1 += 1) { + var encodedOffset = offsetEncoder(offsets[i$1]); + Array.prototype.push.apply(encodedOffsets, encodedOffset); + } + + return Array.prototype.concat(encode.Card16(l.length), + encode.OffSize(offSize), + encodedOffsets, + data); +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.INDEX = function(v) { + return encode.INDEX(v).length; +}; + +/** + * Convert an object to a CFF DICT structure. + * The keys should be numeric. + * The values should be objects containing name / type / value. + * @param {Object} m + * @returns {Array} + */ +encode.DICT = function(m) { + var d = []; + var keys = Object.keys(m); + var length = keys.length; + + for (var i = 0; i < length; i += 1) { + // Object.keys() return string keys, but our keys are always numeric. + var k = parseInt(keys[i], 0); + var v = m[k]; + // Value comes before the key. + d = d.concat(encode.OPERAND(v.value, v.type)); + d = d.concat(encode.OPERATOR(k)); + } + + return d; +}; + +/** + * @param {Object} + * @returns {number} + */ +sizeOf.DICT = function(m) { + return encode.DICT(m).length; +}; + +/** + * @param {number} + * @returns {Array} + */ +encode.OPERATOR = function(v) { + if (v < 1200) { + return [v]; + } else { + return [12, v - 1200]; + } +}; + +/** + * @param {Array} v + * @param {string} + * @returns {Array} + */ +encode.OPERAND = function(v, type) { + var d = []; + if (Array.isArray(type)) { + for (var i = 0; i < type.length; i += 1) { + check.argument(v.length === type.length, 'Not enough arguments given for type' + type); + d = d.concat(encode.OPERAND(v[i], type[i])); + } + } else { + if (type === 'SID') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'offset') { + // We make it easy for ourselves and always encode offsets as + // 4 bytes. This makes offset calculation for the top dict easier. + d = d.concat(encode.NUMBER32(v)); + } else if (type === 'number') { + d = d.concat(encode.NUMBER(v)); + } else if (type === 'real') { + d = d.concat(encode.REAL(v)); + } else { + throw new Error('Unknown operand type ' + type); + // FIXME Add support for booleans + } + } + + return d; +}; + +encode.OP = encode.BYTE; +sizeOf.OP = sizeOf.BYTE; + +// memoize charstring encoding using WeakMap if available +var wmm = typeof WeakMap === 'function' && new WeakMap(); + +/** + * Convert a list of CharString operations to bytes. + * @param {Array} + * @returns {Array} + */ +encode.CHARSTRING = function(ops) { + // See encode.MACSTRING for why we don't do "if (wmm && wmm.has(ops))". + if (wmm) { + var cachedValue = wmm.get(ops); + if (cachedValue !== undefined) { + return cachedValue; + } + } + + var d = []; + var length = ops.length; + + for (var i = 0; i < length; i += 1) { + var op = ops[i]; + d = d.concat(encode[op.type](op.value)); + } + + if (wmm) { + wmm.set(ops, d); + } + + return d; +}; + +/** + * @param {Array} + * @returns {number} + */ +sizeOf.CHARSTRING = function(ops) { + return encode.CHARSTRING(ops).length; +}; + +// Utility functions //////////////////////////////////////////////////////// + +/** + * Convert an object containing name / type / value to bytes. + * @param {Object} + * @returns {Array} + */ +encode.OBJECT = function(v) { + var encodingFunction = encode[v.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for type ' + v.type); + return encodingFunction(v.value); +}; + +/** + * @param {Object} + * @returns {number} + */ +sizeOf.OBJECT = function(v) { + var sizeOfFunction = sizeOf[v.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for type ' + v.type); + return sizeOfFunction(v.value); +}; + +/** + * Convert a table object to bytes. + * A table contains a list of fields containing the metadata (name, type and default value). + * The table itself has the field values set as attributes. + * @param {opentype.Table} + * @returns {Array} + */ +encode.TABLE = function(table) { + var d = []; + var length = table.fields.length; + var subtables = []; + var subtableOffsets = []; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var encodingFunction = encode[field.type]; + check.argument(encodingFunction !== undefined, 'No encoding function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + var bytes = encodingFunction(value); + + if (field.type === 'TABLE') { + subtableOffsets.push(d.length); + d = d.concat([0, 0]); + subtables.push(bytes); + } else { + d = d.concat(bytes); + } + } + + for (var i$1 = 0; i$1 < subtables.length; i$1 += 1) { + var o = subtableOffsets[i$1]; + var offset = d.length; + check.argument(offset < 65536, 'Table ' + table.tableName + ' too big.'); + d[o] = offset >> 8; + d[o + 1] = offset & 0xff; + d = d.concat(subtables[i$1]); + } + + return d; +}; + +/** + * @param {opentype.Table} + * @returns {number} + */ +sizeOf.TABLE = function(table) { + var numBytes = 0; + var length = table.fields.length; + + for (var i = 0; i < length; i += 1) { + var field = table.fields[i]; + var sizeOfFunction = sizeOf[field.type]; + check.argument(sizeOfFunction !== undefined, 'No sizeOf function for field type ' + field.type + ' (' + field.name + ')'); + var value = table[field.name]; + if (value === undefined) { + value = field.value; + } + + numBytes += sizeOfFunction(value); + + // Subtables take 2 more bytes for offsets. + if (field.type === 'TABLE') { + numBytes += 2; + } + } + + return numBytes; +}; + +encode.RECORD = encode.TABLE; +sizeOf.RECORD = sizeOf.TABLE; + +// Merge in a list of bytes. +encode.LITERAL = function(v) { + return v; +}; + +sizeOf.LITERAL = function(v) { + return v.length; +}; + +// Table metadata + +/** + * @exports opentype.Table + * @class + * @param {string} tableName + * @param {Array} fields + * @param {Object} options + * @constructor + */ +function Table(tableName, fields, options) { + // For coverage tables with coverage format 2, we do not want to add the coverage data directly to the table object, + // as this will result in wrong encoding order of the coverage data on serialization to bytes. + // The fallback of using the field values directly when not present on the table is handled in types.encode.TABLE() already. + if (fields.length && (fields[0].name !== 'coverageFormat' || fields[0].value === 1)) { + for (var i = 0; i < fields.length; i += 1) { + var field = fields[i]; + this[field.name] = field.value; + } + } + + this.tableName = tableName; + this.fields = fields; + if (options) { + var optionKeys = Object.keys(options); + for (var i$1 = 0; i$1 < optionKeys.length; i$1 += 1) { + var k = optionKeys[i$1]; + var v = options[k]; + if (this[k] !== undefined) { + this[k] = v; + } + } + } +} + +/** + * Encodes the table and returns an array of bytes + * @return {Array} + */ +Table.prototype.encode = function() { + return encode.TABLE(this); +}; + +/** + * Get the size of the table. + * @return {number} + */ +Table.prototype.sizeOf = function() { + return sizeOf.TABLE(this); +}; + +/** + * @private + */ +function ushortList(itemName, list, count) { + if (count === undefined) { + count = list.length; + } + var fields = new Array(list.length + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < list.length; i++) { + fields[i + 1] = {name: itemName + i, type: 'USHORT', value: list[i]}; + } + return fields; +} + +/** + * @private + */ +function tableList(itemName, records, itemCallback) { + var count = records.length; + var fields = new Array(count + 1); + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields[i + 1] = {name: itemName + i, type: 'TABLE', value: itemCallback(records[i], i)}; + } + return fields; +} + +/** + * @private + */ +function recordList(itemName, records, itemCallback) { + var count = records.length; + var fields = []; + fields[0] = {name: itemName + 'Count', type: 'USHORT', value: count}; + for (var i = 0; i < count; i++) { + fields = fields.concat(itemCallback(records[i], i)); + } + return fields; +} + +// Common Layout Tables + +/** + * @exports opentype.Coverage + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function Coverage(coverageTable) { + if (coverageTable.format === 1) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 1}] + .concat(ushortList('glyph', coverageTable.glyphs)) + ); + } else if (coverageTable.format === 2) { + Table.call(this, 'coverageTable', + [{name: 'coverageFormat', type: 'USHORT', value: 2}] + .concat(recordList('rangeRecord', coverageTable.ranges, function(RangeRecord) { + return [ + {name: 'startGlyphID', type: 'USHORT', value: RangeRecord.start}, + {name: 'endGlyphID', type: 'USHORT', value: RangeRecord.end}, + {name: 'startCoverageIndex', type: 'USHORT', value: RangeRecord.index} ]; + })) + ); + } else { + check.assert(false, 'Coverage format must be 1 or 2.'); + } +} +Coverage.prototype = Object.create(Table.prototype); +Coverage.prototype.constructor = Coverage; + +function ScriptList(scriptListTable) { + Table.call(this, 'scriptListTable', + recordList('scriptRecord', scriptListTable, function(scriptRecord, i) { + var script = scriptRecord.script; + var defaultLangSys = script.defaultLangSys; + check.assert(!!defaultLangSys, 'Unable to write GSUB: script ' + scriptRecord.tag + ' has no default language system.'); + return [ + {name: 'scriptTag' + i, type: 'TAG', value: scriptRecord.tag}, + {name: 'script' + i, type: 'TABLE', value: new Table('scriptTable', [ + {name: 'defaultLangSys', type: 'TABLE', value: new Table('defaultLangSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: defaultLangSys.reqFeatureIndex}] + .concat(ushortList('featureIndex', defaultLangSys.featureIndexes)))} + ].concat(recordList('langSys', script.langSysRecords, function(langSysRecord, i) { + var langSys = langSysRecord.langSys; + return [ + {name: 'langSysTag' + i, type: 'TAG', value: langSysRecord.tag}, + {name: 'langSys' + i, type: 'TABLE', value: new Table('langSys', [ + {name: 'lookupOrder', type: 'USHORT', value: 0}, + {name: 'reqFeatureIndex', type: 'USHORT', value: langSys.reqFeatureIndex} + ].concat(ushortList('featureIndex', langSys.featureIndexes)))} + ]; + })))} + ]; + }) + ); +} +ScriptList.prototype = Object.create(Table.prototype); +ScriptList.prototype.constructor = ScriptList; + +/** + * @exports opentype.FeatureList + * @class + * @param {opentype.Table} + * @constructor + * @extends opentype.Table + */ +function FeatureList(featureListTable) { + Table.call(this, 'featureListTable', + recordList('featureRecord', featureListTable, function(featureRecord, i) { + var feature = featureRecord.feature; + return [ + {name: 'featureTag' + i, type: 'TAG', value: featureRecord.tag}, + {name: 'feature' + i, type: 'TABLE', value: new Table('featureTable', [ + {name: 'featureParams', type: 'USHORT', value: feature.featureParams} ].concat(ushortList('lookupListIndex', feature.lookupListIndexes)))} + ]; + }) + ); +} +FeatureList.prototype = Object.create(Table.prototype); +FeatureList.prototype.constructor = FeatureList; + +/** + * @exports opentype.LookupList + * @class + * @param {opentype.Table} + * @param {Object} + * @constructor + * @extends opentype.Table + */ +function LookupList(lookupListTable, subtableMakers) { + Table.call(this, 'lookupListTable', tableList('lookup', lookupListTable, function(lookupTable) { + var subtableCallback = subtableMakers[lookupTable.lookupType]; + check.assert(!!subtableCallback, 'Unable to write GSUB lookup type ' + lookupTable.lookupType + ' tables.'); + return new Table('lookupTable', [ + {name: 'lookupType', type: 'USHORT', value: lookupTable.lookupType}, + {name: 'lookupFlag', type: 'USHORT', value: lookupTable.lookupFlag} + ].concat(tableList('subtable', lookupTable.subtables, subtableCallback))); + })); +} +LookupList.prototype = Object.create(Table.prototype); +LookupList.prototype.constructor = LookupList; + +// Record = same as Table, but inlined (a Table has an offset and its data is further in the stream) +// Don't use offsets inside Records (probable bug), only in Tables. +var table = { + Table: Table, + Record: Table, + Coverage: Coverage, + ScriptList: ScriptList, + FeatureList: FeatureList, + LookupList: LookupList, + ushortList: ushortList, + tableList: tableList, + recordList: recordList, +}; + +// Parsing utility functions + +// Retrieve an unsigned byte from the DataView. +function getByte(dataView, offset) { + return dataView.getUint8(offset); +} + +// Retrieve an unsigned 16-bit short from the DataView. +// The value is stored in big endian. +function getUShort(dataView, offset) { + return dataView.getUint16(offset, false); +} + +// Retrieve a signed 16-bit short from the DataView. +// The value is stored in big endian. +function getShort(dataView, offset) { + return dataView.getInt16(offset, false); +} + +// Retrieve an unsigned 32-bit long from the DataView. +// The value is stored in big endian. +function getULong(dataView, offset) { + return dataView.getUint32(offset, false); +} + +// Retrieve a 32-bit signed fixed-point number (16.16) from the DataView. +// The value is stored in big endian. +function getFixed(dataView, offset) { + var decimal = dataView.getInt16(offset, false); + var fraction = dataView.getUint16(offset + 2, false); + return decimal + fraction / 65535; +} + +// Retrieve a 4-character tag from the DataView. +// Tags are used to identify tables. +function getTag(dataView, offset) { + var tag = ''; + for (var i = offset; i < offset + 4; i += 1) { + tag += String.fromCharCode(dataView.getInt8(i)); + } + + return tag; +} + +// Retrieve an offset from the DataView. +// Offsets are 1 to 4 bytes in length, depending on the offSize argument. +function getOffset(dataView, offset, offSize) { + var v = 0; + for (var i = 0; i < offSize; i += 1) { + v <<= 8; + v += dataView.getUint8(offset + i); + } + + return v; +} + +// Retrieve a number of bytes from start offset to the end offset from the DataView. +function getBytes(dataView, startOffset, endOffset) { + var bytes = []; + for (var i = startOffset; i < endOffset; i += 1) { + bytes.push(dataView.getUint8(i)); + } + + return bytes; +} + +// Convert the list of bytes to a string. +function bytesToString(bytes) { + var s = ''; + for (var i = 0; i < bytes.length; i += 1) { + s += String.fromCharCode(bytes[i]); + } + + return s; +} + +var typeOffsets = { + byte: 1, + uShort: 2, + short: 2, + uLong: 4, + fixed: 4, + longDateTime: 8, + tag: 4 +}; + +// A stateful parser that changes the offset whenever a value is retrieved. +// The data is a DataView. +function Parser(data, offset) { + this.data = data; + this.offset = offset; + this.relativeOffset = 0; +} + +Parser.prototype.parseByte = function() { + var v = this.data.getUint8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseChar = function() { + var v = this.data.getInt8(this.offset + this.relativeOffset); + this.relativeOffset += 1; + return v; +}; + +Parser.prototype.parseCard8 = Parser.prototype.parseByte; + +Parser.prototype.parseUShort = function() { + var v = this.data.getUint16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseCard16 = Parser.prototype.parseUShort; +Parser.prototype.parseSID = Parser.prototype.parseUShort; +Parser.prototype.parseOffset16 = Parser.prototype.parseUShort; + +Parser.prototype.parseShort = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset); + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseF2Dot14 = function() { + var v = this.data.getInt16(this.offset + this.relativeOffset) / 16384; + this.relativeOffset += 2; + return v; +}; + +Parser.prototype.parseULong = function() { + var v = getULong(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseOffset32 = Parser.prototype.parseULong; + +Parser.prototype.parseFixed = function() { + var v = getFixed(this.data, this.offset + this.relativeOffset); + this.relativeOffset += 4; + return v; +}; + +Parser.prototype.parseString = function(length) { + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + var string = ''; + this.relativeOffset += length; + for (var i = 0; i < length; i++) { + string += String.fromCharCode(dataView.getUint8(offset + i)); + } + + return string; +}; + +Parser.prototype.parseTag = function() { + return this.parseString(4); +}; + +// LONGDATETIME is a 64-bit integer. +// JavaScript and unix timestamps traditionally use 32 bits, so we +// only take the last 32 bits. +// + Since until 2038 those bits will be filled by zeros we can ignore them. +Parser.prototype.parseLongDateTime = function() { + var v = getULong(this.data, this.offset + this.relativeOffset + 4); + // Subtract seconds between 01/01/1904 and 01/01/1970 + // to convert Apple Mac timestamp to Standard Unix timestamp + v -= 2082844800; + this.relativeOffset += 8; + return v; +}; + +Parser.prototype.parseVersion = function(minorBase) { + var major = getUShort(this.data, this.offset + this.relativeOffset); + + // How to interpret the minor version is very vague in the spec. 0x5000 is 5, 0x1000 is 1 + // Default returns the correct number if minor = 0xN000 where N is 0-9 + // Set minorBase to 1 for tables that use minor = N where N is 0-9 + var minor = getUShort(this.data, this.offset + this.relativeOffset + 2); + this.relativeOffset += 4; + if (minorBase === undefined) { minorBase = 0x1000; } + return major + minor / minorBase / 10; +}; + +Parser.prototype.skip = function(type, amount) { + if (amount === undefined) { + amount = 1; + } + + this.relativeOffset += typeOffsets[type] * amount; +}; + +///// Parsing lists and records /////////////////////////////// + +// Parse a list of 32 bit unsigned integers. +Parser.prototype.parseULongList = function(count) { + if (count === undefined) { count = this.parseULong(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint32(offset); + offset += 4; + } + + this.relativeOffset += count * 4; + return offsets; +}; + +// Parse a list of 16 bit unsigned integers. The length of the list can be read on the stream +// or provided as an argument. +Parser.prototype.parseOffset16List = +Parser.prototype.parseUShortList = function(count) { + if (count === undefined) { count = this.parseUShort(); } + var offsets = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + offsets[i] = dataView.getUint16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return offsets; +}; + +// Parses a list of 16 bit signed integers. +Parser.prototype.parseShortList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getInt16(offset); + offset += 2; + } + + this.relativeOffset += count * 2; + return list; +}; + +// Parses a list of bytes. +Parser.prototype.parseByteList = function(count) { + var list = new Array(count); + var dataView = this.data; + var offset = this.offset + this.relativeOffset; + for (var i = 0; i < count; i++) { + list[i] = dataView.getUint8(offset++); + } + + this.relativeOffset += count; + return list; +}; + +/** + * Parse a list of items. + * Record count is optional, if omitted it is read from the stream. + * itemCallback is one of the Parser methods. + */ +Parser.prototype.parseList = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseUShort(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; + +Parser.prototype.parseList32 = function(count, itemCallback) { + if (!itemCallback) { + itemCallback = count; + count = this.parseULong(); + } + var list = new Array(count); + for (var i = 0; i < count; i++) { + list[i] = itemCallback.call(this); + } + return list; +}; + +/** + * Parse a list of records. + * Record count is optional, if omitted it is read from the stream. + * Example of recordDescription: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } + */ +Parser.prototype.parseRecordList = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseUShort(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; +}; + +Parser.prototype.parseRecordList32 = function(count, recordDescription) { + // If the count argument is absent, read it in the stream. + if (!recordDescription) { + recordDescription = count; + count = this.parseULong(); + } + var records = new Array(count); + var fields = Object.keys(recordDescription); + for (var i = 0; i < count; i++) { + var rec = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = recordDescription[fieldName]; + rec[fieldName] = fieldType.call(this); + } + records[i] = rec; + } + return records; +}; + +// Parse a data structure into an object +// Example of description: { sequenceIndex: Parser.uShort, lookupListIndex: Parser.uShort } +Parser.prototype.parseStruct = function(description) { + if (typeof description === 'function') { + return description.call(this); + } else { + var fields = Object.keys(description); + var struct = {}; + for (var j = 0; j < fields.length; j++) { + var fieldName = fields[j]; + var fieldType = description[fieldName]; + struct[fieldName] = fieldType.call(this); + } + return struct; + } +}; + +/** + * Parse a GPOS valueRecord + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat is optional, if omitted it is read from the stream. + */ +Parser.prototype.parseValueRecord = function(valueFormat) { + if (valueFormat === undefined) { + valueFormat = this.parseUShort(); + } + if (valueFormat === 0) { + // valueFormat2 in kerning pairs is most often 0 + // in this case return undefined instead of an empty object, to save space + return; + } + var valueRecord = {}; + + if (valueFormat & 0x0001) { valueRecord.xPlacement = this.parseShort(); } + if (valueFormat & 0x0002) { valueRecord.yPlacement = this.parseShort(); } + if (valueFormat & 0x0004) { valueRecord.xAdvance = this.parseShort(); } + if (valueFormat & 0x0008) { valueRecord.yAdvance = this.parseShort(); } + + // Device table (non-variable font) / VariationIndex table (variable font) not supported + // https://docs.microsoft.com/fr-fr/typography/opentype/spec/chapter2#devVarIdxTbls + if (valueFormat & 0x0010) { valueRecord.xPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0020) { valueRecord.yPlaDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0040) { valueRecord.xAdvDevice = undefined; this.parseShort(); } + if (valueFormat & 0x0080) { valueRecord.yAdvDevice = undefined; this.parseShort(); } + + return valueRecord; +}; + +/** + * Parse a list of GPOS valueRecords + * https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#value-record + * valueFormat and valueCount are read from the stream. + */ +Parser.prototype.parseValueRecordList = function() { + var valueFormat = this.parseUShort(); + var valueCount = this.parseUShort(); + var values = new Array(valueCount); + for (var i = 0; i < valueCount; i++) { + values[i] = this.parseValueRecord(valueFormat); + } + return values; +}; + +Parser.prototype.parsePointer = function(description) { + var structOffset = this.parseOffset16(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; + +Parser.prototype.parsePointer32 = function(description) { + var structOffset = this.parseOffset32(); + if (structOffset > 0) { + // NULL offset => return undefined + return new Parser(this.data, this.offset + structOffset).parseStruct(description); + } + return undefined; +}; + +/** + * Parse a list of offsets to lists of 16-bit integers, + * or a list of offsets to lists of offsets to any kind of items. + * If itemCallback is not provided, a list of list of UShort is assumed. + * If provided, itemCallback is called on each item and must parse the item. + * See examples in tables/gsub.js + */ +Parser.prototype.parseListOfLists = function(itemCallback) { + var offsets = this.parseOffset16List(); + var count = offsets.length; + var relativeOffset = this.relativeOffset; + var list = new Array(count); + for (var i = 0; i < count; i++) { + var start = offsets[i]; + if (start === 0) { + // NULL offset + // Add i as owned property to list. Convenient with assert. + list[i] = undefined; + continue; + } + this.relativeOffset = start; + if (itemCallback) { + var subOffsets = this.parseOffset16List(); + var subList = new Array(subOffsets.length); + for (var j = 0; j < subOffsets.length; j++) { + this.relativeOffset = start + subOffsets[j]; + subList[j] = itemCallback.call(this); + } + list[i] = subList; + } else { + list[i] = this.parseUShortList(); + } + } + this.relativeOffset = relativeOffset; + return list; +}; + +///// Complex tables parsing ////////////////////////////////// + +// Parse a coverage table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +// parser.offset must point to the start of the table containing the coverage. +Parser.prototype.parseCoverage = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + var count = this.parseUShort(); + if (format === 1) { + return { + format: 1, + glyphs: this.parseUShortList(count) + }; + } else if (format === 2) { + var ranges = new Array(count); + for (var i = 0; i < count; i++) { + ranges[i] = { + start: this.parseUShort(), + end: this.parseUShort(), + index: this.parseUShort() + }; + } + return { + format: 2, + ranges: ranges + }; + } + throw new Error('0x' + startOffset.toString(16) + ': Coverage format must be 1 or 2.'); +}; + +// Parse a Class Definition Table in a GSUB, GPOS or GDEF table. +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm +Parser.prototype.parseClassDef = function() { + var startOffset = this.offset + this.relativeOffset; + var format = this.parseUShort(); + if (format === 1) { + return { + format: 1, + startGlyph: this.parseUShort(), + classes: this.parseUShortList() + }; + } else if (format === 2) { + return { + format: 2, + ranges: this.parseRecordList({ + start: Parser.uShort, + end: Parser.uShort, + classId: Parser.uShort + }) + }; + } + throw new Error('0x' + startOffset.toString(16) + ': ClassDef format must be 1 or 2.'); +}; + +///// Static methods /////////////////////////////////// +// These convenience methods can be used as callbacks and should be called with "this" context set to a Parser instance. + +Parser.list = function(count, itemCallback) { + return function() { + return this.parseList(count, itemCallback); + }; +}; + +Parser.list32 = function(count, itemCallback) { + return function() { + return this.parseList32(count, itemCallback); + }; +}; + +Parser.recordList = function(count, recordDescription) { + return function() { + return this.parseRecordList(count, recordDescription); + }; +}; + +Parser.recordList32 = function(count, recordDescription) { + return function() { + return this.parseRecordList32(count, recordDescription); + }; +}; + +Parser.pointer = function(description) { + return function() { + return this.parsePointer(description); + }; +}; + +Parser.pointer32 = function(description) { + return function() { + return this.parsePointer32(description); + }; +}; + +Parser.tag = Parser.prototype.parseTag; +Parser.byte = Parser.prototype.parseByte; +Parser.uShort = Parser.offset16 = Parser.prototype.parseUShort; +Parser.uShortList = Parser.prototype.parseUShortList; +Parser.uLong = Parser.offset32 = Parser.prototype.parseULong; +Parser.uLongList = Parser.prototype.parseULongList; +Parser.struct = Parser.prototype.parseStruct; +Parser.coverage = Parser.prototype.parseCoverage; +Parser.classDef = Parser.prototype.parseClassDef; + +///// Script, Feature, Lookup lists /////////////////////////////////////////////// +// https://www.microsoft.com/typography/OTSPEC/chapter2.htm + +var langSysTable = { + reserved: Parser.uShort, + reqFeatureIndex: Parser.uShort, + featureIndexes: Parser.uShortList +}; + +Parser.prototype.parseScriptList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + script: Parser.pointer({ + defaultLangSys: Parser.pointer(langSysTable), + langSysRecords: Parser.recordList({ + tag: Parser.tag, + langSys: Parser.pointer(langSysTable) + }) + }) + })) || []; +}; + +Parser.prototype.parseFeatureList = function() { + return this.parsePointer(Parser.recordList({ + tag: Parser.tag, + feature: Parser.pointer({ + featureParams: Parser.offset16, + lookupListIndexes: Parser.uShortList + }) + })) || []; +}; + +Parser.prototype.parseLookupList = function(lookupTableParsers) { + return this.parsePointer(Parser.list(Parser.pointer(function() { + var lookupType = this.parseUShort(); + check.argument(1 <= lookupType && lookupType <= 9, 'GPOS/GSUB lookup type ' + lookupType + ' unknown.'); + var lookupFlag = this.parseUShort(); + var useMarkFilteringSet = lookupFlag & 0x10; + return { + lookupType: lookupType, + lookupFlag: lookupFlag, + subtables: this.parseList(Parser.pointer(lookupTableParsers[lookupType])), + markFilteringSet: useMarkFilteringSet ? this.parseUShort() : undefined + }; + }))) || []; +}; + +Parser.prototype.parseFeatureVariationsList = function() { + return this.parsePointer32(function() { + var majorVersion = this.parseUShort(); + var minorVersion = this.parseUShort(); + check.argument(majorVersion === 1 && minorVersion < 1, 'GPOS/GSUB feature variations table unknown.'); + var featureVariations = this.parseRecordList32({ + conditionSetOffset: Parser.offset32, + featureTableSubstitutionOffset: Parser.offset32 + }); + return featureVariations; + }) || []; +}; + +var parse = { + getByte: getByte, + getCard8: getByte, + getUShort: getUShort, + getCard16: getUShort, + getShort: getShort, + getULong: getULong, + getFixed: getFixed, + getTag: getTag, + getOffset: getOffset, + getBytes: getBytes, + bytesToString: bytesToString, + Parser: Parser, +}; + +// The `cmap` table stores the mappings from characters to glyphs. + +function parseCmapTableFormat12(cmap, p) { + //Skip reserved. + p.parseUShort(); + + // Length in bytes of the sub-tables. + cmap.length = p.parseULong(); + cmap.language = p.parseULong(); + + var groupCount; + cmap.groupCount = groupCount = p.parseULong(); + cmap.glyphIndexMap = {}; + + for (var i = 0; i < groupCount; i += 1) { + var startCharCode = p.parseULong(); + var endCharCode = p.parseULong(); + var startGlyphId = p.parseULong(); + + for (var c = startCharCode; c <= endCharCode; c += 1) { + cmap.glyphIndexMap[c] = startGlyphId; + startGlyphId++; + } + } +} + +function parseCmapTableFormat4(cmap, p, data, start, offset) { + // Length in bytes of the sub-tables. + cmap.length = p.parseUShort(); + cmap.language = p.parseUShort(); + + // segCount is stored x 2. + var segCount; + cmap.segCount = segCount = p.parseUShort() >> 1; + + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + + // The "unrolled" mapping from character codes to glyph indices. + cmap.glyphIndexMap = {}; + var endCountParser = new parse.Parser(data, start + offset + 14); + var startCountParser = new parse.Parser(data, start + offset + 16 + segCount * 2); + var idDeltaParser = new parse.Parser(data, start + offset + 16 + segCount * 4); + var idRangeOffsetParser = new parse.Parser(data, start + offset + 16 + segCount * 6); + var glyphIndexOffset = start + offset + 16 + segCount * 8; + for (var i = 0; i < segCount - 1; i += 1) { + var glyphIndex = (void 0); + var endCount = endCountParser.parseUShort(); + var startCount = startCountParser.parseUShort(); + var idDelta = idDeltaParser.parseShort(); + var idRangeOffset = idRangeOffsetParser.parseUShort(); + for (var c = startCount; c <= endCount; c += 1) { + if (idRangeOffset !== 0) { + // The idRangeOffset is relative to the current position in the idRangeOffset array. + // Take the current offset in the idRangeOffset array. + glyphIndexOffset = (idRangeOffsetParser.offset + idRangeOffsetParser.relativeOffset - 2); + + // Add the value of the idRangeOffset, which will move us into the glyphIndex array. + glyphIndexOffset += idRangeOffset; + + // Then add the character index of the current segment, multiplied by 2 for USHORTs. + glyphIndexOffset += (c - startCount) * 2; + glyphIndex = parse.getUShort(data, glyphIndexOffset); + if (glyphIndex !== 0) { + glyphIndex = (glyphIndex + idDelta) & 0xFFFF; + } + } else { + glyphIndex = (c + idDelta) & 0xFFFF; + } + + cmap.glyphIndexMap[c] = glyphIndex; + } + } +} + +// Parse the `cmap` table. This table stores the mappings from characters to glyphs. +// There are many available formats, but we only support the Windows format 4 and 12. +// This function returns a `CmapEncoding` object or null if no supported format could be found. +function parseCmapTable(data, start) { + var cmap = {}; + cmap.version = parse.getUShort(data, start); + check.argument(cmap.version === 0, 'cmap table version should be 0.'); + + // The cmap table can contain many sub-tables, each with their own format. + // We're only interested in a "platform 0" (Unicode format) and "platform 3" (Windows format) table. + cmap.numTables = parse.getUShort(data, start + 2); + var offset = -1; + for (var i = cmap.numTables - 1; i >= 0; i -= 1) { + var platformId = parse.getUShort(data, start + 4 + (i * 8)); + var encodingId = parse.getUShort(data, start + 4 + (i * 8) + 2); + if ((platformId === 3 && (encodingId === 0 || encodingId === 1 || encodingId === 10)) || + (platformId === 0 && (encodingId === 0 || encodingId === 1 || encodingId === 2 || encodingId === 3 || encodingId === 4))) { + offset = parse.getULong(data, start + 4 + (i * 8) + 4); + break; + } + } + + if (offset === -1) { + // There is no cmap table in the font that we support. + throw new Error('No valid cmap sub-tables found.'); + } + + var p = new parse.Parser(data, start + offset); + cmap.format = p.parseUShort(); + + if (cmap.format === 12) { + parseCmapTableFormat12(cmap, p); + } else if (cmap.format === 4) { + parseCmapTableFormat4(cmap, p, data, start, offset); + } else { + throw new Error('Only format 4 and 12 cmap tables are supported (found format ' + cmap.format + ').'); + } + + return cmap; +} + +function addSegment(t, code, glyphIndex) { + t.segments.push({ + end: code, + start: code, + delta: -(code - glyphIndex), + offset: 0, + glyphIndex: glyphIndex + }); +} + +function addTerminatorSegment(t) { + t.segments.push({ + end: 0xFFFF, + start: 0xFFFF, + delta: 1, + offset: 0 + }); +} + +// Make cmap table, format 4 by default, 12 if needed only +function makeCmapTable(glyphs) { + // Plan 0 is the base Unicode Plan but emojis, for example are on another plan, and needs cmap 12 format (with 32bit) + var isPlan0Only = true; + var i; + + // Check if we need to add cmap format 12 or if format 4 only is fine + for (i = glyphs.length - 1; i > 0; i -= 1) { + var g = glyphs.get(i); + if (g.unicode > 65535) { + console.log('Adding CMAP format 12 (needed!)'); + isPlan0Only = false; + break; + } + } + + var cmapTable = [ + {name: 'version', type: 'USHORT', value: 0}, + {name: 'numTables', type: 'USHORT', value: isPlan0Only ? 1 : 2}, + + // CMAP 4 header + {name: 'platformID', type: 'USHORT', value: 3}, + {name: 'encodingID', type: 'USHORT', value: 1}, + {name: 'offset', type: 'ULONG', value: isPlan0Only ? 12 : (12 + 8)} + ]; + + if (!isPlan0Only) + { cmapTable = cmapTable.concat([ + // CMAP 12 header + {name: 'cmap12PlatformID', type: 'USHORT', value: 3}, // We encode only for PlatformID = 3 (Windows) because it is supported everywhere + {name: 'cmap12EncodingID', type: 'USHORT', value: 10}, + {name: 'cmap12Offset', type: 'ULONG', value: 0} + ]); } + + cmapTable = cmapTable.concat([ + // CMAP 4 Subtable + {name: 'format', type: 'USHORT', value: 4}, + {name: 'cmap4Length', type: 'USHORT', value: 0}, + {name: 'language', type: 'USHORT', value: 0}, + {name: 'segCountX2', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + + var t = new table.Table('cmap', cmapTable); + + t.segments = []; + for (i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + addSegment(t, glyph.unicodes[j], i); + } + + t.segments = t.segments.sort(function (a, b) { + return a.start - b.start; + }); + } + + addTerminatorSegment(t); + + var segCount = t.segments.length; + var segCountToRemove = 0; + + // CMAP 4 + // Set up parallel segment arrays. + var endCounts = []; + var startCounts = []; + var idDeltas = []; + var idRangeOffsets = []; + var glyphIds = []; + + // CMAP 12 + var cmap12Groups = []; + + // Reminder this loop is not following the specification at 100% + // The specification -> find suites of characters and make a group + // Here we're doing one group for each letter + // Doing as the spec can save 8 times (or more) space + for (i = 0; i < segCount; i += 1) { + var segment = t.segments[i]; + + // CMAP 4 + if (segment.end <= 65535 && segment.start <= 65535) { + endCounts = endCounts.concat({name: 'end_' + i, type: 'USHORT', value: segment.end}); + startCounts = startCounts.concat({name: 'start_' + i, type: 'USHORT', value: segment.start}); + idDeltas = idDeltas.concat({name: 'idDelta_' + i, type: 'SHORT', value: segment.delta}); + idRangeOffsets = idRangeOffsets.concat({name: 'idRangeOffset_' + i, type: 'USHORT', value: segment.offset}); + if (segment.glyphId !== undefined) { + glyphIds = glyphIds.concat({name: 'glyph_' + i, type: 'USHORT', value: segment.glyphId}); + } + } else { + // Skip Unicode > 65535 (16bit unsigned max) for CMAP 4, will be added in CMAP 12 + segCountToRemove += 1; + } + + // CMAP 12 + // Skip Terminator Segment + if (!isPlan0Only && segment.glyphIndex !== undefined) { + cmap12Groups = cmap12Groups.concat({name: 'cmap12Start_' + i, type: 'ULONG', value: segment.start}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12End_' + i, type: 'ULONG', value: segment.end}); + cmap12Groups = cmap12Groups.concat({name: 'cmap12Glyph_' + i, type: 'ULONG', value: segment.glyphIndex}); + } + } + + // CMAP 4 Subtable + t.segCountX2 = (segCount - segCountToRemove) * 2; + t.searchRange = Math.pow(2, Math.floor(Math.log((segCount - segCountToRemove)) / Math.log(2))) * 2; + t.entrySelector = Math.log(t.searchRange / 2) / Math.log(2); + t.rangeShift = t.segCountX2 - t.searchRange; + + t.fields = t.fields.concat(endCounts); + t.fields.push({name: 'reservedPad', type: 'USHORT', value: 0}); + t.fields = t.fields.concat(startCounts); + t.fields = t.fields.concat(idDeltas); + t.fields = t.fields.concat(idRangeOffsets); + t.fields = t.fields.concat(glyphIds); + + t.cmap4Length = 14 + // Subtable header + endCounts.length * 2 + + 2 + // reservedPad + startCounts.length * 2 + + idDeltas.length * 2 + + idRangeOffsets.length * 2 + + glyphIds.length * 2; + + if (!isPlan0Only) { + // CMAP 12 Subtable + var cmap12Length = 16 + // Subtable header + cmap12Groups.length * 4; + + t.cmap12Offset = 12 + (2 * 2) + 4 + t.cmap4Length; + t.fields = t.fields.concat([ + {name: 'cmap12Format', type: 'USHORT', value: 12}, + {name: 'cmap12Reserved', type: 'USHORT', value: 0}, + {name: 'cmap12Length', type: 'ULONG', value: cmap12Length}, + {name: 'cmap12Language', type: 'ULONG', value: 0}, + {name: 'cmap12nGroups', type: 'ULONG', value: cmap12Groups.length / 3} + ]); + + t.fields = t.fields.concat(cmap12Groups); + } + + return t; +} + +var cmap = { parse: parseCmapTable, make: makeCmapTable }; + +// Glyph encoding + +var cffStandardStrings = [ + '.notdef', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', 'exclamdown', 'cent', 'sterling', + 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', 'quotedblleft', 'guillemotleft', + 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'endash', 'dagger', 'daggerdbl', 'periodcentered', 'paragraph', + 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', 'guillemotright', 'ellipsis', 'perthousand', + 'questiondown', 'grave', 'acute', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'dieresis', 'ring', + 'cedilla', 'hungarumlaut', 'ogonek', 'caron', 'emdash', 'AE', 'ordfeminine', 'Lslash', 'Oslash', 'OE', + 'ordmasculine', 'ae', 'dotlessi', 'lslash', 'oslash', 'oe', 'germandbls', 'onesuperior', 'logicalnot', 'mu', + 'trademark', 'Eth', 'onehalf', 'plusminus', 'Thorn', 'onequarter', 'divide', 'brokenbar', 'degree', 'thorn', + 'threequarters', 'twosuperior', 'registered', 'minus', 'eth', 'multiply', 'threesuperior', 'copyright', + 'Aacute', 'Acircumflex', 'Adieresis', 'Agrave', 'Aring', 'Atilde', 'Ccedilla', 'Eacute', 'Ecircumflex', + 'Edieresis', 'Egrave', 'Iacute', 'Icircumflex', 'Idieresis', 'Igrave', 'Ntilde', 'Oacute', 'Ocircumflex', + 'Odieresis', 'Ograve', 'Otilde', 'Scaron', 'Uacute', 'Ucircumflex', 'Udieresis', 'Ugrave', 'Yacute', + 'Ydieresis', 'Zcaron', 'aacute', 'acircumflex', 'adieresis', 'agrave', 'aring', 'atilde', 'ccedilla', 'eacute', + 'ecircumflex', 'edieresis', 'egrave', 'iacute', 'icircumflex', 'idieresis', 'igrave', 'ntilde', 'oacute', + 'ocircumflex', 'odieresis', 'ograve', 'otilde', 'scaron', 'uacute', 'ucircumflex', 'udieresis', 'ugrave', + 'yacute', 'ydieresis', 'zcaron', 'exclamsmall', 'Hungarumlautsmall', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', '266 ff', 'onedotenleader', + 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', + 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'commasuperior', 'threequartersemdash', 'periodsuperior', + 'questionsmall', 'asuperior', 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', 'isuperior', 'lsuperior', + 'msuperior', 'nsuperior', 'osuperior', 'rsuperior', 'ssuperior', 'tsuperior', 'ff', 'ffi', 'ffl', + 'parenleftinferior', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', 'exclamdownsmall', + 'centoldstyle', 'Lslashsmall', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', 'Brevesmall', 'Caronsmall', + 'Dotaccentsmall', 'Macronsmall', 'figuredash', 'hypheninferior', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', + 'zerosuperior', 'foursuperior', 'fivesuperior', 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', + 'zeroinferior', 'oneinferior', 'twoinferior', 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', + 'seveninferior', 'eightinferior', 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', + 'commainferior', 'Agravesmall', 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', + 'Aringsmall', 'AEsmall', 'Ccedillasmall', 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', + 'Igravesmall', 'Iacutesmall', 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', + 'Oacutesmall', 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', + 'Uacutesmall', 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall', '001.000', + '001.001', '001.002', '001.003', 'Black', 'Bold', 'Book', 'Light', 'Medium', 'Regular', 'Roman', 'Semibold']; + +var cffStandardEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', 'ampersand', 'quoteright', + 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', 'zero', 'one', 'two', + 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', 'equal', 'greater', + 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', + 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', 'asciicircum', 'underscore', + 'quoteleft', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', + 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', '', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdown', 'cent', 'sterling', 'fraction', 'yen', 'florin', 'section', 'currency', 'quotesingle', + 'quotedblleft', 'guillemotleft', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', '', 'endash', 'dagger', + 'daggerdbl', 'periodcentered', '', 'paragraph', 'bullet', 'quotesinglbase', 'quotedblbase', 'quotedblright', + 'guillemotright', 'ellipsis', 'perthousand', '', 'questiondown', '', 'grave', 'acute', 'circumflex', 'tilde', + 'macron', 'breve', 'dotaccent', 'dieresis', '', 'ring', 'cedilla', '', 'hungarumlaut', 'ogonek', 'caron', + 'emdash', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', 'AE', '', 'ordfeminine', '', '', '', + '', 'Lslash', 'Oslash', 'OE', 'ordmasculine', '', '', '', '', '', 'ae', '', '', '', 'dotlessi', '', '', + 'lslash', 'oslash', 'oe', 'germandbls']; + +var cffExpertEncoding = [ + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '', '', '', 'space', 'exclamsmall', 'Hungarumlautsmall', '', 'dollaroldstyle', 'dollarsuperior', + 'ampersandsmall', 'Acutesmall', 'parenleftsuperior', 'parenrightsuperior', 'twodotenleader', 'onedotenleader', + 'comma', 'hyphen', 'period', 'fraction', 'zerooldstyle', 'oneoldstyle', 'twooldstyle', 'threeoldstyle', + 'fouroldstyle', 'fiveoldstyle', 'sixoldstyle', 'sevenoldstyle', 'eightoldstyle', 'nineoldstyle', 'colon', + 'semicolon', 'commasuperior', 'threequartersemdash', 'periodsuperior', 'questionsmall', '', 'asuperior', + 'bsuperior', 'centsuperior', 'dsuperior', 'esuperior', '', '', 'isuperior', '', '', 'lsuperior', 'msuperior', + 'nsuperior', 'osuperior', '', '', 'rsuperior', 'ssuperior', 'tsuperior', '', 'ff', 'fi', 'fl', 'ffi', 'ffl', + 'parenleftinferior', '', 'parenrightinferior', 'Circumflexsmall', 'hyphensuperior', 'Gravesmall', 'Asmall', + 'Bsmall', 'Csmall', 'Dsmall', 'Esmall', 'Fsmall', 'Gsmall', 'Hsmall', 'Ismall', 'Jsmall', 'Ksmall', 'Lsmall', + 'Msmall', 'Nsmall', 'Osmall', 'Psmall', 'Qsmall', 'Rsmall', 'Ssmall', 'Tsmall', 'Usmall', 'Vsmall', 'Wsmall', + 'Xsmall', 'Ysmall', 'Zsmall', 'colonmonetary', 'onefitted', 'rupiah', 'Tildesmall', '', '', '', '', '', '', '', + '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', + 'exclamdownsmall', 'centoldstyle', 'Lslashsmall', '', '', 'Scaronsmall', 'Zcaronsmall', 'Dieresissmall', + 'Brevesmall', 'Caronsmall', '', 'Dotaccentsmall', '', '', 'Macronsmall', '', '', 'figuredash', 'hypheninferior', + '', '', 'Ogoneksmall', 'Ringsmall', 'Cedillasmall', '', '', '', 'onequarter', 'onehalf', 'threequarters', + 'questiondownsmall', 'oneeighth', 'threeeighths', 'fiveeighths', 'seveneighths', 'onethird', 'twothirds', '', + '', 'zerosuperior', 'onesuperior', 'twosuperior', 'threesuperior', 'foursuperior', 'fivesuperior', + 'sixsuperior', 'sevensuperior', 'eightsuperior', 'ninesuperior', 'zeroinferior', 'oneinferior', 'twoinferior', + 'threeinferior', 'fourinferior', 'fiveinferior', 'sixinferior', 'seveninferior', 'eightinferior', + 'nineinferior', 'centinferior', 'dollarinferior', 'periodinferior', 'commainferior', 'Agravesmall', + 'Aacutesmall', 'Acircumflexsmall', 'Atildesmall', 'Adieresissmall', 'Aringsmall', 'AEsmall', 'Ccedillasmall', + 'Egravesmall', 'Eacutesmall', 'Ecircumflexsmall', 'Edieresissmall', 'Igravesmall', 'Iacutesmall', + 'Icircumflexsmall', 'Idieresissmall', 'Ethsmall', 'Ntildesmall', 'Ogravesmall', 'Oacutesmall', + 'Ocircumflexsmall', 'Otildesmall', 'Odieresissmall', 'OEsmall', 'Oslashsmall', 'Ugravesmall', 'Uacutesmall', + 'Ucircumflexsmall', 'Udieresissmall', 'Yacutesmall', 'Thornsmall', 'Ydieresissmall']; + +var standardNames = [ + '.notdef', '.null', 'nonmarkingreturn', 'space', 'exclam', 'quotedbl', 'numbersign', 'dollar', 'percent', + 'ampersand', 'quotesingle', 'parenleft', 'parenright', 'asterisk', 'plus', 'comma', 'hyphen', 'period', 'slash', + 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'colon', 'semicolon', 'less', + 'equal', 'greater', 'question', 'at', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'bracketleft', 'backslash', 'bracketright', + 'asciicircum', 'underscore', 'grave', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'braceleft', 'bar', 'braceright', 'asciitilde', + 'Adieresis', 'Aring', 'Ccedilla', 'Eacute', 'Ntilde', 'Odieresis', 'Udieresis', 'aacute', 'agrave', + 'acircumflex', 'adieresis', 'atilde', 'aring', 'ccedilla', 'eacute', 'egrave', 'ecircumflex', 'edieresis', + 'iacute', 'igrave', 'icircumflex', 'idieresis', 'ntilde', 'oacute', 'ograve', 'ocircumflex', 'odieresis', + 'otilde', 'uacute', 'ugrave', 'ucircumflex', 'udieresis', 'dagger', 'degree', 'cent', 'sterling', 'section', + 'bullet', 'paragraph', 'germandbls', 'registered', 'copyright', 'trademark', 'acute', 'dieresis', 'notequal', + 'AE', 'Oslash', 'infinity', 'plusminus', 'lessequal', 'greaterequal', 'yen', 'mu', 'partialdiff', 'summation', + 'product', 'pi', 'integral', 'ordfeminine', 'ordmasculine', 'Omega', 'ae', 'oslash', 'questiondown', + 'exclamdown', 'logicalnot', 'radical', 'florin', 'approxequal', 'Delta', 'guillemotleft', 'guillemotright', + 'ellipsis', 'nonbreakingspace', 'Agrave', 'Atilde', 'Otilde', 'OE', 'oe', 'endash', 'emdash', 'quotedblleft', + 'quotedblright', 'quoteleft', 'quoteright', 'divide', 'lozenge', 'ydieresis', 'Ydieresis', 'fraction', + 'currency', 'guilsinglleft', 'guilsinglright', 'fi', 'fl', 'daggerdbl', 'periodcentered', 'quotesinglbase', + 'quotedblbase', 'perthousand', 'Acircumflex', 'Ecircumflex', 'Aacute', 'Edieresis', 'Egrave', 'Iacute', + 'Icircumflex', 'Idieresis', 'Igrave', 'Oacute', 'Ocircumflex', 'apple', 'Ograve', 'Uacute', 'Ucircumflex', + 'Ugrave', 'dotlessi', 'circumflex', 'tilde', 'macron', 'breve', 'dotaccent', 'ring', 'cedilla', 'hungarumlaut', + 'ogonek', 'caron', 'Lslash', 'lslash', 'Scaron', 'scaron', 'Zcaron', 'zcaron', 'brokenbar', 'Eth', 'eth', + 'Yacute', 'yacute', 'Thorn', 'thorn', 'minus', 'multiply', 'onesuperior', 'twosuperior', 'threesuperior', + 'onehalf', 'onequarter', 'threequarters', 'franc', 'Gbreve', 'gbreve', 'Idotaccent', 'Scedilla', 'scedilla', + 'Cacute', 'cacute', 'Ccaron', 'ccaron', 'dcroat']; + +/** + * This is the encoding used for fonts created from scratch. + * It loops through all glyphs and finds the appropriate unicode value. + * Since it's linear time, other encodings will be faster. + * @exports opentype.DefaultEncoding + * @class + * @constructor + * @param {opentype.Font} + */ +function DefaultEncoding(font) { + this.font = font; +} + +DefaultEncoding.prototype.charToGlyphIndex = function(c) { + var code = c.codePointAt(0); + var glyphs = this.font.glyphs; + if (glyphs) { + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + for (var j = 0; j < glyph.unicodes.length; j += 1) { + if (glyph.unicodes[j] === code) { + return i; + } + } + } + } + return null; +}; + +/** + * @exports opentype.CmapEncoding + * @class + * @constructor + * @param {Object} cmap - a object with the cmap encoded data + */ +function CmapEncoding(cmap) { + this.cmap = cmap; +} + +/** + * @param {string} c - the character + * @return {number} The glyph index. + */ +CmapEncoding.prototype.charToGlyphIndex = function(c) { + return this.cmap.glyphIndexMap[c.codePointAt(0)] || 0; +}; + +/** + * @exports opentype.CffEncoding + * @class + * @constructor + * @param {string} encoding - The encoding + * @param {Array} charset - The character set. + */ +function CffEncoding(encoding, charset) { + this.encoding = encoding; + this.charset = charset; +} + +/** + * @param {string} s - The character + * @return {number} The index. + */ +CffEncoding.prototype.charToGlyphIndex = function(s) { + var code = s.codePointAt(0); + var charName = this.encoding[code]; + return this.charset.indexOf(charName); +}; + +/** + * @exports opentype.GlyphNames + * @class + * @constructor + * @param {Object} post + */ +function GlyphNames(post) { + switch (post.version) { + case 1: + this.names = standardNames.slice(); + break; + case 2: + this.names = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + if (post.glyphNameIndex[i] < standardNames.length) { + this.names[i] = standardNames[post.glyphNameIndex[i]]; + } else { + this.names[i] = post.names[post.glyphNameIndex[i] - standardNames.length]; + } + } + + break; + case 2.5: + this.names = new Array(post.numberOfGlyphs); + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + this.names[i$1] = standardNames[i$1 + post.glyphNameIndex[i$1]]; + } + + break; + case 3: + this.names = []; + break; + default: + this.names = []; + break; + } +} + +/** + * Gets the index of a glyph by name. + * @param {string} name - The glyph name + * @return {number} The index + */ +GlyphNames.prototype.nameToGlyphIndex = function(name) { + return this.names.indexOf(name); +}; + +/** + * @param {number} gid + * @return {string} + */ +GlyphNames.prototype.glyphIndexToName = function(gid) { + return this.names[gid]; +}; + +function addGlyphNamesAll(font) { + var glyph; + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + glyph = font.glyphs.get(glyphIndex); + glyph.addUnicode(parseInt(c)); + } + + for (var i$1 = 0; i$1 < font.glyphs.length; i$1 += 1) { + glyph = font.glyphs.get(i$1); + if (font.cffEncoding) { + if (font.isCIDFont) { + glyph.name = 'gid' + i$1; + } else { + glyph.name = font.cffEncoding.charset[i$1]; + } + } else if (font.glyphNames.names) { + glyph.name = font.glyphNames.glyphIndexToName(i$1); + } + } +} + +function addGlyphNamesToUnicodeMap(font) { + font._IndexToUnicodeMap = {}; + + var glyphIndexMap = font.tables.cmap.glyphIndexMap; + var charCodes = Object.keys(glyphIndexMap); + + for (var i = 0; i < charCodes.length; i += 1) { + var c = charCodes[i]; + var glyphIndex = glyphIndexMap[c]; + if (font._IndexToUnicodeMap[glyphIndex] === undefined) { + font._IndexToUnicodeMap[glyphIndex] = { + unicodes: [parseInt(c)] + }; + } else { + font._IndexToUnicodeMap[glyphIndex].unicodes.push(parseInt(c)); + } + } +} + +/** + * @alias opentype.addGlyphNames + * @param {opentype.Font} + * @param {Object} + */ +function addGlyphNames(font, opt) { + if (opt.lowMemory) { + addGlyphNamesToUnicodeMap(font); + } else { + addGlyphNamesAll(font); + } +} + +// Drawing utility functions. + +// Draw a line on the given context from point `x1,y1` to point `x2,y2`. +function line(ctx, x1, y1, x2, y2) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); +} + +var draw = { line: line }; + +// The Glyph object +// import glyf from './tables/glyf' Can't be imported here, because it's a circular dependency + +function getPathDefinition(glyph, path) { + var _path = path || new Path(); + return { + configurable: true, + + get: function() { + if (typeof _path === 'function') { + _path = _path(); + } + + return _path; + }, + + set: function(p) { + _path = p; + } + }; +} +/** + * @typedef GlyphOptions + * @type Object + * @property {string} [name] - The glyph name + * @property {number} [unicode] + * @property {Array} [unicodes] + * @property {number} [xMin] + * @property {number} [yMin] + * @property {number} [xMax] + * @property {number} [yMax] + * @property {number} [advanceWidth] + */ + +// A Glyph is an individual mark that often corresponds to a character. +// Some glyphs, such as ligatures, are a combination of many characters. +// Glyphs are the basic building blocks of a font. +// +// The `Glyph` class contains utility methods for drawing the path and its points. +/** + * @exports opentype.Glyph + * @class + * @param {GlyphOptions} + * @constructor + */ +function Glyph(options) { + // By putting all the code on a prototype function (which is only declared once) + // we reduce the memory requirements for larger fonts by some 2% + this.bindConstructorValues(options); +} + +/** + * @param {GlyphOptions} + */ +Glyph.prototype.bindConstructorValues = function(options) { + this.index = options.index || 0; + + // These three values cannot be deferred for memory optimization: + this.name = options.name || null; + this.unicode = options.unicode || undefined; + this.unicodes = options.unicodes || options.unicode !== undefined ? [options.unicode] : []; + + // But by binding these values only when necessary, we reduce can + // the memory requirements by almost 3% for larger fonts. + if ('xMin' in options) { + this.xMin = options.xMin; + } + + if ('yMin' in options) { + this.yMin = options.yMin; + } + + if ('xMax' in options) { + this.xMax = options.xMax; + } + + if ('yMax' in options) { + this.yMax = options.yMax; + } + + if ('advanceWidth' in options) { + this.advanceWidth = options.advanceWidth; + } + + // The path for a glyph is the most memory intensive, and is bound as a value + // with a getter/setter to ensure we actually do path parsing only once the + // path is actually needed by anything. + Object.defineProperty(this, 'path', getPathDefinition(this, options.path)); +}; + +/** + * @param {number} + */ +Glyph.prototype.addUnicode = function(unicode) { + if (this.unicodes.length === 0) { + this.unicode = unicode; + } + + this.unicodes.push(unicode); +}; + +/** + * Calculate the minimum bounding box for this glyph. + * @return {opentype.BoundingBox} + */ +Glyph.prototype.getBoundingBox = function() { + return this.path.getBoundingBox(); +}; + +/** + * Convert the glyph to a Path we can draw on a drawing context. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + * @param {opentype.Font} if hinting is to be used, the font + * @return {opentype.Path} + */ +Glyph.prototype.getPath = function(x, y, fontSize, options, font) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + var commands; + var hPoints; + if (!options) { options = { }; } + var xScale = options.xScale; + var yScale = options.yScale; + + if (options.hinting && font && font.hinting) { + // in case of hinting, the hinting engine takes care + // of scaling the points (not the path) before hinting. + hPoints = this.path && font.hinting.exec(this, fontSize); + // in case the hinting engine failed hPoints is undefined + // and thus reverts to plain rending + } + + if (hPoints) { + // Call font.hinting.getCommands instead of `glyf.getPath(hPoints).commands` to avoid a circular dependency + commands = font.hinting.getCommands(hPoints); + x = Math.round(x); + y = Math.round(y); + // TODO in case of hinting xyScaling is not yet supported + xScale = yScale = 1; + } else { + commands = this.path.commands; + var scale = 1 / (this.path.unitsPerEm || 1000) * fontSize; + if (xScale === undefined) { xScale = scale; } + if (yScale === undefined) { yScale = scale; } + } + + var p = new Path(); + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type === 'M') { + p.moveTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'L') { + p.lineTo(x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Q') { + p.quadraticCurveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'C') { + p.curveTo(x + (cmd.x1 * xScale), y + (-cmd.y1 * yScale), + x + (cmd.x2 * xScale), y + (-cmd.y2 * yScale), + x + (cmd.x * xScale), y + (-cmd.y * yScale)); + } else if (cmd.type === 'Z') { + p.closePath(); + } + } + + return p; +}; + +/** + * Split the glyph into contours. + * This function is here for backwards compatibility, and to + * provide raw access to the TrueType glyph outlines. + * @return {Array} + */ +Glyph.prototype.getContours = function() { + if (this.points === undefined) { + return []; + } + + var contours = []; + var currentContour = []; + for (var i = 0; i < this.points.length; i += 1) { + var pt = this.points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +}; + +/** + * Calculate the xMin/yMin/xMax/yMax/lsb/rsb for a Glyph. + * @return {Object} + */ +Glyph.prototype.getMetrics = function() { + var commands = this.path.commands; + var xCoords = []; + var yCoords = []; + for (var i = 0; i < commands.length; i += 1) { + var cmd = commands[i]; + if (cmd.type !== 'Z') { + xCoords.push(cmd.x); + yCoords.push(cmd.y); + } + + if (cmd.type === 'Q' || cmd.type === 'C') { + xCoords.push(cmd.x1); + yCoords.push(cmd.y1); + } + + if (cmd.type === 'C') { + xCoords.push(cmd.x2); + yCoords.push(cmd.y2); + } + } + + var metrics = { + xMin: Math.min.apply(null, xCoords), + yMin: Math.min.apply(null, yCoords), + xMax: Math.max.apply(null, xCoords), + yMax: Math.max.apply(null, yCoords), + leftSideBearing: this.leftSideBearing + }; + + if (!isFinite(metrics.xMin)) { + metrics.xMin = 0; + } + + if (!isFinite(metrics.xMax)) { + metrics.xMax = this.advanceWidth; + } + + if (!isFinite(metrics.yMin)) { + metrics.yMin = 0; + } + + if (!isFinite(metrics.yMax)) { + metrics.yMax = 0; + } + + metrics.rightSideBearing = this.advanceWidth - metrics.leftSideBearing - (metrics.xMax - metrics.xMin); + return metrics; +}; + +/** + * Draw the glyph on the given context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {Object=} options - xScale, yScale to stretch the glyph. + */ +Glyph.prototype.draw = function(ctx, x, y, fontSize, options) { + this.getPath(x, y, fontSize, options).draw(ctx); +}; + +/** + * Draw the points of the glyph. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawPoints = function(ctx, x, y, fontSize) { + function drawCircles(l, x, y, scale) { + ctx.beginPath(); + for (var j = 0; j < l.length; j += 1) { + ctx.moveTo(x + (l[j].x * scale), y + (l[j].y * scale)); + ctx.arc(x + (l[j].x * scale), y + (l[j].y * scale), 2, 0, Math.PI * 2, false); + } + + ctx.closePath(); + ctx.fill(); + } + + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + var scale = 1 / this.path.unitsPerEm * fontSize; + + var blueCircles = []; + var redCircles = []; + var path = this.path; + for (var i = 0; i < path.commands.length; i += 1) { + var cmd = path.commands[i]; + if (cmd.x !== undefined) { + blueCircles.push({x: cmd.x, y: -cmd.y}); + } + + if (cmd.x1 !== undefined) { + redCircles.push({x: cmd.x1, y: -cmd.y1}); + } + + if (cmd.x2 !== undefined) { + redCircles.push({x: cmd.x2, y: -cmd.y2}); + } + } + + ctx.fillStyle = 'blue'; + drawCircles(blueCircles, x, y, scale); + ctx.fillStyle = 'red'; + drawCircles(redCircles, x, y, scale); +}; + +/** + * Draw lines indicating important font measurements. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + */ +Glyph.prototype.drawMetrics = function(ctx, x, y, fontSize) { + var scale; + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 24; + scale = 1 / this.path.unitsPerEm * fontSize; + ctx.lineWidth = 1; + + // Draw the origin + ctx.strokeStyle = 'black'; + draw.line(ctx, x, -10000, x, 10000); + draw.line(ctx, -10000, y, 10000, y); + + // This code is here due to memory optimization: by not using + // defaults in the constructor, we save a notable amount of memory. + var xMin = this.xMin || 0; + var yMin = this.yMin || 0; + var xMax = this.xMax || 0; + var yMax = this.yMax || 0; + var advanceWidth = this.advanceWidth || 0; + + // Draw the glyph box + ctx.strokeStyle = 'blue'; + draw.line(ctx, x + (xMin * scale), -10000, x + (xMin * scale), 10000); + draw.line(ctx, x + (xMax * scale), -10000, x + (xMax * scale), 10000); + draw.line(ctx, -10000, y + (-yMin * scale), 10000, y + (-yMin * scale)); + draw.line(ctx, -10000, y + (-yMax * scale), 10000, y + (-yMax * scale)); + + // Draw the advance width + ctx.strokeStyle = 'green'; + draw.line(ctx, x + (advanceWidth * scale), -10000, x + (advanceWidth * scale), 10000); +}; + +// The GlyphSet object + +// Define a property on the glyph that depends on the path being loaded. +function defineDependentProperty(glyph, externalName, internalName) { + Object.defineProperty(glyph, externalName, { + get: function() { + // Request the path property to make sure the path is loaded. + glyph.path; // jshint ignore:line + return glyph[internalName]; + }, + set: function(newValue) { + glyph[internalName] = newValue; + }, + enumerable: true, + configurable: true + }); +} + +/** + * A GlyphSet represents all glyphs available in the font, but modelled using + * a deferred glyph loader, for retrieving glyphs only once they are absolutely + * necessary, to keep the memory footprint down. + * @exports opentype.GlyphSet + * @class + * @param {opentype.Font} + * @param {Array} + */ +function GlyphSet(font, glyphs) { + this.font = font; + this.glyphs = {}; + if (Array.isArray(glyphs)) { + for (var i = 0; i < glyphs.length; i++) { + var glyph = glyphs[i]; + glyph.path.unitsPerEm = font.unitsPerEm; + this.glyphs[i] = glyph; + } + } + + this.length = (glyphs && glyphs.length) || 0; +} + +/** + * @param {number} index + * @return {opentype.Glyph} + */ +GlyphSet.prototype.get = function(index) { + // this.glyphs[index] is 'undefined' when low memory mode is on. glyph is pushed on request only. + if (this.glyphs[index] === undefined) { + this.font._push(index); + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + + var glyph = this.glyphs[index]; + var unicodeObj = this.font._IndexToUnicodeMap[index]; + + if (unicodeObj) { + for (var j = 0; j < unicodeObj.unicodes.length; j++) + { glyph.addUnicode(unicodeObj.unicodes[j]); } + } + + if (this.font.cffEncoding) { + if (this.font.isCIDFont) { + glyph.name = 'gid' + index; + } else { + glyph.name = this.font.cffEncoding.charset[index]; + } + } else if (this.font.glyphNames.names) { + glyph.name = this.font.glyphNames.glyphIndexToName(index); + } + + this.glyphs[index].advanceWidth = this.font._hmtxTableData[index].advanceWidth; + this.glyphs[index].leftSideBearing = this.font._hmtxTableData[index].leftSideBearing; + } else { + if (typeof this.glyphs[index] === 'function') { + this.glyphs[index] = this.glyphs[index](); + } + } + + return this.glyphs[index]; +}; + +/** + * @param {number} index + * @param {Object} + */ +GlyphSet.prototype.push = function(index, loader) { + this.glyphs[index] = loader; + this.length++; +}; + +/** + * @alias opentype.glyphLoader + * @param {opentype.Font} font + * @param {number} index + * @return {opentype.Glyph} + */ +function glyphLoader(font, index) { + return new Glyph({index: index, font: font}); +} + +/** + * Generate a stub glyph that can be filled with all metadata *except* + * the "points" and "path" properties, which must be loaded only once + * the glyph's path is actually requested for text shaping. + * @alias opentype.ttfGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseGlyph + * @param {Object} data + * @param {number} position + * @param {Function} buildPath + * @return {opentype.Glyph} + */ +function ttfGlyphLoader(font, index, parseGlyph, data, position, buildPath) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + parseGlyph(glyph, data, position); + var path = buildPath(font.glyphs, glyph); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + defineDependentProperty(glyph, 'xMin', '_xMin'); + defineDependentProperty(glyph, 'xMax', '_xMax'); + defineDependentProperty(glyph, 'yMin', '_yMin'); + defineDependentProperty(glyph, 'yMax', '_yMax'); + + return glyph; + }; +} +/** + * @alias opentype.cffGlyphLoader + * @param {opentype.Font} font + * @param {number} index + * @param {Function} parseCFFCharstring + * @param {string} charstring + * @return {opentype.Glyph} + */ +function cffGlyphLoader(font, index, parseCFFCharstring, charstring) { + return function() { + var glyph = new Glyph({index: index, font: font}); + + glyph.path = function() { + var path = parseCFFCharstring(font, glyph, charstring); + path.unitsPerEm = font.unitsPerEm; + return path; + }; + + return glyph; + }; +} + +var glyphset = { GlyphSet: GlyphSet, glyphLoader: glyphLoader, ttfGlyphLoader: ttfGlyphLoader, cffGlyphLoader: cffGlyphLoader }; + +// The `CFF` table contains the glyph outlines in PostScript format. + +// Custom equals function that can also check lists. +function equals(a, b) { + if (a === b) { + return true; + } else if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) { + return false; + } + + for (var i = 0; i < a.length; i += 1) { + if (!equals(a[i], b[i])) { + return false; + } + } + + return true; + } else { + return false; + } +} + +// Subroutines are encoded using the negative half of the number space. +// See type 2 chapter 4.7 "Subroutine operators". +function calcCFFSubroutineBias(subrs) { + var bias; + if (subrs.length < 1240) { + bias = 107; + } else if (subrs.length < 33900) { + bias = 1131; + } else { + bias = 32768; + } + + return bias; +} + +// Parse a `CFF` INDEX array. +// An index array consists of a list of offsets, then a list of objects at those offsets. +function parseCFFIndex(data, start, conversionFn) { + var offsets = []; + var objects = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + for (var i$1 = 0; i$1 < offsets.length - 1; i$1 += 1) { + var value = parse.getBytes(data, objectOffset + offsets[i$1], objectOffset + offsets[i$1 + 1]); + if (conversionFn) { + value = conversionFn(value); + } + + objects.push(value); + } + + return {objects: objects, startOffset: start, endOffset: endOffset}; +} + +function parseCFFIndexLowMemory(data, start) { + var offsets = []; + var count = parse.getCard16(data, start); + var objectOffset; + var endOffset; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + var pos = start + 3; + for (var i = 0; i < count + 1; i += 1) { + offsets.push(parse.getOffset(data, pos, offsetSize)); + pos += offsetSize; + } + + // The total size of the index array is 4 header bytes + the value of the last offset. + endOffset = objectOffset + offsets[count]; + } else { + endOffset = start + 2; + } + + return {offsets: offsets, startOffset: start, endOffset: endOffset}; +} +function getCffIndexObject(i, offsets, data, start, conversionFn) { + var count = parse.getCard16(data, start); + var objectOffset = 0; + if (count !== 0) { + var offsetSize = parse.getByte(data, start + 2); + objectOffset = start + ((count + 1) * offsetSize) + 2; + } + + var value = parse.getBytes(data, objectOffset + offsets[i], objectOffset + offsets[i + 1]); + if (conversionFn) { + value = conversionFn(value); + } + return value; +} + +// Parse a `CFF` DICT real value. +function parseFloatOperand(parser) { + var s = ''; + var eof = 15; + var lookup = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'E', 'E-', null, '-']; + while (true) { + var b = parser.parseByte(); + var n1 = b >> 4; + var n2 = b & 15; + + if (n1 === eof) { + break; + } + + s += lookup[n1]; + + if (n2 === eof) { + break; + } + + s += lookup[n2]; + } + + return parseFloat(s); +} + +// Parse a `CFF` DICT operand. +function parseOperand(parser, b0) { + var b1; + var b2; + var b3; + var b4; + if (b0 === 28) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + return b1 << 8 | b2; + } + + if (b0 === 29) { + b1 = parser.parseByte(); + b2 = parser.parseByte(); + b3 = parser.parseByte(); + b4 = parser.parseByte(); + return b1 << 24 | b2 << 16 | b3 << 8 | b4; + } + + if (b0 === 30) { + return parseFloatOperand(parser); + } + + if (b0 >= 32 && b0 <= 246) { + return b0 - 139; + } + + if (b0 >= 247 && b0 <= 250) { + b1 = parser.parseByte(); + return (b0 - 247) * 256 + b1 + 108; + } + + if (b0 >= 251 && b0 <= 254) { + b1 = parser.parseByte(); + return -(b0 - 251) * 256 - b1 - 108; + } + + throw new Error('Invalid b0 ' + b0); +} + +// Convert the entries returned by `parseDict` to a proper dictionary. +// If a value is a list of one, it is unpacked. +function entriesToObject(entries) { + var o = {}; + for (var i = 0; i < entries.length; i += 1) { + var key = entries[i][0]; + var values = entries[i][1]; + var value = (void 0); + if (values.length === 1) { + value = values[0]; + } else { + value = values; + } + + if (o.hasOwnProperty(key) && !isNaN(o[key])) { + throw new Error('Object ' + o + ' already has key ' + key); + } + + o[key] = value; + } + + return o; +} + +// Parse a `CFF` DICT object. +// A dictionary contains key-value pairs in a compact tokenized format. +function parseCFFDict(data, start, size) { + start = start !== undefined ? start : 0; + var parser = new parse.Parser(data, start); + var entries = []; + var operands = []; + size = size !== undefined ? size : data.length; + + while (parser.relativeOffset < size) { + var op = parser.parseByte(); + + // The first byte for each dict item distinguishes between operator (key) and operand (value). + // Values <= 21 are operators. + if (op <= 21) { + // Two-byte operators have an initial escape byte of 12. + if (op === 12) { + op = 1200 + parser.parseByte(); + } + + entries.push([op, operands]); + operands = []; + } else { + // Since the operands (values) come before the operators (keys), we store all operands in a list + // until we encounter an operator. + operands.push(parseOperand(parser, op)); + } + } + + return entriesToObject(entries); +} + +// Given a String Index (SID), return the value of the string. +// Strings below index 392 are standard CFF strings and are not encoded in the font. +function getCFFString(strings, index) { + if (index <= 390) { + index = cffStandardStrings[index]; + } else { + index = strings[index - 391]; + } + + return index; +} + +// Interpret a dictionary and return a new dictionary with readable keys and values for missing entries. +// This function takes `meta` which is a list of objects containing `operand`, `name` and `default`. +function interpretDict(dict, meta, strings) { + var newDict = {}; + var value; + + // Because we also want to include missing values, we start out from the meta list + // and lookup values in the dict. + for (var i = 0; i < meta.length; i += 1) { + var m = meta[i]; + + if (Array.isArray(m.type)) { + var values = []; + values.length = m.type.length; + for (var j = 0; j < m.type.length; j++) { + value = dict[m.op] !== undefined ? dict[m.op][j] : undefined; + if (value === undefined) { + value = m.value !== undefined && m.value[j] !== undefined ? m.value[j] : null; + } + if (m.type[j] === 'SID') { + value = getCFFString(strings, value); + } + values[j] = value; + } + newDict[m.name] = values; + } else { + value = dict[m.op]; + if (value === undefined) { + value = m.value !== undefined ? m.value : null; + } + + if (m.type === 'SID') { + value = getCFFString(strings, value); + } + newDict[m.name] = value; + } + } + + return newDict; +} + +// Parse the CFF header. +function parseCFFHeader(data, start) { + var header = {}; + header.formatMajor = parse.getCard8(data, start); + header.formatMinor = parse.getCard8(data, start + 1); + header.size = parse.getCard8(data, start + 2); + header.offsetSize = parse.getCard8(data, start + 3); + header.startOffset = start; + header.endOffset = start + 4; + return header; +} + +var TOP_DICT_META = [ + {name: 'version', op: 0, type: 'SID'}, + {name: 'notice', op: 1, type: 'SID'}, + {name: 'copyright', op: 1200, type: 'SID'}, + {name: 'fullName', op: 2, type: 'SID'}, + {name: 'familyName', op: 3, type: 'SID'}, + {name: 'weight', op: 4, type: 'SID'}, + {name: 'isFixedPitch', op: 1201, type: 'number', value: 0}, + {name: 'italicAngle', op: 1202, type: 'number', value: 0}, + {name: 'underlinePosition', op: 1203, type: 'number', value: -100}, + {name: 'underlineThickness', op: 1204, type: 'number', value: 50}, + {name: 'paintType', op: 1205, type: 'number', value: 0}, + {name: 'charstringType', op: 1206, type: 'number', value: 2}, + { + name: 'fontMatrix', + op: 1207, + type: ['real', 'real', 'real', 'real', 'real', 'real'], + value: [0.001, 0, 0, 0.001, 0, 0] + }, + {name: 'uniqueId', op: 13, type: 'number'}, + {name: 'fontBBox', op: 5, type: ['number', 'number', 'number', 'number'], value: [0, 0, 0, 0]}, + {name: 'strokeWidth', op: 1208, type: 'number', value: 0}, + {name: 'xuid', op: 14, type: [], value: null}, + {name: 'charset', op: 15, type: 'offset', value: 0}, + {name: 'encoding', op: 16, type: 'offset', value: 0}, + {name: 'charStrings', op: 17, type: 'offset', value: 0}, + {name: 'private', op: 18, type: ['number', 'offset'], value: [0, 0]}, + {name: 'ros', op: 1230, type: ['SID', 'SID', 'number']}, + {name: 'cidFontVersion', op: 1231, type: 'number', value: 0}, + {name: 'cidFontRevision', op: 1232, type: 'number', value: 0}, + {name: 'cidFontType', op: 1233, type: 'number', value: 0}, + {name: 'cidCount', op: 1234, type: 'number', value: 8720}, + {name: 'uidBase', op: 1235, type: 'number'}, + {name: 'fdArray', op: 1236, type: 'offset'}, + {name: 'fdSelect', op: 1237, type: 'offset'}, + {name: 'fontName', op: 1238, type: 'SID'} +]; + +var PRIVATE_DICT_META = [ + {name: 'subrs', op: 19, type: 'offset', value: 0}, + {name: 'defaultWidthX', op: 20, type: 'number', value: 0}, + {name: 'nominalWidthX', op: 21, type: 'number', value: 0} +]; + +// Parse the CFF top dictionary. A CFF table can contain multiple fonts, each with their own top dictionary. +// The top dictionary contains the essential metadata for the font, together with the private dictionary. +function parseCFFTopDict(data, strings) { + var dict = parseCFFDict(data, 0, data.byteLength); + return interpretDict(dict, TOP_DICT_META, strings); +} + +// Parse the CFF private dictionary. We don't fully parse out all the values, only the ones we need. +function parseCFFPrivateDict(data, start, size, strings) { + var dict = parseCFFDict(data, start, size); + return interpretDict(dict, PRIVATE_DICT_META, strings); +} + +// Returns a list of "Top DICT"s found using an INDEX list. +// Used to read both the usual high-level Top DICTs and also the FDArray +// discovered inside CID-keyed fonts. When a Top DICT has a reference to +// a Private DICT that is read and saved into the Top DICT. +// +// In addition to the expected/optional values as outlined in TOP_DICT_META +// the following values might be saved into the Top DICT. +// +// _subrs [] array of local CFF subroutines from Private DICT +// _subrsBias bias value computed from number of subroutines +// (see calcCFFSubroutineBias() and parseCFFCharstring()) +// _defaultWidthX default widths for CFF characters +// _nominalWidthX bias added to width embedded within glyph description +// +// _privateDict saved copy of parsed Private DICT from Top DICT +function gatherCFFTopDicts(data, start, cffIndex, strings) { + var topDictArray = []; + for (var iTopDict = 0; iTopDict < cffIndex.length; iTopDict += 1) { + var topDictData = new DataView(new Uint8Array(cffIndex[iTopDict]).buffer); + var topDict = parseCFFTopDict(topDictData, strings); + topDict._subrs = []; + topDict._subrsBias = 0; + topDict._defaultWidthX = 0; + topDict._nominalWidthX = 0; + var privateSize = topDict.private[0]; + var privateOffset = topDict.private[1]; + if (privateSize !== 0 && privateOffset !== 0) { + var privateDict = parseCFFPrivateDict(data, privateOffset + start, privateSize, strings); + topDict._defaultWidthX = privateDict.defaultWidthX; + topDict._nominalWidthX = privateDict.nominalWidthX; + if (privateDict.subrs !== 0) { + var subrOffset = privateOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset + start); + topDict._subrs = subrIndex.objects; + topDict._subrsBias = calcCFFSubroutineBias(topDict._subrs); + } + topDict._privateDict = privateDict; + } + topDictArray.push(topDict); + } + return topDictArray; +} + +// Parse the CFF charset table, which contains internal names for all the glyphs. +// This function will return a list of glyph names. +// See Adobe TN #5176 chapter 13, "Charsets". +function parseCFFCharset(data, start, nGlyphs, strings) { + var sid; + var count; + var parser = new parse.Parser(data, start); + + // The .notdef glyph is not included, so subtract 1. + nGlyphs -= 1; + var charset = ['.notdef']; + + var format = parser.parseCard8(); + if (format === 0) { + for (var i = 0; i < nGlyphs; i += 1) { + sid = parser.parseSID(); + charset.push(getCFFString(strings, sid)); + } + } else if (format === 1) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard8(); + for (var i$1 = 0; i$1 <= count; i$1 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else if (format === 2) { + while (charset.length <= nGlyphs) { + sid = parser.parseSID(); + count = parser.parseCard16(); + for (var i$2 = 0; i$2 <= count; i$2 += 1) { + charset.push(getCFFString(strings, sid)); + sid += 1; + } + } + } else { + throw new Error('Unknown charset format ' + format); + } + + return charset; +} + +// Parse the CFF encoding data. Only one encoding can be specified per font. +// See Adobe TN #5176 chapter 12, "Encodings". +function parseCFFEncoding(data, start, charset) { + var code; + var enc = {}; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + var nCodes = parser.parseCard8(); + for (var i = 0; i < nCodes; i += 1) { + code = parser.parseCard8(); + enc[code] = i; + } + } else if (format === 1) { + var nRanges = parser.parseCard8(); + code = 1; + for (var i$1 = 0; i$1 < nRanges; i$1 += 1) { + var first = parser.parseCard8(); + var nLeft = parser.parseCard8(); + for (var j = first; j <= first + nLeft; j += 1) { + enc[j] = code; + code += 1; + } + } + } else { + throw new Error('Unknown encoding format ' + format); + } + + return new CffEncoding(enc, charset); +} + +// Take in charstring code and return a Glyph object. +// The encoding is described in the Type 2 Charstring Format +// https://www.microsoft.com/typography/OTSPEC/charstr2.htm +function parseCFFCharstring(font, glyph, code) { + var c1x; + var c1y; + var c2x; + var c2y; + var p = new Path(); + var stack = []; + var nStems = 0; + var haveWidth = false; + var open = false; + var x = 0; + var y = 0; + var subrs; + var subrsBias; + var defaultWidthX; + var nominalWidthX; + if (font.isCIDFont) { + var fdIndex = font.tables.cff.topDict._fdSelect[glyph.index]; + var fdDict = font.tables.cff.topDict._fdArray[fdIndex]; + subrs = fdDict._subrs; + subrsBias = fdDict._subrsBias; + defaultWidthX = fdDict._defaultWidthX; + nominalWidthX = fdDict._nominalWidthX; + } else { + subrs = font.tables.cff.topDict._subrs; + subrsBias = font.tables.cff.topDict._subrsBias; + defaultWidthX = font.tables.cff.topDict._defaultWidthX; + nominalWidthX = font.tables.cff.topDict._nominalWidthX; + } + var width = defaultWidthX; + + function newContour(x, y) { + if (open) { + p.closePath(); + } + + p.moveTo(x, y); + open = true; + } + + function parseStems() { + var hasWidthArg; + + // The number of stem operators on the stack is always even. + // If the value is uneven, that means a width is specified. + hasWidthArg = stack.length % 2 !== 0; + if (hasWidthArg && !haveWidth) { + width = stack.shift() + nominalWidthX; + } + + nStems += stack.length >> 1; + stack.length = 0; + haveWidth = true; + } + + function parse(code) { + var b1; + var b2; + var b3; + var b4; + var codeIndex; + var subrCode; + var jpx; + var jpy; + var c3x; + var c3y; + var c4x; + var c4y; + + var i = 0; + while (i < code.length) { + var v = code[i]; + i += 1; + switch (v) { + case 1: // hstem + parseStems(); + break; + case 3: // vstem + parseStems(); + break; + case 4: // vmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + newContour(x, y); + break; + case 5: // rlineto + while (stack.length > 0) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 6: // hlineto + while (stack.length > 0) { + x += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + y += stack.shift(); + p.lineTo(x, y); + } + + break; + case 7: // vlineto + while (stack.length > 0) { + y += stack.shift(); + p.lineTo(x, y); + if (stack.length === 0) { + break; + } + + x += stack.shift(); + p.lineTo(x, y); + } + + break; + case 8: // rrcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 10: // callsubr + codeIndex = stack.pop() + subrsBias; + subrCode = subrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 11: // return + return; + case 12: // flex operators + v = code[i]; + i += 1; + switch (v) { + case 35: // flex + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 dx6 dy6 fd flex (12 35) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + y = c4y + stack.shift(); // dy6 + stack.shift(); // flex depth + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 34: // hflex + // |- dx1 dx2 dy2 dx3 dx4 dx5 dx6 hflex (12 34) |- + c1x = x + stack.shift(); // dx1 + c1y = y; // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = y; // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 36: // hflex1 + // |- dx1 dy1 dx2 dy2 dx3 dx4 dx5 dy5 dx6 hflex1 (12 36) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y; // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = c2y; // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + x = c4x + stack.shift(); // dx6 + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + case 37: // flex1 + // |- dx1 dy1 dx2 dy2 dx3 dy3 dx4 dy4 dx5 dy5 d6 flex1 (12 37) |- + c1x = x + stack.shift(); // dx1 + c1y = y + stack.shift(); // dy1 + c2x = c1x + stack.shift(); // dx2 + c2y = c1y + stack.shift(); // dy2 + jpx = c2x + stack.shift(); // dx3 + jpy = c2y + stack.shift(); // dy3 + c3x = jpx + stack.shift(); // dx4 + c3y = jpy + stack.shift(); // dy4 + c4x = c3x + stack.shift(); // dx5 + c4y = c3y + stack.shift(); // dy5 + if (Math.abs(c4x - x) > Math.abs(c4y - y)) { + x = c4x + stack.shift(); + } else { + y = c4y + stack.shift(); + } + + p.curveTo(c1x, c1y, c2x, c2y, jpx, jpy); + p.curveTo(c3x, c3y, c4x, c4y, x, y); + break; + default: + console.log('Glyph ' + glyph.index + ': unknown operator ' + 1200 + v); + stack.length = 0; + } + break; + case 14: // endchar + if (stack.length > 0 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + if (open) { + p.closePath(); + open = false; + } + + break; + case 18: // hstemhm + parseStems(); + break; + case 19: // hintmask + case 20: // cntrmask + parseStems(); + i += (nStems + 7) >> 3; + break; + case 21: // rmoveto + if (stack.length > 2 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + y += stack.pop(); + x += stack.pop(); + newContour(x, y); + break; + case 22: // hmoveto + if (stack.length > 1 && !haveWidth) { + width = stack.shift() + nominalWidthX; + haveWidth = true; + } + + x += stack.pop(); + newContour(x, y); + break; + case 23: // vstemhm + parseStems(); + break; + case 24: // rcurveline + while (stack.length > 2) { + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + break; + case 25: // rlinecurve + while (stack.length > 6) { + x += stack.shift(); + y += stack.shift(); + p.lineTo(x, y); + } + + c1x = x + stack.shift(); + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + break; + case 26: // vvcurveto + if (stack.length % 2) { + x += stack.shift(); + } + + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x; + y = c2y + stack.shift(); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 27: // hhcurveto + if (stack.length % 2) { + y += stack.shift(); + } + + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y; + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 28: // shortint + b1 = code[i]; + b2 = code[i + 1]; + stack.push(((b1 << 24) | (b2 << 16)) >> 16); + i += 2; + break; + case 29: // callgsubr + codeIndex = stack.pop() + font.gsubrsBias; + subrCode = font.gsubrs[codeIndex]; + if (subrCode) { + parse(subrCode); + } + + break; + case 30: // vhcurveto + while (stack.length > 0) { + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + case 31: // hvcurveto + while (stack.length > 0) { + c1x = x + stack.shift(); + c1y = y; + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + y = c2y + stack.shift(); + x = c2x + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + if (stack.length === 0) { + break; + } + + c1x = x; + c1y = y + stack.shift(); + c2x = c1x + stack.shift(); + c2y = c1y + stack.shift(); + x = c2x + stack.shift(); + y = c2y + (stack.length === 1 ? stack.shift() : 0); + p.curveTo(c1x, c1y, c2x, c2y, x, y); + } + + break; + default: + if (v < 32) { + console.log('Glyph ' + glyph.index + ': unknown operator ' + v); + } else if (v < 247) { + stack.push(v - 139); + } else if (v < 251) { + b1 = code[i]; + i += 1; + stack.push((v - 247) * 256 + b1 + 108); + } else if (v < 255) { + b1 = code[i]; + i += 1; + stack.push(-(v - 251) * 256 - b1 - 108); + } else { + b1 = code[i]; + b2 = code[i + 1]; + b3 = code[i + 2]; + b4 = code[i + 3]; + i += 4; + stack.push(((b1 << 24) | (b2 << 16) | (b3 << 8) | b4) / 65536); + } + } + } + } + + parse(code); + + glyph.advanceWidth = width; + return p; +} + +function parseCFFFDSelect(data, start, nGlyphs, fdArrayCount) { + var fdSelect = []; + var fdIndex; + var parser = new parse.Parser(data, start); + var format = parser.parseCard8(); + if (format === 0) { + // Simple list of nGlyphs elements + for (var iGid = 0; iGid < nGlyphs; iGid++) { + fdIndex = parser.parseCard8(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + fdSelect.push(fdIndex); + } + } else if (format === 3) { + // Ranges + var nRanges = parser.parseCard16(); + var first = parser.parseCard16(); + if (first !== 0) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad initial GID ' + first); + } + var next; + for (var iRange = 0; iRange < nRanges; iRange++) { + fdIndex = parser.parseCard8(); + next = parser.parseCard16(); + if (fdIndex >= fdArrayCount) { + throw new Error('CFF table CID Font FDSelect has bad FD index value ' + fdIndex + ' (FD count ' + fdArrayCount + ')'); + } + if (next > nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad GID ' + next); + } + for (; first < next; first++) { + fdSelect.push(fdIndex); + } + first = next; + } + if (next !== nGlyphs) { + throw new Error('CFF Table CID Font FDSelect format 3 range has bad final GID ' + next); + } + } else { + throw new Error('CFF Table CID Font FDSelect table has unsupported format ' + format); + } + return fdSelect; +} + +// Parse the `CFF` table, which contains the glyph outlines in PostScript format. +function parseCFFTable(data, start, font, opt) { + font.tables.cff = {}; + var header = parseCFFHeader(data, start); + var nameIndex = parseCFFIndex(data, header.endOffset, parse.bytesToString); + var topDictIndex = parseCFFIndex(data, nameIndex.endOffset); + var stringIndex = parseCFFIndex(data, topDictIndex.endOffset, parse.bytesToString); + var globalSubrIndex = parseCFFIndex(data, stringIndex.endOffset); + font.gsubrs = globalSubrIndex.objects; + font.gsubrsBias = calcCFFSubroutineBias(font.gsubrs); + + var topDictArray = gatherCFFTopDicts(data, start, topDictIndex.objects, stringIndex.objects); + if (topDictArray.length !== 1) { + throw new Error('CFF table has too many fonts in \'FontSet\' - count of fonts NameIndex.length = ' + topDictArray.length); + } + + var topDict = topDictArray[0]; + font.tables.cff.topDict = topDict; + + if (topDict._privateDict) { + font.defaultWidthX = topDict._privateDict.defaultWidthX; + font.nominalWidthX = topDict._privateDict.nominalWidthX; + } + + if (topDict.ros[0] !== undefined && topDict.ros[1] !== undefined) { + font.isCIDFont = true; + } + + if (font.isCIDFont) { + var fdArrayOffset = topDict.fdArray; + var fdSelectOffset = topDict.fdSelect; + if (fdArrayOffset === 0 || fdSelectOffset === 0) { + throw new Error('Font is marked as a CID font, but FDArray and/or FDSelect information is missing'); + } + fdArrayOffset += start; + var fdArrayIndex = parseCFFIndex(data, fdArrayOffset); + var fdArray = gatherCFFTopDicts(data, start, fdArrayIndex.objects, stringIndex.objects); + topDict._fdArray = fdArray; + fdSelectOffset += start; + topDict._fdSelect = parseCFFFDSelect(data, fdSelectOffset, font.numGlyphs, fdArray.length); + } + + var privateDictOffset = start + topDict.private[1]; + var privateDict = parseCFFPrivateDict(data, privateDictOffset, topDict.private[0], stringIndex.objects); + font.defaultWidthX = privateDict.defaultWidthX; + font.nominalWidthX = privateDict.nominalWidthX; + + if (privateDict.subrs !== 0) { + var subrOffset = privateDictOffset + privateDict.subrs; + var subrIndex = parseCFFIndex(data, subrOffset); + font.subrs = subrIndex.objects; + font.subrsBias = calcCFFSubroutineBias(font.subrs); + } else { + font.subrs = []; + font.subrsBias = 0; + } + + // Offsets in the top dict are relative to the beginning of the CFF data, so add the CFF start offset. + var charStringsIndex; + if (opt.lowMemory) { + charStringsIndex = parseCFFIndexLowMemory(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.offsets.length; + } else { + charStringsIndex = parseCFFIndex(data, start + topDict.charStrings); + font.nGlyphs = charStringsIndex.objects.length; + } + + var charset = parseCFFCharset(data, start + topDict.charset, font.nGlyphs, stringIndex.objects); + if (topDict.encoding === 0) { + // Standard encoding + font.cffEncoding = new CffEncoding(cffStandardEncoding, charset); + } else if (topDict.encoding === 1) { + // Expert encoding + font.cffEncoding = new CffEncoding(cffExpertEncoding, charset); + } else { + font.cffEncoding = parseCFFEncoding(data, start + topDict.encoding, charset); + } + + // Prefer the CMAP encoding to the CFF encoding. + font.encoding = font.encoding || font.cffEncoding; + + font.glyphs = new glyphset.GlyphSet(font); + if (opt.lowMemory) { + font._push = function(i) { + var charString = getCffIndexObject(i, charStringsIndex.offsets, data, start + topDict.charStrings); + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + }; + } else { + for (var i = 0; i < font.nGlyphs; i += 1) { + var charString = charStringsIndex.objects[i]; + font.glyphs.push(i, glyphset.cffGlyphLoader(font, i, parseCFFCharstring, charString)); + } + } +} + +// Convert a string to a String ID (SID). +// The list of strings is modified in place. +function encodeString(s, strings) { + var sid; + + // Is the string in the CFF standard strings? + var i = cffStandardStrings.indexOf(s); + if (i >= 0) { + sid = i; + } + + // Is the string already in the string index? + i = strings.indexOf(s); + if (i >= 0) { + sid = i + cffStandardStrings.length; + } else { + sid = cffStandardStrings.length + strings.length; + strings.push(s); + } + + return sid; +} + +function makeHeader() { + return new table.Record('Header', [ + {name: 'major', type: 'Card8', value: 1}, + {name: 'minor', type: 'Card8', value: 0}, + {name: 'hdrSize', type: 'Card8', value: 4}, + {name: 'major', type: 'Card8', value: 1} + ]); +} + +function makeNameIndex(fontNames) { + var t = new table.Record('Name INDEX', [ + {name: 'names', type: 'INDEX', value: []} + ]); + t.names = []; + for (var i = 0; i < fontNames.length; i += 1) { + t.names.push({name: 'name_' + i, type: 'NAME', value: fontNames[i]}); + } + + return t; +} + +// Given a dictionary's metadata, create a DICT structure. +function makeDict(meta, attrs, strings) { + var m = {}; + for (var i = 0; i < meta.length; i += 1) { + var entry = meta[i]; + var value = attrs[entry.name]; + if (value !== undefined && !equals(value, entry.value)) { + if (entry.type === 'SID') { + value = encodeString(value, strings); + } + + m[entry.op] = {name: entry.name, type: entry.type, value: value}; + } + } + + return m; +} + +// The Top DICT houses the global font attributes. +function makeTopDict(attrs, strings) { + var t = new table.Record('Top DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(TOP_DICT_META, attrs, strings); + return t; +} + +function makeTopDictIndex(topDict) { + var t = new table.Record('Top DICT INDEX', [ + {name: 'topDicts', type: 'INDEX', value: []} + ]); + t.topDicts = [{name: 'topDict_0', type: 'TABLE', value: topDict}]; + return t; +} + +function makeStringIndex(strings) { + var t = new table.Record('String INDEX', [ + {name: 'strings', type: 'INDEX', value: []} + ]); + t.strings = []; + for (var i = 0; i < strings.length; i += 1) { + t.strings.push({name: 'string_' + i, type: 'STRING', value: strings[i]}); + } + + return t; +} + +function makeGlobalSubrIndex() { + // Currently we don't use subroutines. + return new table.Record('Global Subr INDEX', [ + {name: 'subrs', type: 'INDEX', value: []} + ]); +} + +function makeCharsets(glyphNames, strings) { + var t = new table.Record('Charsets', [ + {name: 'format', type: 'Card8', value: 0} + ]); + for (var i = 0; i < glyphNames.length; i += 1) { + var glyphName = glyphNames[i]; + var glyphSID = encodeString(glyphName, strings); + t.fields.push({name: 'glyph_' + i, type: 'SID', value: glyphSID}); + } + + return t; +} + +function glyphToOps(glyph) { + var ops = []; + var path = glyph.path; + ops.push({name: 'width', type: 'NUMBER', value: glyph.advanceWidth}); + var x = 0; + var y = 0; + for (var i = 0; i < path.commands.length; i += 1) { + var dx = (void 0); + var dy = (void 0); + var cmd = path.commands[i]; + if (cmd.type === 'Q') { + // CFF only supports bézier curves, so convert the quad to a bézier. + var _13 = 1 / 3; + var _23 = 2 / 3; + + // We're going to create a new command so we don't change the original path. + // Since all coordinates are relative, we round() them ASAP to avoid propagating errors. + cmd = { + type: 'C', + x: cmd.x, + y: cmd.y, + x1: Math.round(_13 * x + _23 * cmd.x1), + y1: Math.round(_13 * y + _23 * cmd.y1), + x2: Math.round(_13 * cmd.x + _23 * cmd.x1), + y2: Math.round(_13 * cmd.y + _23 * cmd.y1) + }; + } + + if (cmd.type === 'M') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rmoveto', type: 'OP', value: 21}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'L') { + dx = Math.round(cmd.x - x); + dy = Math.round(cmd.y - y); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rlineto', type: 'OP', value: 5}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } else if (cmd.type === 'C') { + var dx1 = Math.round(cmd.x1 - x); + var dy1 = Math.round(cmd.y1 - y); + var dx2 = Math.round(cmd.x2 - cmd.x1); + var dy2 = Math.round(cmd.y2 - cmd.y1); + dx = Math.round(cmd.x - cmd.x2); + dy = Math.round(cmd.y - cmd.y2); + ops.push({name: 'dx1', type: 'NUMBER', value: dx1}); + ops.push({name: 'dy1', type: 'NUMBER', value: dy1}); + ops.push({name: 'dx2', type: 'NUMBER', value: dx2}); + ops.push({name: 'dy2', type: 'NUMBER', value: dy2}); + ops.push({name: 'dx', type: 'NUMBER', value: dx}); + ops.push({name: 'dy', type: 'NUMBER', value: dy}); + ops.push({name: 'rrcurveto', type: 'OP', value: 8}); + x = Math.round(cmd.x); + y = Math.round(cmd.y); + } + + // Contours are closed automatically. + } + + ops.push({name: 'endchar', type: 'OP', value: 14}); + return ops; +} + +function makeCharStringsIndex(glyphs) { + var t = new table.Record('CharStrings INDEX', [ + {name: 'charStrings', type: 'INDEX', value: []} + ]); + + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var ops = glyphToOps(glyph); + t.charStrings.push({name: glyph.name, type: 'CHARSTRING', value: ops}); + } + + return t; +} + +function makePrivateDict(attrs, strings) { + var t = new table.Record('Private DICT', [ + {name: 'dict', type: 'DICT', value: {}} + ]); + t.dict = makeDict(PRIVATE_DICT_META, attrs, strings); + return t; +} + +function makeCFFTable(glyphs, options) { + var t = new table.Table('CFF ', [ + {name: 'header', type: 'RECORD'}, + {name: 'nameIndex', type: 'RECORD'}, + {name: 'topDictIndex', type: 'RECORD'}, + {name: 'stringIndex', type: 'RECORD'}, + {name: 'globalSubrIndex', type: 'RECORD'}, + {name: 'charsets', type: 'RECORD'}, + {name: 'charStringsIndex', type: 'RECORD'}, + {name: 'privateDict', type: 'RECORD'} + ]); + + var fontScale = 1 / options.unitsPerEm; + // We use non-zero values for the offsets so that the DICT encodes them. + // This is important because the size of the Top DICT plays a role in offset calculation, + // and the size shouldn't change after we've written correct offsets. + var attrs = { + version: options.version, + fullName: options.fullName, + familyName: options.familyName, + weight: options.weightName, + fontBBox: options.fontBBox || [0, 0, 0, 0], + fontMatrix: [fontScale, 0, 0, fontScale, 0, 0], + charset: 999, + encoding: 0, + charStrings: 999, + private: [0, 999] + }; + + var privateAttrs = {}; + + var glyphNames = []; + var glyph; + + // Skip first glyph (.notdef) + for (var i = 1; i < glyphs.length; i += 1) { + glyph = glyphs.get(i); + glyphNames.push(glyph.name); + } + + var strings = []; + + t.header = makeHeader(); + t.nameIndex = makeNameIndex([options.postScriptName]); + var topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + t.globalSubrIndex = makeGlobalSubrIndex(); + t.charsets = makeCharsets(glyphNames, strings); + t.charStringsIndex = makeCharStringsIndex(glyphs); + t.privateDict = makePrivateDict(privateAttrs, strings); + + // Needs to come at the end, to encode all custom strings used in the font. + t.stringIndex = makeStringIndex(strings); + + var startOffset = t.header.sizeOf() + + t.nameIndex.sizeOf() + + t.topDictIndex.sizeOf() + + t.stringIndex.sizeOf() + + t.globalSubrIndex.sizeOf(); + attrs.charset = startOffset; + + // We use the CFF standard encoding; proper encoding will be handled in cmap. + attrs.encoding = 0; + attrs.charStrings = attrs.charset + t.charsets.sizeOf(); + attrs.private[1] = attrs.charStrings + t.charStringsIndex.sizeOf(); + + // Recreate the Top DICT INDEX with the correct offsets. + topDict = makeTopDict(attrs, strings); + t.topDictIndex = makeTopDictIndex(topDict); + + return t; +} + +var cff = { parse: parseCFFTable, make: makeCFFTable }; + +// The `head` table contains global information about the font. + +// Parse the header `head` table +function parseHeadTable(data, start) { + var head = {}; + var p = new parse.Parser(data, start); + head.version = p.parseVersion(); + head.fontRevision = Math.round(p.parseFixed() * 1000) / 1000; + head.checkSumAdjustment = p.parseULong(); + head.magicNumber = p.parseULong(); + check.argument(head.magicNumber === 0x5F0F3CF5, 'Font header has wrong magic number.'); + head.flags = p.parseUShort(); + head.unitsPerEm = p.parseUShort(); + head.created = p.parseLongDateTime(); + head.modified = p.parseLongDateTime(); + head.xMin = p.parseShort(); + head.yMin = p.parseShort(); + head.xMax = p.parseShort(); + head.yMax = p.parseShort(); + head.macStyle = p.parseUShort(); + head.lowestRecPPEM = p.parseUShort(); + head.fontDirectionHint = p.parseShort(); + head.indexToLocFormat = p.parseShort(); + head.glyphDataFormat = p.parseShort(); + return head; +} + +function makeHeadTable(options) { + // Apple Mac timestamp epoch is 01/01/1904 not 01/01/1970 + var timestamp = Math.round(new Date().getTime() / 1000) + 2082844800; + var createdTimestamp = timestamp; + + if (options.createdTimestamp) { + createdTimestamp = options.createdTimestamp + 2082844800; + } + + return new table.Table('head', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'fontRevision', type: 'FIXED', value: 0x00010000}, + {name: 'checkSumAdjustment', type: 'ULONG', value: 0}, + {name: 'magicNumber', type: 'ULONG', value: 0x5F0F3CF5}, + {name: 'flags', type: 'USHORT', value: 0}, + {name: 'unitsPerEm', type: 'USHORT', value: 1000}, + {name: 'created', type: 'LONGDATETIME', value: createdTimestamp}, + {name: 'modified', type: 'LONGDATETIME', value: timestamp}, + {name: 'xMin', type: 'SHORT', value: 0}, + {name: 'yMin', type: 'SHORT', value: 0}, + {name: 'xMax', type: 'SHORT', value: 0}, + {name: 'yMax', type: 'SHORT', value: 0}, + {name: 'macStyle', type: 'USHORT', value: 0}, + {name: 'lowestRecPPEM', type: 'USHORT', value: 0}, + {name: 'fontDirectionHint', type: 'SHORT', value: 2}, + {name: 'indexToLocFormat', type: 'SHORT', value: 0}, + {name: 'glyphDataFormat', type: 'SHORT', value: 0} + ], options); +} + +var head = { parse: parseHeadTable, make: makeHeadTable }; + +// The `hhea` table contains information for horizontal layout. + +// Parse the horizontal header `hhea` table +function parseHheaTable(data, start) { + var hhea = {}; + var p = new parse.Parser(data, start); + hhea.version = p.parseVersion(); + hhea.ascender = p.parseShort(); + hhea.descender = p.parseShort(); + hhea.lineGap = p.parseShort(); + hhea.advanceWidthMax = p.parseUShort(); + hhea.minLeftSideBearing = p.parseShort(); + hhea.minRightSideBearing = p.parseShort(); + hhea.xMaxExtent = p.parseShort(); + hhea.caretSlopeRise = p.parseShort(); + hhea.caretSlopeRun = p.parseShort(); + hhea.caretOffset = p.parseShort(); + p.relativeOffset += 8; + hhea.metricDataFormat = p.parseShort(); + hhea.numberOfHMetrics = p.parseUShort(); + return hhea; +} + +function makeHheaTable(options) { + return new table.Table('hhea', [ + {name: 'version', type: 'FIXED', value: 0x00010000}, + {name: 'ascender', type: 'FWORD', value: 0}, + {name: 'descender', type: 'FWORD', value: 0}, + {name: 'lineGap', type: 'FWORD', value: 0}, + {name: 'advanceWidthMax', type: 'UFWORD', value: 0}, + {name: 'minLeftSideBearing', type: 'FWORD', value: 0}, + {name: 'minRightSideBearing', type: 'FWORD', value: 0}, + {name: 'xMaxExtent', type: 'FWORD', value: 0}, + {name: 'caretSlopeRise', type: 'SHORT', value: 1}, + {name: 'caretSlopeRun', type: 'SHORT', value: 0}, + {name: 'caretOffset', type: 'SHORT', value: 0}, + {name: 'reserved1', type: 'SHORT', value: 0}, + {name: 'reserved2', type: 'SHORT', value: 0}, + {name: 'reserved3', type: 'SHORT', value: 0}, + {name: 'reserved4', type: 'SHORT', value: 0}, + {name: 'metricDataFormat', type: 'SHORT', value: 0}, + {name: 'numberOfHMetrics', type: 'USHORT', value: 0} + ], options); +} + +var hhea = { parse: parseHheaTable, make: makeHheaTable }; + +// The `hmtx` table contains the horizontal metrics for all glyphs. + +function parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs) { + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + var glyph = glyphs.get(i); + glyph.advanceWidth = advanceWidth; + glyph.leftSideBearing = leftSideBearing; + } +} + +function parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs) { + font._hmtxTableData = {}; + + var advanceWidth; + var leftSideBearing; + var p = new parse.Parser(data, start); + for (var i = 0; i < numGlyphs; i += 1) { + // If the font is monospaced, only one entry is needed. This last entry applies to all subsequent glyphs. + if (i < numMetrics) { + advanceWidth = p.parseUShort(); + leftSideBearing = p.parseShort(); + } + + font._hmtxTableData[i] = { + advanceWidth: advanceWidth, + leftSideBearing: leftSideBearing + }; + } +} + +// Parse the `hmtx` table, which contains the horizontal metrics for all glyphs. +// This function augments the glyph array, adding the advanceWidth and leftSideBearing to each glyph. +function parseHmtxTable(font, data, start, numMetrics, numGlyphs, glyphs, opt) { + if (opt.lowMemory) + { parseHmtxTableOnLowMemory(font, data, start, numMetrics, numGlyphs); } + else + { parseHmtxTableAll(data, start, numMetrics, numGlyphs, glyphs); } +} + +function makeHmtxTable(glyphs) { + var t = new table.Table('hmtx', []); + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs.get(i); + var advanceWidth = glyph.advanceWidth || 0; + var leftSideBearing = glyph.leftSideBearing || 0; + t.fields.push({name: 'advanceWidth_' + i, type: 'USHORT', value: advanceWidth}); + t.fields.push({name: 'leftSideBearing_' + i, type: 'SHORT', value: leftSideBearing}); + } + + return t; +} + +var hmtx = { parse: parseHmtxTable, make: makeHmtxTable }; + +// The `ltag` table stores IETF BCP-47 language tags. It allows supporting + +function makeLtagTable(tags) { + var result = new table.Table('ltag', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'numTags', type: 'ULONG', value: tags.length} + ]); + + var stringPool = ''; + var stringPoolOffset = 12 + tags.length * 4; + for (var i = 0; i < tags.length; ++i) { + var pos = stringPool.indexOf(tags[i]); + if (pos < 0) { + pos = stringPool.length; + stringPool += tags[i]; + } + + result.fields.push({name: 'offset ' + i, type: 'USHORT', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + i, type: 'USHORT', value: tags[i].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + return result; +} + +function parseLtagTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported ltag table version.'); + // The 'ltag' specification does not define any flags; skip the field. + p.skip('uLong', 1); + var numTags = p.parseULong(); + + var tags = []; + for (var i = 0; i < numTags; i++) { + var tag = ''; + var offset = start + p.parseUShort(); + var length = p.parseUShort(); + for (var j = offset; j < offset + length; ++j) { + tag += String.fromCharCode(data.getInt8(j)); + } + + tags.push(tag); + } + + return tags; +} + +var ltag = { make: makeLtagTable, parse: parseLtagTable }; + +// The `maxp` table establishes the memory requirements for the font. + +// Parse the maximum profile `maxp` table. +function parseMaxpTable(data, start) { + var maxp = {}; + var p = new parse.Parser(data, start); + maxp.version = p.parseVersion(); + maxp.numGlyphs = p.parseUShort(); + if (maxp.version === 1.0) { + maxp.maxPoints = p.parseUShort(); + maxp.maxContours = p.parseUShort(); + maxp.maxCompositePoints = p.parseUShort(); + maxp.maxCompositeContours = p.parseUShort(); + maxp.maxZones = p.parseUShort(); + maxp.maxTwilightPoints = p.parseUShort(); + maxp.maxStorage = p.parseUShort(); + maxp.maxFunctionDefs = p.parseUShort(); + maxp.maxInstructionDefs = p.parseUShort(); + maxp.maxStackElements = p.parseUShort(); + maxp.maxSizeOfInstructions = p.parseUShort(); + maxp.maxComponentElements = p.parseUShort(); + maxp.maxComponentDepth = p.parseUShort(); + } + + return maxp; +} + +function makeMaxpTable(numGlyphs) { + return new table.Table('maxp', [ + {name: 'version', type: 'FIXED', value: 0x00005000}, + {name: 'numGlyphs', type: 'USHORT', value: numGlyphs} + ]); +} + +var maxp = { parse: parseMaxpTable, make: makeMaxpTable }; + +// The `name` naming table. + +// NameIDs for the name table. +var nameTableNames = [ + 'copyright', // 0 + 'fontFamily', // 1 + 'fontSubfamily', // 2 + 'uniqueID', // 3 + 'fullName', // 4 + 'version', // 5 + 'postScriptName', // 6 + 'trademark', // 7 + 'manufacturer', // 8 + 'designer', // 9 + 'description', // 10 + 'manufacturerURL', // 11 + 'designerURL', // 12 + 'license', // 13 + 'licenseURL', // 14 + 'reserved', // 15 + 'preferredFamily', // 16 + 'preferredSubfamily', // 17 + 'compatibleFullName', // 18 + 'sampleText', // 19 + 'postScriptFindFontName', // 20 + 'wwsFamily', // 21 + 'wwsSubfamily' // 22 +]; + +var macLanguages = { + 0: 'en', + 1: 'fr', + 2: 'de', + 3: 'it', + 4: 'nl', + 5: 'sv', + 6: 'es', + 7: 'da', + 8: 'pt', + 9: 'no', + 10: 'he', + 11: 'ja', + 12: 'ar', + 13: 'fi', + 14: 'el', + 15: 'is', + 16: 'mt', + 17: 'tr', + 18: 'hr', + 19: 'zh-Hant', + 20: 'ur', + 21: 'hi', + 22: 'th', + 23: 'ko', + 24: 'lt', + 25: 'pl', + 26: 'hu', + 27: 'es', + 28: 'lv', + 29: 'se', + 30: 'fo', + 31: 'fa', + 32: 'ru', + 33: 'zh', + 34: 'nl-BE', + 35: 'ga', + 36: 'sq', + 37: 'ro', + 38: 'cz', + 39: 'sk', + 40: 'si', + 41: 'yi', + 42: 'sr', + 43: 'mk', + 44: 'bg', + 45: 'uk', + 46: 'be', + 47: 'uz', + 48: 'kk', + 49: 'az-Cyrl', + 50: 'az-Arab', + 51: 'hy', + 52: 'ka', + 53: 'mo', + 54: 'ky', + 55: 'tg', + 56: 'tk', + 57: 'mn-CN', + 58: 'mn', + 59: 'ps', + 60: 'ks', + 61: 'ku', + 62: 'sd', + 63: 'bo', + 64: 'ne', + 65: 'sa', + 66: 'mr', + 67: 'bn', + 68: 'as', + 69: 'gu', + 70: 'pa', + 71: 'or', + 72: 'ml', + 73: 'kn', + 74: 'ta', + 75: 'te', + 76: 'si', + 77: 'my', + 78: 'km', + 79: 'lo', + 80: 'vi', + 81: 'id', + 82: 'tl', + 83: 'ms', + 84: 'ms-Arab', + 85: 'am', + 86: 'ti', + 87: 'om', + 88: 'so', + 89: 'sw', + 90: 'rw', + 91: 'rn', + 92: 'ny', + 93: 'mg', + 94: 'eo', + 128: 'cy', + 129: 'eu', + 130: 'ca', + 131: 'la', + 132: 'qu', + 133: 'gn', + 134: 'ay', + 135: 'tt', + 136: 'ug', + 137: 'dz', + 138: 'jv', + 139: 'su', + 140: 'gl', + 141: 'af', + 142: 'br', + 143: 'iu', + 144: 'gd', + 145: 'gv', + 146: 'ga', + 147: 'to', + 148: 'el-polyton', + 149: 'kl', + 150: 'az', + 151: 'nn' +}; + +// MacOS language ID → MacOS script ID +// +// Note that the script ID is not sufficient to determine what encoding +// to use in TrueType files. For some languages, MacOS used a modification +// of a mainstream script. For example, an Icelandic name would be stored +// with smRoman in the TrueType naming table, but the actual encoding +// is a special Icelandic version of the normal Macintosh Roman encoding. +// As another example, Inuktitut uses an 8-bit encoding for Canadian Aboriginal +// Syllables but MacOS had run out of available script codes, so this was +// done as a (pretty radical) "modification" of Ethiopic. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageToScript = { + 0: 0, // langEnglish → smRoman + 1: 0, // langFrench → smRoman + 2: 0, // langGerman → smRoman + 3: 0, // langItalian → smRoman + 4: 0, // langDutch → smRoman + 5: 0, // langSwedish → smRoman + 6: 0, // langSpanish → smRoman + 7: 0, // langDanish → smRoman + 8: 0, // langPortuguese → smRoman + 9: 0, // langNorwegian → smRoman + 10: 5, // langHebrew → smHebrew + 11: 1, // langJapanese → smJapanese + 12: 4, // langArabic → smArabic + 13: 0, // langFinnish → smRoman + 14: 6, // langGreek → smGreek + 15: 0, // langIcelandic → smRoman (modified) + 16: 0, // langMaltese → smRoman + 17: 0, // langTurkish → smRoman (modified) + 18: 0, // langCroatian → smRoman (modified) + 19: 2, // langTradChinese → smTradChinese + 20: 4, // langUrdu → smArabic + 21: 9, // langHindi → smDevanagari + 22: 21, // langThai → smThai + 23: 3, // langKorean → smKorean + 24: 29, // langLithuanian → smCentralEuroRoman + 25: 29, // langPolish → smCentralEuroRoman + 26: 29, // langHungarian → smCentralEuroRoman + 27: 29, // langEstonian → smCentralEuroRoman + 28: 29, // langLatvian → smCentralEuroRoman + 29: 0, // langSami → smRoman + 30: 0, // langFaroese → smRoman (modified) + 31: 4, // langFarsi → smArabic (modified) + 32: 7, // langRussian → smCyrillic + 33: 25, // langSimpChinese → smSimpChinese + 34: 0, // langFlemish → smRoman + 35: 0, // langIrishGaelic → smRoman (modified) + 36: 0, // langAlbanian → smRoman + 37: 0, // langRomanian → smRoman (modified) + 38: 29, // langCzech → smCentralEuroRoman + 39: 29, // langSlovak → smCentralEuroRoman + 40: 0, // langSlovenian → smRoman (modified) + 41: 5, // langYiddish → smHebrew + 42: 7, // langSerbian → smCyrillic + 43: 7, // langMacedonian → smCyrillic + 44: 7, // langBulgarian → smCyrillic + 45: 7, // langUkrainian → smCyrillic (modified) + 46: 7, // langByelorussian → smCyrillic + 47: 7, // langUzbek → smCyrillic + 48: 7, // langKazakh → smCyrillic + 49: 7, // langAzerbaijani → smCyrillic + 50: 4, // langAzerbaijanAr → smArabic + 51: 24, // langArmenian → smArmenian + 52: 23, // langGeorgian → smGeorgian + 53: 7, // langMoldavian → smCyrillic + 54: 7, // langKirghiz → smCyrillic + 55: 7, // langTajiki → smCyrillic + 56: 7, // langTurkmen → smCyrillic + 57: 27, // langMongolian → smMongolian + 58: 7, // langMongolianCyr → smCyrillic + 59: 4, // langPashto → smArabic + 60: 4, // langKurdish → smArabic + 61: 4, // langKashmiri → smArabic + 62: 4, // langSindhi → smArabic + 63: 26, // langTibetan → smTibetan + 64: 9, // langNepali → smDevanagari + 65: 9, // langSanskrit → smDevanagari + 66: 9, // langMarathi → smDevanagari + 67: 13, // langBengali → smBengali + 68: 13, // langAssamese → smBengali + 69: 11, // langGujarati → smGujarati + 70: 10, // langPunjabi → smGurmukhi + 71: 12, // langOriya → smOriya + 72: 17, // langMalayalam → smMalayalam + 73: 16, // langKannada → smKannada + 74: 14, // langTamil → smTamil + 75: 15, // langTelugu → smTelugu + 76: 18, // langSinhalese → smSinhalese + 77: 19, // langBurmese → smBurmese + 78: 20, // langKhmer → smKhmer + 79: 22, // langLao → smLao + 80: 30, // langVietnamese → smVietnamese + 81: 0, // langIndonesian → smRoman + 82: 0, // langTagalog → smRoman + 83: 0, // langMalayRoman → smRoman + 84: 4, // langMalayArabic → smArabic + 85: 28, // langAmharic → smEthiopic + 86: 28, // langTigrinya → smEthiopic + 87: 28, // langOromo → smEthiopic + 88: 0, // langSomali → smRoman + 89: 0, // langSwahili → smRoman + 90: 0, // langKinyarwanda → smRoman + 91: 0, // langRundi → smRoman + 92: 0, // langNyanja → smRoman + 93: 0, // langMalagasy → smRoman + 94: 0, // langEsperanto → smRoman + 128: 0, // langWelsh → smRoman (modified) + 129: 0, // langBasque → smRoman + 130: 0, // langCatalan → smRoman + 131: 0, // langLatin → smRoman + 132: 0, // langQuechua → smRoman + 133: 0, // langGuarani → smRoman + 134: 0, // langAymara → smRoman + 135: 7, // langTatar → smCyrillic + 136: 4, // langUighur → smArabic + 137: 26, // langDzongkha → smTibetan + 138: 0, // langJavaneseRom → smRoman + 139: 0, // langSundaneseRom → smRoman + 140: 0, // langGalician → smRoman + 141: 0, // langAfrikaans → smRoman + 142: 0, // langBreton → smRoman (modified) + 143: 28, // langInuktitut → smEthiopic (modified) + 144: 0, // langScottishGaelic → smRoman (modified) + 145: 0, // langManxGaelic → smRoman (modified) + 146: 0, // langIrishGaelicScript → smRoman (modified) + 147: 0, // langTongan → smRoman + 148: 6, // langGreekAncient → smRoman + 149: 0, // langGreenlandic → smRoman + 150: 0, // langAzerbaijanRoman → smRoman + 151: 0 // langNynorsk → smRoman +}; + +// While Microsoft indicates a region/country for all its language +// IDs, we omit the region code if it's equal to the "most likely +// region subtag" according to Unicode CLDR. For scripts, we omit +// the subtag if it is equal to the Suppress-Script entry in the +// IANA language subtag registry for IETF BCP 47. +// +// For example, Microsoft states that its language code 0x041A is +// Croatian in Croatia. We transform this to the BCP 47 language code 'hr' +// and not 'hr-HR' because Croatia is the default country for Croatian, +// according to Unicode CLDR. As another example, Microsoft states +// that 0x101A is Croatian (Latin) in Bosnia-Herzegovina. We transform +// this to 'hr-BA' and not 'hr-Latn-BA' because Latin is the default script +// for the Croatian language, according to IANA. +// +// http://www.unicode.org/cldr/charts/latest/supplemental/likely_subtags.html +// http://www.iana.org/assignments/language-subtag-registry/language-subtag-registry +var windowsLanguages = { + 0x0436: 'af', + 0x041C: 'sq', + 0x0484: 'gsw', + 0x045E: 'am', + 0x1401: 'ar-DZ', + 0x3C01: 'ar-BH', + 0x0C01: 'ar', + 0x0801: 'ar-IQ', + 0x2C01: 'ar-JO', + 0x3401: 'ar-KW', + 0x3001: 'ar-LB', + 0x1001: 'ar-LY', + 0x1801: 'ary', + 0x2001: 'ar-OM', + 0x4001: 'ar-QA', + 0x0401: 'ar-SA', + 0x2801: 'ar-SY', + 0x1C01: 'aeb', + 0x3801: 'ar-AE', + 0x2401: 'ar-YE', + 0x042B: 'hy', + 0x044D: 'as', + 0x082C: 'az-Cyrl', + 0x042C: 'az', + 0x046D: 'ba', + 0x042D: 'eu', + 0x0423: 'be', + 0x0845: 'bn', + 0x0445: 'bn-IN', + 0x201A: 'bs-Cyrl', + 0x141A: 'bs', + 0x047E: 'br', + 0x0402: 'bg', + 0x0403: 'ca', + 0x0C04: 'zh-HK', + 0x1404: 'zh-MO', + 0x0804: 'zh', + 0x1004: 'zh-SG', + 0x0404: 'zh-TW', + 0x0483: 'co', + 0x041A: 'hr', + 0x101A: 'hr-BA', + 0x0405: 'cs', + 0x0406: 'da', + 0x048C: 'prs', + 0x0465: 'dv', + 0x0813: 'nl-BE', + 0x0413: 'nl', + 0x0C09: 'en-AU', + 0x2809: 'en-BZ', + 0x1009: 'en-CA', + 0x2409: 'en-029', + 0x4009: 'en-IN', + 0x1809: 'en-IE', + 0x2009: 'en-JM', + 0x4409: 'en-MY', + 0x1409: 'en-NZ', + 0x3409: 'en-PH', + 0x4809: 'en-SG', + 0x1C09: 'en-ZA', + 0x2C09: 'en-TT', + 0x0809: 'en-GB', + 0x0409: 'en', + 0x3009: 'en-ZW', + 0x0425: 'et', + 0x0438: 'fo', + 0x0464: 'fil', + 0x040B: 'fi', + 0x080C: 'fr-BE', + 0x0C0C: 'fr-CA', + 0x040C: 'fr', + 0x140C: 'fr-LU', + 0x180C: 'fr-MC', + 0x100C: 'fr-CH', + 0x0462: 'fy', + 0x0456: 'gl', + 0x0437: 'ka', + 0x0C07: 'de-AT', + 0x0407: 'de', + 0x1407: 'de-LI', + 0x1007: 'de-LU', + 0x0807: 'de-CH', + 0x0408: 'el', + 0x046F: 'kl', + 0x0447: 'gu', + 0x0468: 'ha', + 0x040D: 'he', + 0x0439: 'hi', + 0x040E: 'hu', + 0x040F: 'is', + 0x0470: 'ig', + 0x0421: 'id', + 0x045D: 'iu', + 0x085D: 'iu-Latn', + 0x083C: 'ga', + 0x0434: 'xh', + 0x0435: 'zu', + 0x0410: 'it', + 0x0810: 'it-CH', + 0x0411: 'ja', + 0x044B: 'kn', + 0x043F: 'kk', + 0x0453: 'km', + 0x0486: 'quc', + 0x0487: 'rw', + 0x0441: 'sw', + 0x0457: 'kok', + 0x0412: 'ko', + 0x0440: 'ky', + 0x0454: 'lo', + 0x0426: 'lv', + 0x0427: 'lt', + 0x082E: 'dsb', + 0x046E: 'lb', + 0x042F: 'mk', + 0x083E: 'ms-BN', + 0x043E: 'ms', + 0x044C: 'ml', + 0x043A: 'mt', + 0x0481: 'mi', + 0x047A: 'arn', + 0x044E: 'mr', + 0x047C: 'moh', + 0x0450: 'mn', + 0x0850: 'mn-CN', + 0x0461: 'ne', + 0x0414: 'nb', + 0x0814: 'nn', + 0x0482: 'oc', + 0x0448: 'or', + 0x0463: 'ps', + 0x0415: 'pl', + 0x0416: 'pt', + 0x0816: 'pt-PT', + 0x0446: 'pa', + 0x046B: 'qu-BO', + 0x086B: 'qu-EC', + 0x0C6B: 'qu', + 0x0418: 'ro', + 0x0417: 'rm', + 0x0419: 'ru', + 0x243B: 'smn', + 0x103B: 'smj-NO', + 0x143B: 'smj', + 0x0C3B: 'se-FI', + 0x043B: 'se', + 0x083B: 'se-SE', + 0x203B: 'sms', + 0x183B: 'sma-NO', + 0x1C3B: 'sms', + 0x044F: 'sa', + 0x1C1A: 'sr-Cyrl-BA', + 0x0C1A: 'sr', + 0x181A: 'sr-Latn-BA', + 0x081A: 'sr-Latn', + 0x046C: 'nso', + 0x0432: 'tn', + 0x045B: 'si', + 0x041B: 'sk', + 0x0424: 'sl', + 0x2C0A: 'es-AR', + 0x400A: 'es-BO', + 0x340A: 'es-CL', + 0x240A: 'es-CO', + 0x140A: 'es-CR', + 0x1C0A: 'es-DO', + 0x300A: 'es-EC', + 0x440A: 'es-SV', + 0x100A: 'es-GT', + 0x480A: 'es-HN', + 0x080A: 'es-MX', + 0x4C0A: 'es-NI', + 0x180A: 'es-PA', + 0x3C0A: 'es-PY', + 0x280A: 'es-PE', + 0x500A: 'es-PR', + + // Microsoft has defined two different language codes for + // “Spanish with modern sorting” and “Spanish with traditional + // sorting”. This makes sense for collation APIs, and it would be + // possible to express this in BCP 47 language tags via Unicode + // extensions (eg., es-u-co-trad is Spanish with traditional + // sorting). However, for storing names in fonts, the distinction + // does not make sense, so we give “es” in both cases. + 0x0C0A: 'es', + 0x040A: 'es', + + 0x540A: 'es-US', + 0x380A: 'es-UY', + 0x200A: 'es-VE', + 0x081D: 'sv-FI', + 0x041D: 'sv', + 0x045A: 'syr', + 0x0428: 'tg', + 0x085F: 'tzm', + 0x0449: 'ta', + 0x0444: 'tt', + 0x044A: 'te', + 0x041E: 'th', + 0x0451: 'bo', + 0x041F: 'tr', + 0x0442: 'tk', + 0x0480: 'ug', + 0x0422: 'uk', + 0x042E: 'hsb', + 0x0420: 'ur', + 0x0843: 'uz-Cyrl', + 0x0443: 'uz', + 0x042A: 'vi', + 0x0452: 'cy', + 0x0488: 'wo', + 0x0485: 'sah', + 0x0478: 'ii', + 0x046A: 'yo' +}; + +// Returns a IETF BCP 47 language code, for example 'zh-Hant' +// for 'Chinese in the traditional script'. +function getLanguageCode(platformID, languageID, ltag) { + switch (platformID) { + case 0: // Unicode + if (languageID === 0xFFFF) { + return 'und'; + } else if (ltag) { + return ltag[languageID]; + } + + break; + + case 1: // Macintosh + return macLanguages[languageID]; + + case 3: // Windows + return windowsLanguages[languageID]; + } + + return undefined; +} + +var utf16 = 'utf-16'; + +// MacOS script ID → encoding. This table stores the default case, +// which can be overridden by macLanguageEncodings. +var macScriptEncodings = { + 0: 'macintosh', // smRoman + 1: 'x-mac-japanese', // smJapanese + 2: 'x-mac-chinesetrad', // smTradChinese + 3: 'x-mac-korean', // smKorean + 6: 'x-mac-greek', // smGreek + 7: 'x-mac-cyrillic', // smCyrillic + 9: 'x-mac-devanagai', // smDevanagari + 10: 'x-mac-gurmukhi', // smGurmukhi + 11: 'x-mac-gujarati', // smGujarati + 12: 'x-mac-oriya', // smOriya + 13: 'x-mac-bengali', // smBengali + 14: 'x-mac-tamil', // smTamil + 15: 'x-mac-telugu', // smTelugu + 16: 'x-mac-kannada', // smKannada + 17: 'x-mac-malayalam', // smMalayalam + 18: 'x-mac-sinhalese', // smSinhalese + 19: 'x-mac-burmese', // smBurmese + 20: 'x-mac-khmer', // smKhmer + 21: 'x-mac-thai', // smThai + 22: 'x-mac-lao', // smLao + 23: 'x-mac-georgian', // smGeorgian + 24: 'x-mac-armenian', // smArmenian + 25: 'x-mac-chinesesimp', // smSimpChinese + 26: 'x-mac-tibetan', // smTibetan + 27: 'x-mac-mongolian', // smMongolian + 28: 'x-mac-ethiopic', // smEthiopic + 29: 'x-mac-ce', // smCentralEuroRoman + 30: 'x-mac-vietnamese', // smVietnamese + 31: 'x-mac-extarabic' // smExtArabic +}; + +// MacOS language ID → encoding. This table stores the exceptional +// cases, which override macScriptEncodings. For writing MacOS naming +// tables, we need to emit a MacOS script ID. Therefore, we cannot +// merge macScriptEncodings into macLanguageEncodings. +// +// http://unicode.org/Public/MAPPINGS/VENDORS/APPLE/Readme.txt +var macLanguageEncodings = { + 15: 'x-mac-icelandic', // langIcelandic + 17: 'x-mac-turkish', // langTurkish + 18: 'x-mac-croatian', // langCroatian + 24: 'x-mac-ce', // langLithuanian + 25: 'x-mac-ce', // langPolish + 26: 'x-mac-ce', // langHungarian + 27: 'x-mac-ce', // langEstonian + 28: 'x-mac-ce', // langLatvian + 30: 'x-mac-icelandic', // langFaroese + 37: 'x-mac-romanian', // langRomanian + 38: 'x-mac-ce', // langCzech + 39: 'x-mac-ce', // langSlovak + 40: 'x-mac-ce', // langSlovenian + 143: 'x-mac-inuit', // langInuktitut + 146: 'x-mac-gaelic' // langIrishGaelicScript +}; + +function getEncoding(platformID, encodingID, languageID) { + switch (platformID) { + case 0: // Unicode + return utf16; + + case 1: // Apple Macintosh + return macLanguageEncodings[languageID] || macScriptEncodings[encodingID]; + + case 3: // Microsoft Windows + if (encodingID === 1 || encodingID === 10) { + return utf16; + } + + break; + } + + return undefined; +} + +// Parse the naming `name` table. +// FIXME: Format 1 additional fields are not supported yet. +// ltag is the content of the `ltag' table, such as ['en', 'zh-Hans', 'de-CH-1904']. +function parseNameTable(data, start, ltag) { + var name = {}; + var p = new parse.Parser(data, start); + var format = p.parseUShort(); + var count = p.parseUShort(); + var stringOffset = p.offset + p.parseUShort(); + for (var i = 0; i < count; i++) { + var platformID = p.parseUShort(); + var encodingID = p.parseUShort(); + var languageID = p.parseUShort(); + var nameID = p.parseUShort(); + var property = nameTableNames[nameID] || nameID; + var byteLength = p.parseUShort(); + var offset = p.parseUShort(); + var language = getLanguageCode(platformID, languageID, ltag); + var encoding = getEncoding(platformID, encodingID, languageID); + if (encoding !== undefined && language !== undefined) { + var text = (void 0); + if (encoding === utf16) { + text = decode.UTF16(data, stringOffset + offset, byteLength); + } else { + text = decode.MACSTRING(data, stringOffset + offset, byteLength, encoding); + } + + if (text) { + var translations = name[property]; + if (translations === undefined) { + translations = name[property] = {}; + } + + translations[language] = text; + } + } + } + + var langTagCount = 0; + if (format === 1) { + // FIXME: Also handle Microsoft's 'name' table 1. + langTagCount = p.parseUShort(); + } + + return name; +} + +// {23: 'foo'} → {'foo': 23} +// ['bar', 'baz'] → {'bar': 0, 'baz': 1} +function reverseDict(dict) { + var result = {}; + for (var key in dict) { + result[dict[key]] = parseInt(key); + } + + return result; +} + +function makeNameRecord(platformID, encodingID, languageID, nameID, length, offset) { + return new table.Record('NameRecord', [ + {name: 'platformID', type: 'USHORT', value: platformID}, + {name: 'encodingID', type: 'USHORT', value: encodingID}, + {name: 'languageID', type: 'USHORT', value: languageID}, + {name: 'nameID', type: 'USHORT', value: nameID}, + {name: 'length', type: 'USHORT', value: length}, + {name: 'offset', type: 'USHORT', value: offset} + ]); +} + +// Finds the position of needle in haystack, or -1 if not there. +// Like String.indexOf(), but for arrays. +function findSubArray(needle, haystack) { + var needleLength = needle.length; + var limit = haystack.length - needleLength + 1; + + loop: + for (var pos = 0; pos < limit; pos++) { + for (; pos < limit; pos++) { + for (var k = 0; k < needleLength; k++) { + if (haystack[pos + k] !== needle[k]) { + continue loop; + } + } + + return pos; + } + } + + return -1; +} + +function addStringToPool(s, pool) { + var offset = findSubArray(s, pool); + if (offset < 0) { + offset = pool.length; + var i = 0; + var len = s.length; + for (; i < len; ++i) { + pool.push(s[i]); + } + + } + + return offset; +} + +function makeNameTable(names, ltag) { + var nameID; + var nameIDs = []; + + var namesWithNumericKeys = {}; + var nameTableIds = reverseDict(nameTableNames); + for (var key in names) { + var id = nameTableIds[key]; + if (id === undefined) { + id = key; + } + + nameID = parseInt(id); + + if (isNaN(nameID)) { + throw new Error('Name table entry "' + key + '" does not exist, see nameTableNames for complete list.'); + } + + namesWithNumericKeys[nameID] = names[key]; + nameIDs.push(nameID); + } + + var macLanguageIds = reverseDict(macLanguages); + var windowsLanguageIds = reverseDict(windowsLanguages); + + var nameRecords = []; + var stringPool = []; + + for (var i = 0; i < nameIDs.length; i++) { + nameID = nameIDs[i]; + var translations = namesWithNumericKeys[nameID]; + for (var lang in translations) { + var text = translations[lang]; + + // For MacOS, we try to emit the name in the form that was introduced + // in the initial version of the TrueType spec (in the late 1980s). + // However, this can fail for various reasons: the requested BCP 47 + // language code might not have an old-style Mac equivalent; + // we might not have a codec for the needed character encoding; + // or the name might contain characters that cannot be expressed + // in the old-style Macintosh encoding. In case of failure, we emit + // the name in a more modern fashion (Unicode encoding with BCP 47 + // language tags) that is recognized by MacOS 10.5, released in 2009. + // If fonts were only read by operating systems, we could simply + // emit all names in the modern form; this would be much easier. + // However, there are many applications and libraries that read + // 'name' tables directly, and these will usually only recognize + // the ancient form (silently skipping the unrecognized names). + var macPlatform = 1; // Macintosh + var macLanguage = macLanguageIds[lang]; + var macScript = macLanguageToScript[macLanguage]; + var macEncoding = getEncoding(macPlatform, macScript, macLanguage); + var macName = encode.MACSTRING(text, macEncoding); + if (macName === undefined) { + macPlatform = 0; // Unicode + macLanguage = ltag.indexOf(lang); + if (macLanguage < 0) { + macLanguage = ltag.length; + ltag.push(lang); + } + + macScript = 4; // Unicode 2.0 and later + macName = encode.UTF16(text); + } + + var macNameOffset = addStringToPool(macName, stringPool); + nameRecords.push(makeNameRecord(macPlatform, macScript, macLanguage, + nameID, macName.length, macNameOffset)); + + var winLanguage = windowsLanguageIds[lang]; + if (winLanguage !== undefined) { + var winName = encode.UTF16(text); + var winNameOffset = addStringToPool(winName, stringPool); + nameRecords.push(makeNameRecord(3, 1, winLanguage, + nameID, winName.length, winNameOffset)); + } + } + } + + nameRecords.sort(function(a, b) { + return ((a.platformID - b.platformID) || + (a.encodingID - b.encodingID) || + (a.languageID - b.languageID) || + (a.nameID - b.nameID)); + }); + + var t = new table.Table('name', [ + {name: 'format', type: 'USHORT', value: 0}, + {name: 'count', type: 'USHORT', value: nameRecords.length}, + {name: 'stringOffset', type: 'USHORT', value: 6 + nameRecords.length * 12} + ]); + + for (var r = 0; r < nameRecords.length; r++) { + t.fields.push({name: 'record_' + r, type: 'RECORD', value: nameRecords[r]}); + } + + t.fields.push({name: 'strings', type: 'LITERAL', value: stringPool}); + return t; +} + +var _name = { parse: parseNameTable, make: makeNameTable }; + +// The `OS/2` table contains metrics required in OpenType fonts. + +var unicodeRanges = [ + {begin: 0x0000, end: 0x007F}, // Basic Latin + {begin: 0x0080, end: 0x00FF}, // Latin-1 Supplement + {begin: 0x0100, end: 0x017F}, // Latin Extended-A + {begin: 0x0180, end: 0x024F}, // Latin Extended-B + {begin: 0x0250, end: 0x02AF}, // IPA Extensions + {begin: 0x02B0, end: 0x02FF}, // Spacing Modifier Letters + {begin: 0x0300, end: 0x036F}, // Combining Diacritical Marks + {begin: 0x0370, end: 0x03FF}, // Greek and Coptic + {begin: 0x2C80, end: 0x2CFF}, // Coptic + {begin: 0x0400, end: 0x04FF}, // Cyrillic + {begin: 0x0530, end: 0x058F}, // Armenian + {begin: 0x0590, end: 0x05FF}, // Hebrew + {begin: 0xA500, end: 0xA63F}, // Vai + {begin: 0x0600, end: 0x06FF}, // Arabic + {begin: 0x07C0, end: 0x07FF}, // NKo + {begin: 0x0900, end: 0x097F}, // Devanagari + {begin: 0x0980, end: 0x09FF}, // Bengali + {begin: 0x0A00, end: 0x0A7F}, // Gurmukhi + {begin: 0x0A80, end: 0x0AFF}, // Gujarati + {begin: 0x0B00, end: 0x0B7F}, // Oriya + {begin: 0x0B80, end: 0x0BFF}, // Tamil + {begin: 0x0C00, end: 0x0C7F}, // Telugu + {begin: 0x0C80, end: 0x0CFF}, // Kannada + {begin: 0x0D00, end: 0x0D7F}, // Malayalam + {begin: 0x0E00, end: 0x0E7F}, // Thai + {begin: 0x0E80, end: 0x0EFF}, // Lao + {begin: 0x10A0, end: 0x10FF}, // Georgian + {begin: 0x1B00, end: 0x1B7F}, // Balinese + {begin: 0x1100, end: 0x11FF}, // Hangul Jamo + {begin: 0x1E00, end: 0x1EFF}, // Latin Extended Additional + {begin: 0x1F00, end: 0x1FFF}, // Greek Extended + {begin: 0x2000, end: 0x206F}, // General Punctuation + {begin: 0x2070, end: 0x209F}, // Superscripts And Subscripts + {begin: 0x20A0, end: 0x20CF}, // Currency Symbol + {begin: 0x20D0, end: 0x20FF}, // Combining Diacritical Marks For Symbols + {begin: 0x2100, end: 0x214F}, // Letterlike Symbols + {begin: 0x2150, end: 0x218F}, // Number Forms + {begin: 0x2190, end: 0x21FF}, // Arrows + {begin: 0x2200, end: 0x22FF}, // Mathematical Operators + {begin: 0x2300, end: 0x23FF}, // Miscellaneous Technical + {begin: 0x2400, end: 0x243F}, // Control Pictures + {begin: 0x2440, end: 0x245F}, // Optical Character Recognition + {begin: 0x2460, end: 0x24FF}, // Enclosed Alphanumerics + {begin: 0x2500, end: 0x257F}, // Box Drawing + {begin: 0x2580, end: 0x259F}, // Block Elements + {begin: 0x25A0, end: 0x25FF}, // Geometric Shapes + {begin: 0x2600, end: 0x26FF}, // Miscellaneous Symbols + {begin: 0x2700, end: 0x27BF}, // Dingbats + {begin: 0x3000, end: 0x303F}, // CJK Symbols And Punctuation + {begin: 0x3040, end: 0x309F}, // Hiragana + {begin: 0x30A0, end: 0x30FF}, // Katakana + {begin: 0x3100, end: 0x312F}, // Bopomofo + {begin: 0x3130, end: 0x318F}, // Hangul Compatibility Jamo + {begin: 0xA840, end: 0xA87F}, // Phags-pa + {begin: 0x3200, end: 0x32FF}, // Enclosed CJK Letters And Months + {begin: 0x3300, end: 0x33FF}, // CJK Compatibility + {begin: 0xAC00, end: 0xD7AF}, // Hangul Syllables + {begin: 0xD800, end: 0xDFFF}, // Non-Plane 0 * + {begin: 0x10900, end: 0x1091F}, // Phoenicia + {begin: 0x4E00, end: 0x9FFF}, // CJK Unified Ideographs + {begin: 0xE000, end: 0xF8FF}, // Private Use Area (plane 0) + {begin: 0x31C0, end: 0x31EF}, // CJK Strokes + {begin: 0xFB00, end: 0xFB4F}, // Alphabetic Presentation Forms + {begin: 0xFB50, end: 0xFDFF}, // Arabic Presentation Forms-A + {begin: 0xFE20, end: 0xFE2F}, // Combining Half Marks + {begin: 0xFE10, end: 0xFE1F}, // Vertical Forms + {begin: 0xFE50, end: 0xFE6F}, // Small Form Variants + {begin: 0xFE70, end: 0xFEFF}, // Arabic Presentation Forms-B + {begin: 0xFF00, end: 0xFFEF}, // Halfwidth And Fullwidth Forms + {begin: 0xFFF0, end: 0xFFFF}, // Specials + {begin: 0x0F00, end: 0x0FFF}, // Tibetan + {begin: 0x0700, end: 0x074F}, // Syriac + {begin: 0x0780, end: 0x07BF}, // Thaana + {begin: 0x0D80, end: 0x0DFF}, // Sinhala + {begin: 0x1000, end: 0x109F}, // Myanmar + {begin: 0x1200, end: 0x137F}, // Ethiopic + {begin: 0x13A0, end: 0x13FF}, // Cherokee + {begin: 0x1400, end: 0x167F}, // Unified Canadian Aboriginal Syllabics + {begin: 0x1680, end: 0x169F}, // Ogham + {begin: 0x16A0, end: 0x16FF}, // Runic + {begin: 0x1780, end: 0x17FF}, // Khmer + {begin: 0x1800, end: 0x18AF}, // Mongolian + {begin: 0x2800, end: 0x28FF}, // Braille Patterns + {begin: 0xA000, end: 0xA48F}, // Yi Syllables + {begin: 0x1700, end: 0x171F}, // Tagalog + {begin: 0x10300, end: 0x1032F}, // Old Italic + {begin: 0x10330, end: 0x1034F}, // Gothic + {begin: 0x10400, end: 0x1044F}, // Deseret + {begin: 0x1D000, end: 0x1D0FF}, // Byzantine Musical Symbols + {begin: 0x1D400, end: 0x1D7FF}, // Mathematical Alphanumeric Symbols + {begin: 0xFF000, end: 0xFFFFD}, // Private Use (plane 15) + {begin: 0xFE00, end: 0xFE0F}, // Variation Selectors + {begin: 0xE0000, end: 0xE007F}, // Tags + {begin: 0x1900, end: 0x194F}, // Limbu + {begin: 0x1950, end: 0x197F}, // Tai Le + {begin: 0x1980, end: 0x19DF}, // New Tai Lue + {begin: 0x1A00, end: 0x1A1F}, // Buginese + {begin: 0x2C00, end: 0x2C5F}, // Glagolitic + {begin: 0x2D30, end: 0x2D7F}, // Tifinagh + {begin: 0x4DC0, end: 0x4DFF}, // Yijing Hexagram Symbols + {begin: 0xA800, end: 0xA82F}, // Syloti Nagri + {begin: 0x10000, end: 0x1007F}, // Linear B Syllabary + {begin: 0x10140, end: 0x1018F}, // Ancient Greek Numbers + {begin: 0x10380, end: 0x1039F}, // Ugaritic + {begin: 0x103A0, end: 0x103DF}, // Old Persian + {begin: 0x10450, end: 0x1047F}, // Shavian + {begin: 0x10480, end: 0x104AF}, // Osmanya + {begin: 0x10800, end: 0x1083F}, // Cypriot Syllabary + {begin: 0x10A00, end: 0x10A5F}, // Kharoshthi + {begin: 0x1D300, end: 0x1D35F}, // Tai Xuan Jing Symbols + {begin: 0x12000, end: 0x123FF}, // Cuneiform + {begin: 0x1D360, end: 0x1D37F}, // Counting Rod Numerals + {begin: 0x1B80, end: 0x1BBF}, // Sundanese + {begin: 0x1C00, end: 0x1C4F}, // Lepcha + {begin: 0x1C50, end: 0x1C7F}, // Ol Chiki + {begin: 0xA880, end: 0xA8DF}, // Saurashtra + {begin: 0xA900, end: 0xA92F}, // Kayah Li + {begin: 0xA930, end: 0xA95F}, // Rejang + {begin: 0xAA00, end: 0xAA5F}, // Cham + {begin: 0x10190, end: 0x101CF}, // Ancient Symbols + {begin: 0x101D0, end: 0x101FF}, // Phaistos Disc + {begin: 0x102A0, end: 0x102DF}, // Carian + {begin: 0x1F030, end: 0x1F09F} // Domino Tiles +]; + +function getUnicodeRange(unicode) { + for (var i = 0; i < unicodeRanges.length; i += 1) { + var range = unicodeRanges[i]; + if (unicode >= range.begin && unicode < range.end) { + return i; + } + } + + return -1; +} + +// Parse the OS/2 and Windows metrics `OS/2` table +function parseOS2Table(data, start) { + var os2 = {}; + var p = new parse.Parser(data, start); + os2.version = p.parseUShort(); + os2.xAvgCharWidth = p.parseShort(); + os2.usWeightClass = p.parseUShort(); + os2.usWidthClass = p.parseUShort(); + os2.fsType = p.parseUShort(); + os2.ySubscriptXSize = p.parseShort(); + os2.ySubscriptYSize = p.parseShort(); + os2.ySubscriptXOffset = p.parseShort(); + os2.ySubscriptYOffset = p.parseShort(); + os2.ySuperscriptXSize = p.parseShort(); + os2.ySuperscriptYSize = p.parseShort(); + os2.ySuperscriptXOffset = p.parseShort(); + os2.ySuperscriptYOffset = p.parseShort(); + os2.yStrikeoutSize = p.parseShort(); + os2.yStrikeoutPosition = p.parseShort(); + os2.sFamilyClass = p.parseShort(); + os2.panose = []; + for (var i = 0; i < 10; i++) { + os2.panose[i] = p.parseByte(); + } + + os2.ulUnicodeRange1 = p.parseULong(); + os2.ulUnicodeRange2 = p.parseULong(); + os2.ulUnicodeRange3 = p.parseULong(); + os2.ulUnicodeRange4 = p.parseULong(); + os2.achVendID = String.fromCharCode(p.parseByte(), p.parseByte(), p.parseByte(), p.parseByte()); + os2.fsSelection = p.parseUShort(); + os2.usFirstCharIndex = p.parseUShort(); + os2.usLastCharIndex = p.parseUShort(); + os2.sTypoAscender = p.parseShort(); + os2.sTypoDescender = p.parseShort(); + os2.sTypoLineGap = p.parseShort(); + os2.usWinAscent = p.parseUShort(); + os2.usWinDescent = p.parseUShort(); + if (os2.version >= 1) { + os2.ulCodePageRange1 = p.parseULong(); + os2.ulCodePageRange2 = p.parseULong(); + } + + if (os2.version >= 2) { + os2.sxHeight = p.parseShort(); + os2.sCapHeight = p.parseShort(); + os2.usDefaultChar = p.parseUShort(); + os2.usBreakChar = p.parseUShort(); + os2.usMaxContent = p.parseUShort(); + } + + return os2; +} + +function makeOS2Table(options) { + return new table.Table('OS/2', [ + {name: 'version', type: 'USHORT', value: 0x0003}, + {name: 'xAvgCharWidth', type: 'SHORT', value: 0}, + {name: 'usWeightClass', type: 'USHORT', value: 0}, + {name: 'usWidthClass', type: 'USHORT', value: 0}, + {name: 'fsType', type: 'USHORT', value: 0}, + {name: 'ySubscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySubscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySubscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySubscriptYOffset', type: 'SHORT', value: 140}, + {name: 'ySuperscriptXSize', type: 'SHORT', value: 650}, + {name: 'ySuperscriptYSize', type: 'SHORT', value: 699}, + {name: 'ySuperscriptXOffset', type: 'SHORT', value: 0}, + {name: 'ySuperscriptYOffset', type: 'SHORT', value: 479}, + {name: 'yStrikeoutSize', type: 'SHORT', value: 49}, + {name: 'yStrikeoutPosition', type: 'SHORT', value: 258}, + {name: 'sFamilyClass', type: 'SHORT', value: 0}, + {name: 'bFamilyType', type: 'BYTE', value: 0}, + {name: 'bSerifStyle', type: 'BYTE', value: 0}, + {name: 'bWeight', type: 'BYTE', value: 0}, + {name: 'bProportion', type: 'BYTE', value: 0}, + {name: 'bContrast', type: 'BYTE', value: 0}, + {name: 'bStrokeVariation', type: 'BYTE', value: 0}, + {name: 'bArmStyle', type: 'BYTE', value: 0}, + {name: 'bLetterform', type: 'BYTE', value: 0}, + {name: 'bMidline', type: 'BYTE', value: 0}, + {name: 'bXHeight', type: 'BYTE', value: 0}, + {name: 'ulUnicodeRange1', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange2', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange3', type: 'ULONG', value: 0}, + {name: 'ulUnicodeRange4', type: 'ULONG', value: 0}, + {name: 'achVendID', type: 'CHARARRAY', value: 'XXXX'}, + {name: 'fsSelection', type: 'USHORT', value: 0}, + {name: 'usFirstCharIndex', type: 'USHORT', value: 0}, + {name: 'usLastCharIndex', type: 'USHORT', value: 0}, + {name: 'sTypoAscender', type: 'SHORT', value: 0}, + {name: 'sTypoDescender', type: 'SHORT', value: 0}, + {name: 'sTypoLineGap', type: 'SHORT', value: 0}, + {name: 'usWinAscent', type: 'USHORT', value: 0}, + {name: 'usWinDescent', type: 'USHORT', value: 0}, + {name: 'ulCodePageRange1', type: 'ULONG', value: 0}, + {name: 'ulCodePageRange2', type: 'ULONG', value: 0}, + {name: 'sxHeight', type: 'SHORT', value: 0}, + {name: 'sCapHeight', type: 'SHORT', value: 0}, + {name: 'usDefaultChar', type: 'USHORT', value: 0}, + {name: 'usBreakChar', type: 'USHORT', value: 0}, + {name: 'usMaxContext', type: 'USHORT', value: 0} + ], options); +} + +var os2 = { parse: parseOS2Table, make: makeOS2Table, unicodeRanges: unicodeRanges, getUnicodeRange: getUnicodeRange }; + +// The `post` table stores additional PostScript information, such as glyph names. + +// Parse the PostScript `post` table +function parsePostTable(data, start) { + var post = {}; + var p = new parse.Parser(data, start); + post.version = p.parseVersion(); + post.italicAngle = p.parseFixed(); + post.underlinePosition = p.parseShort(); + post.underlineThickness = p.parseShort(); + post.isFixedPitch = p.parseULong(); + post.minMemType42 = p.parseULong(); + post.maxMemType42 = p.parseULong(); + post.minMemType1 = p.parseULong(); + post.maxMemType1 = p.parseULong(); + switch (post.version) { + case 1: + post.names = standardNames.slice(); + break; + case 2: + post.numberOfGlyphs = p.parseUShort(); + post.glyphNameIndex = new Array(post.numberOfGlyphs); + for (var i = 0; i < post.numberOfGlyphs; i++) { + post.glyphNameIndex[i] = p.parseUShort(); + } + + post.names = []; + for (var i$1 = 0; i$1 < post.numberOfGlyphs; i$1++) { + if (post.glyphNameIndex[i$1] >= standardNames.length) { + var nameLength = p.parseChar(); + post.names.push(p.parseString(nameLength)); + } + } + + break; + case 2.5: + post.numberOfGlyphs = p.parseUShort(); + post.offset = new Array(post.numberOfGlyphs); + for (var i$2 = 0; i$2 < post.numberOfGlyphs; i$2++) { + post.offset[i$2] = p.parseChar(); + } + + break; + } + return post; +} + +function makePostTable() { + return new table.Table('post', [ + {name: 'version', type: 'FIXED', value: 0x00030000}, + {name: 'italicAngle', type: 'FIXED', value: 0}, + {name: 'underlinePosition', type: 'FWORD', value: 0}, + {name: 'underlineThickness', type: 'FWORD', value: 0}, + {name: 'isFixedPitch', type: 'ULONG', value: 0}, + {name: 'minMemType42', type: 'ULONG', value: 0}, + {name: 'maxMemType42', type: 'ULONG', value: 0}, + {name: 'minMemType1', type: 'ULONG', value: 0}, + {name: 'maxMemType1', type: 'ULONG', value: 0} + ]); +} + +var post = { parse: parsePostTable, make: makePostTable }; + +// The `GSUB` table contains ligatures, among other things. + +var subtableParsers = new Array(9); // subtableParsers[0] is unused + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#SS +subtableParsers[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + deltaGlyphId: this.parseUShort() + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + substitute: this.parseOffset16List() + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 1 format must be 1 or 2.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#MS +subtableParsers[2] = function parseLookup2() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Multiple Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + sequences: this.parseListOfLists() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#AS +subtableParsers[3] = function parseLookup3() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Alternate Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + alternateSets: this.parseListOfLists() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#LS +subtableParsers[4] = function parseLookup4() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB ligature table identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ligatureSets: this.parseListOfLists(function() { + return { + ligGlyph: this.parseUShort(), + components: this.parseUShortList(this.parseUShort() - 1) + }; + }) + }; +}; + +var lookupRecordDesc = { + sequenceIndex: Parser.uShort, + lookupListIndex: Parser.uShort +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CSF +subtableParsers[5] = function parseLookup5() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + + if (substFormat === 1) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + ruleSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + input: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + classDef: this.parsePointer(Parser.classDef), + classSets: this.parseListOfLists(function() { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + classes: this.parseUShortList(glyphCount - 1), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + var glyphCount = this.parseUShort(); + var substCount = this.parseUShort(); + return { + substFormat: substFormat, + coverages: this.parseList(glyphCount, Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(substCount, lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 5 format must be 1, 2 or 3.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#CC +subtableParsers[6] = function parseLookup6() { + var start = this.offset + this.relativeOffset; + var substFormat = this.parseUShort(); + if (substFormat === 1) { + return { + substFormat: 1, + coverage: this.parsePointer(Parser.coverage), + chainRuleSets: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 2) { + return { + substFormat: 2, + coverage: this.parsePointer(Parser.coverage), + backtrackClassDef: this.parsePointer(Parser.classDef), + inputClassDef: this.parsePointer(Parser.classDef), + lookaheadClassDef: this.parsePointer(Parser.classDef), + chainClassSet: this.parseListOfLists(function() { + return { + backtrack: this.parseUShortList(), + input: this.parseUShortList(this.parseShort() - 1), + lookahead: this.parseUShortList(), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + }) + }; + } else if (substFormat === 3) { + return { + substFormat: 3, + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + inputCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookupRecords: this.parseRecordList(lookupRecordDesc) + }; + } + check.assert(false, '0x' + start.toString(16) + ': lookup type 6 format must be 1, 2 or 3.'); +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#ES +subtableParsers[7] = function parseLookup7() { + // Extension Substitution subtable + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Extension Substitution subtable identifier-format must be 1'); + var extensionLookupType = this.parseUShort(); + var extensionParser = new Parser(this.data, this.offset + this.parseULong()); + return { + substFormat: 1, + lookupType: extensionLookupType, + extension: subtableParsers[extensionLookupType].call(extensionParser) + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/GSUB.htm#RCCS +subtableParsers[8] = function parseLookup8() { + var substFormat = this.parseUShort(); + check.argument(substFormat === 1, 'GSUB Reverse Chaining Contextual Single Substitution Subtable identifier-format must be 1'); + return { + substFormat: substFormat, + coverage: this.parsePointer(Parser.coverage), + backtrackCoverage: this.parseList(Parser.pointer(Parser.coverage)), + lookaheadCoverage: this.parseList(Parser.pointer(Parser.coverage)), + substitutes: this.parseUShortList() + }; +}; + +// https://www.microsoft.com/typography/OTSPEC/gsub.htm +function parseGsubTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GSUB table version.'); + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers), + variations: p.parseFeatureVariationsList() + }; + } + +} + +// GSUB Writing ////////////////////////////////////////////// +var subtableMakers = new Array(9); + +subtableMakers[1] = function makeLookup1(subtable) { + if (subtable.substFormat === 1) { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)}, + {name: 'deltaGlyphID', type: 'USHORT', value: subtable.deltaGlyphId} + ]); + } else { + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 2}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.ushortList('substitute', subtable.substitute))); + } +}; + +subtableMakers[2] = function makeLookup2(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 2 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('seqSet', subtable.sequences, function(sequenceSet) { + return new table.Table('sequenceSetTable', table.ushortList('sequence', sequenceSet)); + }))); +}; + +subtableMakers[3] = function makeLookup3(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 3 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('altSet', subtable.alternateSets, function(alternateSet) { + return new table.Table('alternateSetTable', table.ushortList('alternate', alternateSet)); + }))); +}; + +subtableMakers[4] = function makeLookup4(subtable) { + check.assert(subtable.substFormat === 1, 'Lookup type 4 substFormat must be 1.'); + return new table.Table('substitutionTable', [ + {name: 'substFormat', type: 'USHORT', value: 1}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('ligSet', subtable.ligatureSets, function(ligatureSet) { + return new table.Table('ligatureSetTable', table.tableList('ligature', ligatureSet, function(ligature) { + return new table.Table('ligatureTable', + [{name: 'ligGlyph', type: 'USHORT', value: ligature.ligGlyph}] + .concat(table.ushortList('component', ligature.components, ligature.components.length + 1)) + ); + })); + }))); +}; + +subtableMakers[6] = function makeLookup6(subtable) { + if (subtable.substFormat === 1) { + var returnTable = new table.Table('chainContextTable', [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat}, + {name: 'coverage', type: 'TABLE', value: new table.Coverage(subtable.coverage)} + ].concat(table.tableList('chainRuleSet', subtable.chainRuleSets, function(chainRuleSet) { + return new table.Table('chainRuleSetTable', table.tableList('chainRule', chainRuleSet, function(chainRule) { + var tableData = table.ushortList('backtrackGlyph', chainRule.backtrack, chainRule.backtrack.length) + .concat(table.ushortList('inputGlyph', chainRule.input, chainRule.input.length + 1)) + .concat(table.ushortList('lookaheadGlyph', chainRule.lookahead, chainRule.lookahead.length)) + .concat(table.ushortList('substitution', [], chainRule.lookupRecords.length)); + + chainRule.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + return new table.Table('chainRuleTable', tableData); + })); + }))); + return returnTable; + } else if (subtable.substFormat === 2) { + check.assert(false, 'lookup type 6 format 2 is not yet supported.'); + } else if (subtable.substFormat === 3) { + var tableData = [ + {name: 'substFormat', type: 'USHORT', value: subtable.substFormat} ]; + + tableData.push({name: 'backtrackGlyphCount', type: 'USHORT', value: subtable.backtrackCoverage.length}); + subtable.backtrackCoverage.forEach(function (coverage, i) { + tableData.push({name: 'backtrackCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'inputGlyphCount', type: 'USHORT', value: subtable.inputCoverage.length}); + subtable.inputCoverage.forEach(function (coverage, i) { + tableData.push({name: 'inputCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + tableData.push({name: 'lookaheadGlyphCount', type: 'USHORT', value: subtable.lookaheadCoverage.length}); + subtable.lookaheadCoverage.forEach(function (coverage, i) { + tableData.push({name: 'lookaheadCoverage' + i, type: 'TABLE', value: new table.Coverage(coverage)}); + }); + + tableData.push({name: 'substitutionCount', type: 'USHORT', value: subtable.lookupRecords.length}); + subtable.lookupRecords.forEach(function (record, i) { + tableData = tableData + .concat({name: 'sequenceIndex' + i, type: 'USHORT', value: record.sequenceIndex}) + .concat({name: 'lookupListIndex' + i, type: 'USHORT', value: record.lookupListIndex}); + }); + + var returnTable$1 = new table.Table('chainContextTable', tableData); + + return returnTable$1; + } + + check.assert(false, 'lookup type 6 format must be 1, 2 or 3.'); +}; + +function makeGsubTable(gsub) { + return new table.Table('GSUB', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gsub.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gsub.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gsub.lookups, subtableMakers)} + ]); +} + +var gsub = { parse: parseGsubTable, make: makeGsubTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +// Parse the metadata `meta` table. +// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6meta.html +function parseMetaTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 1, 'Unsupported META table version.'); + p.parseULong(); // flags - currently unused and set to 0 + p.parseULong(); // tableOffset + var numDataMaps = p.parseULong(); + + var tags = {}; + for (var i = 0; i < numDataMaps; i++) { + var tag = p.parseTag(); + var dataOffset = p.parseULong(); + var dataLength = p.parseULong(); + var text = decode.UTF8(data, start + dataOffset, dataLength); + + tags[tag] = text; + } + return tags; +} + +function makeMetaTable(tags) { + var numTags = Object.keys(tags).length; + var stringPool = ''; + var stringPoolOffset = 16 + numTags * 12; + + var result = new table.Table('meta', [ + {name: 'version', type: 'ULONG', value: 1}, + {name: 'flags', type: 'ULONG', value: 0}, + {name: 'offset', type: 'ULONG', value: stringPoolOffset}, + {name: 'numTags', type: 'ULONG', value: numTags} + ]); + + for (var tag in tags) { + var pos = stringPool.length; + stringPool += tags[tag]; + + result.fields.push({name: 'tag ' + tag, type: 'TAG', value: tag}); + result.fields.push({name: 'offset ' + tag, type: 'ULONG', value: stringPoolOffset + pos}); + result.fields.push({name: 'length ' + tag, type: 'ULONG', value: tags[tag].length}); + } + + result.fields.push({name: 'stringPool', type: 'CHARARRAY', value: stringPool}); + + return result; +} + +var meta = { parse: parseMetaTable, make: makeMetaTable }; + +// The `COLR` table adds support for multi-colored glyphs + +function parseColrTable(data, start) { + var p = new Parser(data, start); + var version = p.parseUShort(); + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var numBaseGlyphRecords = p.parseUShort(); + var baseGlyphRecordsOffset = p.parseOffset32(); + var layerRecordsOffset = p.parseOffset32(); + var numLayerRecords = p.parseUShort(); + p.relativeOffset = baseGlyphRecordsOffset; + var baseGlyphRecords = p.parseRecordList(numBaseGlyphRecords, { + glyphID: Parser.uShort, + firstLayerIndex: Parser.uShort, + numLayers: Parser.uShort, + }); + p.relativeOffset = layerRecordsOffset; + var layerRecords = p.parseRecordList(numLayerRecords, { + glyphID: Parser.uShort, + paletteIndex: Parser.uShort + }); + + return { + version: version, + baseGlyphRecords: baseGlyphRecords, + layerRecords: layerRecords, + }; +} + +function makeColrTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0x0000; + var baseGlyphRecords = ref.baseGlyphRecords; if ( baseGlyphRecords === void 0 ) baseGlyphRecords = []; + var layerRecords = ref.layerRecords; if ( layerRecords === void 0 ) layerRecords = []; + + check.argument(version === 0x0000, 'Only COLRv0 supported.'); + var baseGlyphRecordsOffset = 14; + var layerRecordsOffset = baseGlyphRecordsOffset + (baseGlyphRecords.length * 6); + return new table.Table('COLR', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numBaseGlyphRecords', type: 'USHORT', value: baseGlyphRecords.length }, + { name: 'baseGlyphRecordsOffset', type: 'ULONG', value: baseGlyphRecordsOffset }, + { name: 'layerRecordsOffset', type: 'ULONG', value: layerRecordsOffset }, + { name: 'numLayerRecords', type: 'USHORT', value: layerRecords.length } ].concat( baseGlyphRecords.map(function (glyph, i) { return [ + { name: 'glyphID_' + i, type: 'USHORT', value: glyph.glyphID }, + { name: 'firstLayerIndex_' + i, type: 'USHORT', value: glyph.firstLayerIndex }, + { name: 'numLayers_' + i, type: 'USHORT', value: glyph.numLayers } ]; }).flat(), + layerRecords.map(function (layer, i) { return [ + { name: 'LayerGlyphID_' + i, type: 'USHORT', value: layer.glyphID }, + { name: 'paletteIndex_' + i, type: 'USHORT', value: layer.paletteIndex } ]; }).flat() )); +} + +var colr = { parse: parseColrTable, make: makeColrTable }; + +// The `CPAL` define a contiguous list of colors (colorRecords) + +// Parse the header `head` table +function parseCpalTable(data, start) { + var p = new Parser(data, start); + var version = p.parseShort(); + var numPaletteEntries = p.parseShort(); + var numPalettes = p.parseShort(); + var numColorRecords = p.parseShort(); + var colorRecordsArrayOffset = p.parseOffset32(); + var colorRecordIndices = p.parseUShortList(numPalettes); + p.relativeOffset = colorRecordsArrayOffset; + var colorRecords = p.parseULongList(numColorRecords); + return { + version: version, + numPaletteEntries: numPaletteEntries, + colorRecords: colorRecords, + colorRecordIndices: colorRecordIndices, + }; +} + +function makeCpalTable(ref) { + var version = ref.version; if ( version === void 0 ) version = 0; + var numPaletteEntries = ref.numPaletteEntries; if ( numPaletteEntries === void 0 ) numPaletteEntries = 0; + var colorRecords = ref.colorRecords; if ( colorRecords === void 0 ) colorRecords = []; + var colorRecordIndices = ref.colorRecordIndices; if ( colorRecordIndices === void 0 ) colorRecordIndices = [0]; + + check.argument(version === 0, 'Only CPALv0 are supported.'); + check.argument(colorRecords.length, 'No colorRecords given.'); + check.argument(colorRecordIndices.length, 'No colorRecordIndices given.'); + check.argument(!numPaletteEntries && colorRecordIndices.length == 1, 'Can\'t infer numPaletteEntries on multiple colorRecordIndices'); + return new table.Table('CPAL', [ + { name: 'version', type: 'USHORT', value: version }, + { name: 'numPaletteEntries', type: 'USHORT', value: numPaletteEntries || colorRecords.length }, + { name: 'numPalettes', type: 'USHORT', value: colorRecordIndices.length }, + { name: 'numColorRecords', type: 'USHORT', value: colorRecords.length }, + { name: 'colorRecordsArrayOffset', type: 'ULONG', value: 12 + 2 * colorRecordIndices.length } ].concat( colorRecordIndices.map(function (palette, i) { return ({ name: 'colorRecordIndices_' + i, type: 'USHORT', value: palette }); }), + colorRecords.map(function (color, i) { return ({ name: 'colorRecords_' + i, type: 'ULONG', value: color }); }) )); +} + +var cpal = { parse: parseCpalTable, make: makeCpalTable }; + +// The `sfnt` wrapper provides organization for the tables in the font. + +function log2(v) { + return Math.log(v) / Math.log(2) | 0; +} + +function computeCheckSum(bytes) { + while (bytes.length % 4 !== 0) { + bytes.push(0); + } + + var sum = 0; + for (var i = 0; i < bytes.length; i += 4) { + sum += (bytes[i] << 24) + + (bytes[i + 1] << 16) + + (bytes[i + 2] << 8) + + (bytes[i + 3]); + } + + sum %= Math.pow(2, 32); + return sum; +} + +function makeTableRecord(tag, checkSum, offset, length) { + return new table.Record('Table Record', [ + {name: 'tag', type: 'TAG', value: tag !== undefined ? tag : ''}, + {name: 'checkSum', type: 'ULONG', value: checkSum !== undefined ? checkSum : 0}, + {name: 'offset', type: 'ULONG', value: offset !== undefined ? offset : 0}, + {name: 'length', type: 'ULONG', value: length !== undefined ? length : 0} + ]); +} + +function makeSfntTable(tables) { + var sfnt = new table.Table('sfnt', [ + {name: 'version', type: 'TAG', value: 'OTTO'}, + {name: 'numTables', type: 'USHORT', value: 0}, + {name: 'searchRange', type: 'USHORT', value: 0}, + {name: 'entrySelector', type: 'USHORT', value: 0}, + {name: 'rangeShift', type: 'USHORT', value: 0} + ]); + sfnt.tables = tables; + sfnt.numTables = tables.length; + var highestPowerOf2 = Math.pow(2, log2(sfnt.numTables)); + sfnt.searchRange = 16 * highestPowerOf2; + sfnt.entrySelector = log2(highestPowerOf2); + sfnt.rangeShift = sfnt.numTables * 16 - sfnt.searchRange; + + var recordFields = []; + var tableFields = []; + + var offset = sfnt.sizeOf() + (makeTableRecord().sizeOf() * sfnt.numTables); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + + for (var i = 0; i < tables.length; i += 1) { + var t = tables[i]; + check.argument(t.tableName.length === 4, 'Table name' + t.tableName + ' is invalid.'); + var tableLength = t.sizeOf(); + var tableRecord = makeTableRecord(t.tableName, computeCheckSum(t.encode()), offset, tableLength); + recordFields.push({name: tableRecord.tag + ' Table Record', type: 'RECORD', value: tableRecord}); + tableFields.push({name: t.tableName + ' table', type: 'RECORD', value: t}); + offset += tableLength; + check.argument(!isNaN(offset), 'Something went wrong calculating the offset.'); + while (offset % 4 !== 0) { + offset += 1; + tableFields.push({name: 'padding', type: 'BYTE', value: 0}); + } + } + + // Table records need to be sorted alphabetically. + recordFields.sort(function(r1, r2) { + if (r1.value.tag > r2.value.tag) { + return 1; + } else { + return -1; + } + }); + + sfnt.fields = sfnt.fields.concat(recordFields); + sfnt.fields = sfnt.fields.concat(tableFields); + return sfnt; +} + +// Get the metrics for a character. If the string has more than one character +// this function returns metrics for the first available character. +// You can provide optional fallback metrics if no characters are available. +function metricsForChar(font, chars, notFoundMetrics) { + for (var i = 0; i < chars.length; i += 1) { + var glyphIndex = font.charToGlyphIndex(chars[i]); + if (glyphIndex > 0) { + var glyph = font.glyphs.get(glyphIndex); + return glyph.getMetrics(); + } + } + + return notFoundMetrics; +} + +function average(vs) { + var sum = 0; + for (var i = 0; i < vs.length; i += 1) { + sum += vs[i]; + } + + return sum / vs.length; +} + +// Convert the font object to a SFNT data structure. +// This structure contains all the necessary tables and metadata to create a binary OTF file. +function fontToSfntTable(font) { + var xMins = []; + var yMins = []; + var xMaxs = []; + var yMaxs = []; + var advanceWidths = []; + var leftSideBearings = []; + var rightSideBearings = []; + var firstCharIndex; + var lastCharIndex = 0; + var ulUnicodeRange1 = 0; + var ulUnicodeRange2 = 0; + var ulUnicodeRange3 = 0; + var ulUnicodeRange4 = 0; + + for (var i = 0; i < font.glyphs.length; i += 1) { + var glyph = font.glyphs.get(i); + var unicode = glyph.unicode | 0; + + if (isNaN(glyph.advanceWidth)) { + throw new Error('Glyph ' + glyph.name + ' (' + i + '): advanceWidth is not a number.'); + } + + if (firstCharIndex > unicode || firstCharIndex === undefined) { + // ignore .notdef char + if (unicode > 0) { + firstCharIndex = unicode; + } + } + + if (lastCharIndex < unicode) { + lastCharIndex = unicode; + } + + var position = os2.getUnicodeRange(unicode); + if (position < 32) { + ulUnicodeRange1 |= 1 << position; + } else if (position < 64) { + ulUnicodeRange2 |= 1 << position - 32; + } else if (position < 96) { + ulUnicodeRange3 |= 1 << position - 64; + } else if (position < 123) { + ulUnicodeRange4 |= 1 << position - 96; + } else { + throw new Error('Unicode ranges bits > 123 are reserved for internal usage'); + } + // Skip non-important characters. + if (glyph.name === '.notdef') { continue; } + var metrics = glyph.getMetrics(); + xMins.push(metrics.xMin); + yMins.push(metrics.yMin); + xMaxs.push(metrics.xMax); + yMaxs.push(metrics.yMax); + leftSideBearings.push(metrics.leftSideBearing); + rightSideBearings.push(metrics.rightSideBearing); + advanceWidths.push(glyph.advanceWidth); + } + + var globals = { + xMin: Math.min.apply(null, xMins), + yMin: Math.min.apply(null, yMins), + xMax: Math.max.apply(null, xMaxs), + yMax: Math.max.apply(null, yMaxs), + advanceWidthMax: Math.max.apply(null, advanceWidths), + advanceWidthAvg: average(advanceWidths), + minLeftSideBearing: Math.min.apply(null, leftSideBearings), + maxLeftSideBearing: Math.max.apply(null, leftSideBearings), + minRightSideBearing: Math.min.apply(null, rightSideBearings) + }; + globals.ascender = font.ascender; + globals.descender = font.descender; + + var headTable = head.make({ + flags: 3, // 00000011 (baseline for font at y=0; left sidebearing point at x=0) + unitsPerEm: font.unitsPerEm, + xMin: globals.xMin, + yMin: globals.yMin, + xMax: globals.xMax, + yMax: globals.yMax, + lowestRecPPEM: 3, + createdTimestamp: font.createdTimestamp + }); + + var hheaTable = hhea.make({ + ascender: globals.ascender, + descender: globals.descender, + advanceWidthMax: globals.advanceWidthMax, + minLeftSideBearing: globals.minLeftSideBearing, + minRightSideBearing: globals.minRightSideBearing, + xMaxExtent: globals.maxLeftSideBearing + (globals.xMax - globals.xMin), + numberOfHMetrics: font.glyphs.length + }); + + var maxpTable = maxp.make(font.glyphs.length); + + var os2Table = os2.make(Object.assign({ + xAvgCharWidth: Math.round(globals.advanceWidthAvg), + usFirstCharIndex: firstCharIndex, + usLastCharIndex: lastCharIndex, + ulUnicodeRange1: ulUnicodeRange1, + ulUnicodeRange2: ulUnicodeRange2, + ulUnicodeRange3: ulUnicodeRange3, + ulUnicodeRange4: ulUnicodeRange4, + // See http://typophile.com/node/13081 for more info on vertical metrics. + // We get metrics for typical characters (such as "x" for xHeight). + // We provide some fallback characters if characters are unavailable: their + // ordering was chosen experimentally. + sTypoAscender: globals.ascender, + sTypoDescender: globals.descender, + sTypoLineGap: 0, + usWinAscent: globals.yMax, + usWinDescent: Math.abs(globals.yMin), + ulCodePageRange1: 1, // FIXME: hard-code Latin 1 support for now + sxHeight: metricsForChar(font, 'xyvw', {yMax: Math.round(globals.ascender / 2)}).yMax, + sCapHeight: metricsForChar(font, 'HIKLEFJMNTZBDPRAGOQSUVWXY', globals).yMax, + usDefaultChar: font.hasChar(' ') ? 32 : 0, // Use space as the default character, if available. + usBreakChar: font.hasChar(' ') ? 32 : 0, // Use space as the break character, if available. + }, font.tables.os2)); + + var hmtxTable = hmtx.make(font.glyphs); + var cmapTable = cmap.make(font.glyphs); + + var englishFamilyName = font.getEnglishName('fontFamily'); + var englishStyleName = font.getEnglishName('fontSubfamily'); + var englishFullName = englishFamilyName + ' ' + englishStyleName; + var postScriptName = font.getEnglishName('postScriptName'); + if (!postScriptName) { + postScriptName = englishFamilyName.replace(/\s/g, '') + '-' + englishStyleName; + } + + var names = {}; + for (var n in font.names) { + names[n] = font.names[n]; + } + + if (!names.uniqueID) { + names.uniqueID = {en: font.getEnglishName('manufacturer') + ':' + englishFullName}; + } + + if (!names.postScriptName) { + names.postScriptName = {en: postScriptName}; + } + + if (!names.preferredFamily) { + names.preferredFamily = font.names.fontFamily; + } + + if (!names.preferredSubfamily) { + names.preferredSubfamily = font.names.fontSubfamily; + } + + var languageTags = []; + var nameTable = _name.make(names, languageTags); + var ltagTable = (languageTags.length > 0 ? ltag.make(languageTags) : undefined); + + var postTable = post.make(); + var cffTable = cff.make(font.glyphs, { + version: font.getEnglishName('version'), + fullName: englishFullName, + familyName: englishFamilyName, + weightName: englishStyleName, + postScriptName: postScriptName, + unitsPerEm: font.unitsPerEm, + fontBBox: [0, globals.yMin, globals.ascender, globals.advanceWidthMax] + }); + + var metaTable = (font.metas && Object.keys(font.metas).length > 0) ? meta.make(font.metas) : undefined; + + // The order does not matter because makeSfntTable() will sort them. + var tables = [headTable, hheaTable, maxpTable, os2Table, nameTable, cmapTable, postTable, cffTable, hmtxTable]; + if (ltagTable) { + tables.push(ltagTable); + } + // Optional tables + if (font.tables.gsub) { + tables.push(gsub.make(font.tables.gsub)); + } + if (font.tables.cpal) { + tables.push(cpal.make(font.tables.cpal)); + } + if (font.tables.colr) { + tables.push(colr.make(font.tables.colr)); + } + if (metaTable) { + tables.push(metaTable); + } + + var sfntTable = makeSfntTable(tables); + + // Compute the font's checkSum and store it in head.checkSumAdjustment. + var bytes = sfntTable.encode(); + var checkSum = computeCheckSum(bytes); + var tableFields = sfntTable.fields; + var checkSumAdjusted = false; + for (var i$1 = 0; i$1 < tableFields.length; i$1 += 1) { + if (tableFields[i$1].name === 'head table') { + tableFields[i$1].value.checkSumAdjustment = 0xB1B0AFBA - checkSum; + checkSumAdjusted = true; + break; + } + } + + if (!checkSumAdjusted) { + throw new Error('Could not find head table with checkSum to adjust.'); + } + + return sfntTable; +} + +var sfnt = { make: makeSfntTable, fontToTable: fontToSfntTable, computeCheckSum: computeCheckSum }; + +// The Layout object is the prototype of Substitution objects, and provides + +function searchTag(arr, tag) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid].tag; + if (val === tag) { + return imid; + } else if (val < tag) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +function binSearch(arr, value) { + /* jshint bitwise: false */ + var imin = 0; + var imax = arr.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + var val = arr[imid]; + if (val === value) { + return imid; + } else if (val < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + // Not found: return -1-insertion point + return -imin - 1; +} + +// binary search in a list of ranges (coverage, class definition) +function searchRange(ranges, value) { + // jshint bitwise: false + var range; + var imin = 0; + var imax = ranges.length - 1; + while (imin <= imax) { + var imid = (imin + imax) >>> 1; + range = ranges[imid]; + var start = range.start; + if (start === value) { + return range; + } else if (start < value) { + imin = imid + 1; + } else { imax = imid - 1; } + } + if (imin > 0) { + range = ranges[imin - 1]; + if (value > range.end) { return 0; } + return range; + } +} + +/** + * @exports opentype.Layout + * @class + */ +function Layout(font, tableName) { + this.font = font; + this.tableName = tableName; +} + +Layout.prototype = { + + /** + * Binary search an object by "tag" property + * @instance + * @function searchTag + * @memberof opentype.Layout + * @param {Array} arr + * @param {string} tag + * @return {number} + */ + searchTag: searchTag, + + /** + * Binary search in a list of numbers + * @instance + * @function binSearch + * @memberof opentype.Layout + * @param {Array} arr + * @param {number} value + * @return {number} + */ + binSearch: binSearch, + + /** + * Get or create the Layout table (GSUB, GPOS etc). + * @param {boolean} create - Whether to create a new one. + * @return {Object} The GSUB or GPOS table. + */ + getTable: function(create) { + var layout = this.font.tables[this.tableName]; + if (!layout && create) { + layout = this.font.tables[this.tableName] = this.createDefaultTable(); + } + return layout; + }, + + /** + * Returns all scripts in the substitution table. + * @instance + * @return {Array} + */ + getScriptNames: function() { + var layout = this.getTable(); + if (!layout) { return []; } + return layout.scripts.map(function(script) { + return script.tag; + }); + }, + + /** + * Returns the best bet for a script name. + * Returns 'DFLT' if it exists. + * If not, returns 'latn' if it exists. + * If neither exist, returns undefined. + */ + getDefaultScriptName: function() { + var layout = this.getTable(); + if (!layout) { return; } + var hasLatn = false; + for (var i = 0; i < layout.scripts.length; i++) { + var name = layout.scripts[i].tag; + if (name === 'DFLT') { return name; } + if (name === 'latn') { hasLatn = true; } + } + if (hasLatn) { return 'latn'; } + }, + + /** + * Returns all LangSysRecords in the given script. + * @instance + * @param {string} [script='DFLT'] + * @param {boolean} create - forces the creation of this script table if it doesn't exist. + * @return {Object} An object with tag and script properties. + */ + getScriptTable: function(script, create) { + var layout = this.getTable(create); + if (layout) { + script = script || 'DFLT'; + var scripts = layout.scripts; + var pos = searchTag(layout.scripts, script); + if (pos >= 0) { + return scripts[pos].script; + } else if (create) { + var scr = { + tag: script, + script: { + defaultLangSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []}, + langSysRecords: [] + } + }; + scripts.splice(-1 - pos, 0, scr); + return scr.script; + } + } + }, + + /** + * Returns a language system table + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {boolean} create - forces the creation of this langSysTable if it doesn't exist. + * @return {Object} + */ + getLangSysTable: function(script, language, create) { + var scriptTable = this.getScriptTable(script, create); + if (scriptTable) { + if (!language || language === 'dflt' || language === 'DFLT') { + return scriptTable.defaultLangSys; + } + var pos = searchTag(scriptTable.langSysRecords, language); + if (pos >= 0) { + return scriptTable.langSysRecords[pos].langSys; + } else if (create) { + var langSysRecord = { + tag: language, + langSys: {reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: []} + }; + scriptTable.langSysRecords.splice(-1 - pos, 0, langSysRecord); + return langSysRecord.langSys; + } + } + }, + + /** + * Get a specific feature table. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - One of the codes listed at https://www.microsoft.com/typography/OTSPEC/featurelist.htm + * @param {boolean} create - forces the creation of the feature table if it doesn't exist. + * @return {Object} + */ + getFeatureTable: function(script, language, feature, create) { + var langSysTable = this.getLangSysTable(script, language, create); + if (langSysTable) { + var featureRecord; + var featIndexes = langSysTable.featureIndexes; + var allFeatures = this.font.tables[this.tableName].features; + // The FeatureIndex array of indices is in arbitrary order, + // even if allFeatures is sorted alphabetically by feature tag. + for (var i = 0; i < featIndexes.length; i++) { + featureRecord = allFeatures[featIndexes[i]]; + if (featureRecord.tag === feature) { + return featureRecord.feature; + } + } + if (create) { + var index = allFeatures.length; + // Automatic ordering of features would require to shift feature indexes in the script list. + check.assert(index === 0 || feature >= allFeatures[index - 1].tag, 'Features must be added in alphabetical order.'); + featureRecord = { + tag: feature, + feature: { params: 0, lookupListIndexes: [] } + }; + allFeatures.push(featureRecord); + featIndexes.push(index); + return featureRecord.feature; + } + } + }, + + /** + * Get the lookup tables of a given type for a script/language/feature. + * @instance + * @param {string} [script='DFLT'] + * @param {string} [language='dlft'] + * @param {string} feature - 4-letter feature code + * @param {number} lookupType - 1 to 9 + * @param {boolean} create - forces the creation of the lookup table if it doesn't exist, with no subtables. + * @return {Object[]} + */ + getLookupTables: function(script, language, feature, lookupType, create) { + var featureTable = this.getFeatureTable(script, language, feature, create); + var tables = []; + if (featureTable) { + var lookupTable; + var lookupListIndexes = featureTable.lookupListIndexes; + var allLookups = this.font.tables[this.tableName].lookups; + // lookupListIndexes are in no particular order, so use naive search. + for (var i = 0; i < lookupListIndexes.length; i++) { + lookupTable = allLookups[lookupListIndexes[i]]; + if (lookupTable.lookupType === lookupType) { + tables.push(lookupTable); + } + } + if (tables.length === 0 && create) { + lookupTable = { + lookupType: lookupType, + lookupFlag: 0, + subtables: [], + markFilteringSet: undefined + }; + var index = allLookups.length; + allLookups.push(lookupTable); + lookupListIndexes.push(index); + return [lookupTable]; + } + } + return tables; + }, + + /** + * Find a glyph in a class definition table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#class-definition-table + * @param {object} classDefTable - an OpenType Layout class definition table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getGlyphClass: function(classDefTable, glyphIndex) { + switch (classDefTable.format) { + case 1: + if (classDefTable.startGlyph <= glyphIndex && glyphIndex < classDefTable.startGlyph + classDefTable.classes.length) { + return classDefTable.classes[glyphIndex - classDefTable.startGlyph]; + } + return 0; + case 2: + var range = searchRange(classDefTable.ranges, glyphIndex); + return range ? range.classId : 0; + } + }, + + /** + * Find a glyph in a coverage table + * https://docs.microsoft.com/en-us/typography/opentype/spec/chapter2#coverage-table + * @param {object} coverageTable - an OpenType Layout coverage table + * @param {number} glyphIndex - the index of the glyph to find + * @returns {number} -1 if not found + */ + getCoverageIndex: function(coverageTable, glyphIndex) { + switch (coverageTable.format) { + case 1: + var index = binSearch(coverageTable.glyphs, glyphIndex); + return index >= 0 ? index : -1; + case 2: + var range = searchRange(coverageTable.ranges, glyphIndex); + return range ? range.index + glyphIndex - range.start : -1; + } + }, + + /** + * Returns the list of glyph indexes of a coverage table. + * Format 1: the list is stored raw + * Format 2: compact list as range records. + * @instance + * @param {Object} coverageTable + * @return {Array} + */ + expandCoverage: function(coverageTable) { + if (coverageTable.format === 1) { + return coverageTable.glyphs; + } else { + var glyphs = []; + var ranges = coverageTable.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + var start = range.start; + var end = range.end; + for (var j = start; j <= end; j++) { + glyphs.push(j); + } + } + return glyphs; + } + } + +}; + +// The Position object provides utility methods to manipulate + +/** + * @exports opentype.Position + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Position(font) { + Layout.call(this, font, 'gpos'); +} + +Position.prototype = Layout.prototype; + +/** + * Init some data for faster and easier access later. + */ +Position.prototype.init = function() { + var script = this.getDefaultScriptName(); + this.defaultKerningTables = this.getKerningTables(script); +}; + +/** + * Find a glyph pair in a list of lookup tables of type 2 and retrieve the xAdvance kerning value. + * + * @param {integer} leftIndex - left glyph index + * @param {integer} rightIndex - right glyph index + * @returns {integer} + */ +Position.prototype.getKerningValue = function(kerningLookups, leftIndex, rightIndex) { + for (var i = 0; i < kerningLookups.length; i++) { + var subtables = kerningLookups[i].subtables; + for (var j = 0; j < subtables.length; j++) { + var subtable = subtables[j]; + var covIndex = this.getCoverageIndex(subtable.coverage, leftIndex); + if (covIndex < 0) { continue; } + switch (subtable.posFormat) { + case 1: + // Search Pair Adjustment Positioning Format 1 + var pairSet = subtable.pairSets[covIndex]; + for (var k = 0; k < pairSet.length; k++) { + var pair = pairSet[k]; + if (pair.secondGlyph === rightIndex) { + return pair.value1 && pair.value1.xAdvance || 0; + } + } + break; // left glyph found, not right glyph - try next subtable + case 2: + // Search Pair Adjustment Positioning Format 2 + var class1 = this.getGlyphClass(subtable.classDef1, leftIndex); + var class2 = this.getGlyphClass(subtable.classDef2, rightIndex); + var pair$1 = subtable.classRecords[class1][class2]; + return pair$1.value1 && pair$1.value1.xAdvance || 0; + } + } + } + return 0; +}; + +/** + * List all kerning lookup tables. + * + * @param {string} [script='DFLT'] - use font.position.getDefaultScriptName() for a better default value + * @param {string} [language='dflt'] + * @return {object[]} The list of kerning lookup tables (may be empty), or undefined if there is no GPOS table (and we should use the kern table) + */ +Position.prototype.getKerningTables = function(script, language) { + if (this.font.tables.gpos) { + return this.getLookupTables(script, language, 'kern', 2); + } +}; + +// The Substitution object provides utility methods to manipulate + +/** + * @exports opentype.Substitution + * @class + * @extends opentype.Layout + * @param {opentype.Font} + * @constructor + */ +function Substitution(font) { + Layout.call(this, font, 'gsub'); +} + +// Check if 2 arrays of primitives are equal. +function arraysEqual(ar1, ar2) { + var n = ar1.length; + if (n !== ar2.length) { return false; } + for (var i = 0; i < n; i++) { + if (ar1[i] !== ar2[i]) { return false; } + } + return true; +} + +// Find the first subtable of a lookup table in a particular format. +function getSubstFormat(lookupTable, format, defaultSubtable) { + var subtables = lookupTable.subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + if (subtable.substFormat === format) { + return subtable; + } + } + if (defaultSubtable) { + subtables.push(defaultSubtable); + return defaultSubtable; + } + return undefined; +} + +Substitution.prototype = Layout.prototype; + +/** + * Create a default GSUB table. + * @return {Object} gsub - The GSUB table. + */ +Substitution.prototype.createDefaultTable = function() { + // Generate a default empty GSUB table with just a DFLT script and dflt lang sys. + return { + version: 1, + scripts: [{ + tag: 'DFLT', + script: { + defaultLangSys: { reserved: 0, reqFeatureIndex: 0xffff, featureIndexes: [] }, + langSysRecords: [] + } + }], + features: [], + lookups: [] + }; +}; + +/** + * List all single substitutions (lookup type 1) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt', 'ss01'...) + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getSingle = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 1); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + if (subtable.substFormat === 1) { + var delta = subtable.deltaGlyphId; + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + substitutions.push({ sub: glyph, by: glyph + delta }); + } + } else { + var substitute = subtable.substitute; + for (j = 0; j < glyphs.length; j++) { + substitutions.push({ sub: glyphs[j], by: substitute[j] }); + } + } + } + } + return substitutions; +}; + +/** + * List all multiple substitutions (lookup type 2) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('ccmp', 'stch') + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getMultiple = function(feature, script, language) { + var substitutions = []; + var lookupTables = this.getLookupTables(script, language, feature, 2); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var j = (void 0); + + for (j = 0; j < glyphs.length; j++) { + var glyph = glyphs[j]; + var replacements = subtable.sequences[j]; + substitutions.push({ sub: glyph, by: replacements }); + } + } + } + return substitutions; +}; + +/** + * List all alternates (lookup type 3) for a given script, language, and feature. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @param {string} feature - 4-character feature name ('aalt', 'salt'...) + * @return {Array} alternates - The list of alternates + */ +Substitution.prototype.getAlternates = function(feature, script, language) { + var alternates = []; + var lookupTables = this.getLookupTables(script, language, feature, 3); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var alternateSets = subtable.alternateSets; + for (var j = 0; j < glyphs.length; j++) { + alternates.push({ sub: glyphs[j], by: alternateSets[j] }); + } + } + } + return alternates; +}; + +/** + * List all ligatures (lookup type 4) for a given script, language, and feature. + * The result is an array of ligature objects like { sub: [ids], by: id } + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} ligatures - The list of ligatures. + */ +Substitution.prototype.getLigatures = function(feature, script, language) { + var ligatures = []; + var lookupTables = this.getLookupTables(script, language, feature, 4); + for (var idx = 0; idx < lookupTables.length; idx++) { + var subtables = lookupTables[idx].subtables; + for (var i = 0; i < subtables.length; i++) { + var subtable = subtables[i]; + var glyphs = this.expandCoverage(subtable.coverage); + var ligatureSets = subtable.ligatureSets; + for (var j = 0; j < glyphs.length; j++) { + var startGlyph = glyphs[j]; + var ligSet = ligatureSets[j]; + for (var k = 0; k < ligSet.length; k++) { + var lig = ligSet[k]; + ligatures.push({ + sub: [startGlyph].concat(lig.components), + by: lig.ligGlyph + }); + } + } + } + } + return ligatures; +}; + +/** + * Add or modify a single substitution (lookup type 1) + * Format 2, more flexible, is always used. + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: id } (format 1 is not supported) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addSingle = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 1, true)[0]; + var subtable = getSubstFormat(lookupTable, 2, { // lookup type 1 subtable, format 2, coverage format 1 + substFormat: 2, + coverage: {format: 1, glyphs: []}, + substitute: [] + }); + check.assert(subtable.coverage.format === 1, 'Single: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.substitute.splice(pos, 0, 0); + } + subtable.substitute[pos] = substitution.by; +}; + +/** + * Add or modify a multiple substitution (lookup type 2) + * @param {string} feature - 4-letter feature name ('ccmp', 'stch') + * @param {Object} substitution - { sub: id, by: [id] } for format 2. + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addMultiple = function(feature, substitution, script, language) { + check.assert(substitution.by instanceof Array && substitution.by.length > 1, 'Multiple: "by" must be an array of two or more ids'); + var lookupTable = this.getLookupTables(script, language, feature, 2, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 2 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + sequences: [] + }); + check.assert(subtable.coverage.format === 1, 'Multiple: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.sequences.splice(pos, 0, 0); + } + subtable.sequences[pos] = substitution.by; +}; + +/** + * Add or modify an alternate substitution (lookup type 3) + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} substitution - { sub: id, by: [ids] } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addAlternate = function(feature, substitution, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 3, true)[0]; + var subtable = getSubstFormat(lookupTable, 1, { // lookup type 3 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: {format: 1, glyphs: []}, + alternateSets: [] + }); + check.assert(subtable.coverage.format === 1, 'Alternate: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = substitution.sub; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos < 0) { + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.alternateSets.splice(pos, 0, 0); + } + subtable.alternateSets[pos] = substitution.by; +}; + +/** + * Add a ligature (lookup type 4) + * Ligatures with more components must be stored ahead of those with fewer components in order to be found + * @param {string} feature - 4-letter feature name ('liga', 'rlig', 'dlig'...) + * @param {Object} ligature - { sub: [ids], by: id } + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.addLigature = function(feature, ligature, script, language) { + var lookupTable = this.getLookupTables(script, language, feature, 4, true)[0]; + var subtable = lookupTable.subtables[0]; + if (!subtable) { + subtable = { // lookup type 4 subtable, format 1, coverage format 1 + substFormat: 1, + coverage: { format: 1, glyphs: [] }, + ligatureSets: [] + }; + lookupTable.subtables[0] = subtable; + } + check.assert(subtable.coverage.format === 1, 'Ligature: unable to modify coverage table format ' + subtable.coverage.format); + var coverageGlyph = ligature.sub[0]; + var ligComponents = ligature.sub.slice(1); + var ligatureTable = { + ligGlyph: ligature.by, + components: ligComponents + }; + var pos = this.binSearch(subtable.coverage.glyphs, coverageGlyph); + if (pos >= 0) { + // ligatureSet already exists + var ligatureSet = subtable.ligatureSets[pos]; + for (var i = 0; i < ligatureSet.length; i++) { + // If ligature already exists, return. + if (arraysEqual(ligatureSet[i].components, ligComponents)) { + return; + } + } + // ligature does not exist: add it. + ligatureSet.push(ligatureTable); + } else { + // Create a new ligatureSet and add coverage for the first glyph. + pos = -1 - pos; + subtable.coverage.glyphs.splice(pos, 0, coverageGlyph); + subtable.ligatureSets.splice(pos, 0, [ligatureTable]); + } +}; + +/** + * List all feature data for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + * @return {Array} substitutions - The list of substitutions. + */ +Substitution.prototype.getFeature = function(feature, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.getSingle(feature, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + return this.getSingle(feature, script, language) + .concat(this.getAlternates(feature, script, language)); + case 'dlig': + case 'liga': + case 'rlig': + return this.getLigatures(feature, script, language); + case 'ccmp': + return this.getMultiple(feature, script, language) + .concat(this.getLigatures(feature, script, language)); + case 'stch': + return this.getMultiple(feature, script, language); + } + return undefined; +}; + +/** + * Add a substitution to a feature for a given script and language. + * @param {string} feature - 4-letter feature name + * @param {Object} sub - the substitution to add (an object like { sub: id or [ids], by: id or [ids] }) + * @param {string} [script='DFLT'] + * @param {string} [language='dflt'] + */ +Substitution.prototype.add = function(feature, sub, script, language) { + if (/ss\d\d/.test(feature)) { + // ss01 - ss20 + return this.addSingle(feature, sub, script, language); + } + switch (feature) { + case 'aalt': + case 'salt': + if (typeof sub.by === 'number') { + return this.addSingle(feature, sub, script, language); + } + return this.addAlternate(feature, sub, script, language); + case 'dlig': + case 'liga': + case 'rlig': + return this.addLigature(feature, sub, script, language); + case 'ccmp': + if (sub.by instanceof Array) { + return this.addMultiple(feature, sub, script, language); + } + return this.addLigature(feature, sub, script, language); + } + return undefined; +}; + +function isBrowser() { + return typeof window !== 'undefined'; +} + +function nodeBufferToArrayBuffer(buffer) { + var ab = new ArrayBuffer(buffer.length); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + view[i] = buffer[i]; + } + + return ab; +} + +function arrayBufferToNodeBuffer(ab) { + var buffer = new Buffer(ab.byteLength); + var view = new Uint8Array(ab); + for (var i = 0; i < buffer.length; ++i) { + buffer[i] = view[i]; + } + + return buffer; +} + +function checkArgument(expression, message) { + if (!expression) { + throw message; + } +} + +// The `glyf` table describes the glyphs in TrueType outline format. + +// Parse the coordinate data for a glyph. +function parseGlyphCoordinate(p, flag, previousValue, shortVectorBitMask, sameBitMask) { + var v; + if ((flag & shortVectorBitMask) > 0) { + // The coordinate is 1 byte long. + v = p.parseByte(); + // The `same` bit is re-used for short values to signify the sign of the value. + if ((flag & sameBitMask) === 0) { + v = -v; + } + + v = previousValue + v; + } else { + // The coordinate is 2 bytes long. + // If the `same` bit is set, the coordinate is the same as the previous coordinate. + if ((flag & sameBitMask) > 0) { + v = previousValue; + } else { + // Parse the coordinate as a signed 16-bit delta value. + v = previousValue + p.parseShort(); + } + } + + return v; +} + +// Parse a TrueType glyph. +function parseGlyph(glyph, data, start) { + var p = new parse.Parser(data, start); + glyph.numberOfContours = p.parseShort(); + glyph._xMin = p.parseShort(); + glyph._yMin = p.parseShort(); + glyph._xMax = p.parseShort(); + glyph._yMax = p.parseShort(); + var flags; + var flag; + + if (glyph.numberOfContours > 0) { + // This glyph is not a composite. + var endPointIndices = glyph.endPointIndices = []; + for (var i = 0; i < glyph.numberOfContours; i += 1) { + endPointIndices.push(p.parseUShort()); + } + + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$1 = 0; i$1 < glyph.instructionLength; i$1 += 1) { + glyph.instructions.push(p.parseByte()); + } + + var numberOfCoordinates = endPointIndices[endPointIndices.length - 1] + 1; + flags = []; + for (var i$2 = 0; i$2 < numberOfCoordinates; i$2 += 1) { + flag = p.parseByte(); + flags.push(flag); + // If bit 3 is set, we repeat this flag n times, where n is the next byte. + if ((flag & 8) > 0) { + var repeatCount = p.parseByte(); + for (var j = 0; j < repeatCount; j += 1) { + flags.push(flag); + i$2 += 1; + } + } + } + + check.argument(flags.length === numberOfCoordinates, 'Bad flags.'); + + if (endPointIndices.length > 0) { + var points = []; + var point; + // X/Y coordinates are relative to the previous point, except for the first point which is relative to 0,0. + if (numberOfCoordinates > 0) { + for (var i$3 = 0; i$3 < numberOfCoordinates; i$3 += 1) { + flag = flags[i$3]; + point = {}; + point.onCurve = !!(flag & 1); + point.lastPointOfContour = endPointIndices.indexOf(i$3) >= 0; + points.push(point); + } + + var px = 0; + for (var i$4 = 0; i$4 < numberOfCoordinates; i$4 += 1) { + flag = flags[i$4]; + point = points[i$4]; + point.x = parseGlyphCoordinate(p, flag, px, 2, 16); + px = point.x; + } + + var py = 0; + for (var i$5 = 0; i$5 < numberOfCoordinates; i$5 += 1) { + flag = flags[i$5]; + point = points[i$5]; + point.y = parseGlyphCoordinate(p, flag, py, 4, 32); + py = point.y; + } + } + + glyph.points = points; + } else { + glyph.points = []; + } + } else if (glyph.numberOfContours === 0) { + glyph.points = []; + } else { + glyph.isComposite = true; + glyph.points = []; + glyph.components = []; + var moreComponents = true; + while (moreComponents) { + flags = p.parseUShort(); + var component = { + glyphIndex: p.parseUShort(), + xScale: 1, + scale01: 0, + scale10: 0, + yScale: 1, + dx: 0, + dy: 0 + }; + if ((flags & 1) > 0) { + // The arguments are words + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseShort(); + component.dy = p.parseShort(); + } else { + // values are matched points + component.matchedPoints = [p.parseUShort(), p.parseUShort()]; + } + + } else { + // The arguments are bytes + if ((flags & 2) > 0) { + // values are offset + component.dx = p.parseChar(); + component.dy = p.parseChar(); + } else { + // values are matched points + component.matchedPoints = [p.parseByte(), p.parseByte()]; + } + } + + if ((flags & 8) > 0) { + // We have a scale + component.xScale = component.yScale = p.parseF2Dot14(); + } else if ((flags & 64) > 0) { + // We have an X / Y scale + component.xScale = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } else if ((flags & 128) > 0) { + // We have a 2x2 transformation + component.xScale = p.parseF2Dot14(); + component.scale01 = p.parseF2Dot14(); + component.scale10 = p.parseF2Dot14(); + component.yScale = p.parseF2Dot14(); + } + + glyph.components.push(component); + moreComponents = !!(flags & 32); + } + if (flags & 0x100) { + // We have instructions + glyph.instructionLength = p.parseUShort(); + glyph.instructions = []; + for (var i$6 = 0; i$6 < glyph.instructionLength; i$6 += 1) { + glyph.instructions.push(p.parseByte()); + } + } + } +} + +// Transform an array of points and return a new array. +function transformPoints(points, transform) { + var newPoints = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + var newPt = { + x: transform.xScale * pt.x + transform.scale01 * pt.y + transform.dx, + y: transform.scale10 * pt.x + transform.yScale * pt.y + transform.dy, + onCurve: pt.onCurve, + lastPointOfContour: pt.lastPointOfContour + }; + newPoints.push(newPt); + } + + return newPoints; +} + +function getContours(points) { + var contours = []; + var currentContour = []; + for (var i = 0; i < points.length; i += 1) { + var pt = points[i]; + currentContour.push(pt); + if (pt.lastPointOfContour) { + contours.push(currentContour); + currentContour = []; + } + } + + check.argument(currentContour.length === 0, 'There are still points left in the current contour.'); + return contours; +} + +// Convert the TrueType glyph outline to a Path. +function getPath(points) { + var p = new Path(); + if (!points) { + return p; + } + + var contours = getContours(points); + + for (var contourIndex = 0; contourIndex < contours.length; ++contourIndex) { + var contour = contours[contourIndex]; + + var prev = null; + var curr = contour[contour.length - 1]; + var next = contour[0]; + + if (curr.onCurve) { + p.moveTo(curr.x, curr.y); + } else { + if (next.onCurve) { + p.moveTo(next.x, next.y); + } else { + // If both first and last points are off-curve, start at their middle. + var start = {x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5}; + p.moveTo(start.x, start.y); + } + } + + for (var i = 0; i < contour.length; ++i) { + prev = curr; + curr = next; + next = contour[(i + 1) % contour.length]; + + if (curr.onCurve) { + // This is a straight line. + p.lineTo(curr.x, curr.y); + } else { + var prev2 = prev; + var next2 = next; + + if (!prev.onCurve) { + prev2 = { x: (curr.x + prev.x) * 0.5, y: (curr.y + prev.y) * 0.5 }; + } + + if (!next.onCurve) { + next2 = { x: (curr.x + next.x) * 0.5, y: (curr.y + next.y) * 0.5 }; + } + + p.quadraticCurveTo(curr.x, curr.y, next2.x, next2.y); + } + } + + p.closePath(); + } + return p; +} + +function buildPath(glyphs, glyph) { + if (glyph.isComposite) { + for (var j = 0; j < glyph.components.length; j += 1) { + var component = glyph.components[j]; + var componentGlyph = glyphs.get(component.glyphIndex); + // Force the ttfGlyphLoader to parse the glyph. + componentGlyph.getPath(); + if (componentGlyph.points) { + var transformedPoints = (void 0); + if (component.matchedPoints === undefined) { + // component positioned by offset + transformedPoints = transformPoints(componentGlyph.points, component); + } else { + // component positioned by matched points + if ((component.matchedPoints[0] > glyph.points.length - 1) || + (component.matchedPoints[1] > componentGlyph.points.length - 1)) { + throw Error('Matched points out of range in ' + glyph.name); + } + var firstPt = glyph.points[component.matchedPoints[0]]; + var secondPt = componentGlyph.points[component.matchedPoints[1]]; + var transform = { + xScale: component.xScale, scale01: component.scale01, + scale10: component.scale10, yScale: component.yScale, + dx: 0, dy: 0 + }; + secondPt = transformPoints([secondPt], transform)[0]; + transform.dx = firstPt.x - secondPt.x; + transform.dy = firstPt.y - secondPt.y; + transformedPoints = transformPoints(componentGlyph.points, transform); + } + glyph.points = glyph.points.concat(transformedPoints); + } + } + } + + return getPath(glyph.points); +} + +function parseGlyfTableAll(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + // The last element of the loca table is invalid. + for (var i = 0; i < loca.length - 1; i += 1) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + } + + return glyphs; +} + +function parseGlyfTableOnLowMemory(data, start, loca, font) { + var glyphs = new glyphset.GlyphSet(font); + + font._push = function(i) { + var offset = loca[i]; + var nextOffset = loca[i + 1]; + if (offset !== nextOffset) { + glyphs.push(i, glyphset.ttfGlyphLoader(font, i, parseGlyph, data, start + offset, buildPath)); + } else { + glyphs.push(i, glyphset.glyphLoader(font, i)); + } + }; + + return glyphs; +} + +// Parse all the glyphs according to the offsets from the `loca` table. +function parseGlyfTable(data, start, loca, font, opt) { + if (opt.lowMemory) + { return parseGlyfTableOnLowMemory(data, start, loca, font); } + else + { return parseGlyfTableAll(data, start, loca, font); } +} + +var glyf = { getPath: getPath, parse: parseGlyfTable}; + +/* A TrueType font hinting interpreter. +* +* (c) 2017 Axel Kittenberger +* +* This interpreter has been implemented according to this documentation: +* https://developer.apple.com/fonts/TrueType-Reference-Manual/RM05/Chap5.html +* +* According to the documentation F24DOT6 values are used for pixels. +* That means calculation is 1/64 pixel accurate and uses integer operations. +* However, Javascript has floating point operations by default and only +* those are available. One could make a case to simulate the 1/64 accuracy +* exactly by truncating after every division operation +* (for example with << 0) to get pixel exactly results as other TrueType +* implementations. It may make sense since some fonts are pixel optimized +* by hand using DELTAP instructions. The current implementation doesn't +* and rather uses full floating point precision. +* +* xScale, yScale and rotation is currently ignored. +* +* A few non-trivial instructions are missing as I didn't encounter yet +* a font that used them to test a possible implementation. +* +* Some fonts seem to use undocumented features regarding the twilight zone. +* Only some of them are implemented as they were encountered. +* +* The exports.DEBUG statements are removed on the minified distribution file. +*/ + +var instructionTable; +var exec; +var execGlyph; +var execComponent; + +/* +* Creates a hinting object. +* +* There ought to be exactly one +* for each truetype font that is used for hinting. +*/ +function Hinting(font) { + // the font this hinting object is for + this.font = font; + + this.getCommands = function (hPoints) { + return glyf.getPath(hPoints).commands; + }; + + // cached states + this._fpgmState = + this._prepState = + undefined; + + // errorState + // 0 ... all okay + // 1 ... had an error in a glyf, + // continue working but stop spamming + // the console + // 2 ... error at prep, stop hinting at this ppem + // 3 ... error at fpeg, stop hinting for this font at all + this._errorState = 0; +} + +/* +* Not rounding. +*/ +function roundOff(v) { + return v; +} + +/* +* Rounding to grid. +*/ +function roundToGrid(v) { + //Rounding in TT is supposed to "symmetrical around zero" + return Math.sign(v) * Math.round(Math.abs(v)); +} + +/* +* Rounding to double grid. +*/ +function roundToDoubleGrid(v) { + return Math.sign(v) * Math.round(Math.abs(v * 2)) / 2; +} + +/* +* Rounding to half grid. +*/ +function roundToHalfGrid(v) { + return Math.sign(v) * (Math.round(Math.abs(v) + 0.5) - 0.5); +} + +/* +* Rounding to up to grid. +*/ +function roundUpToGrid(v) { + return Math.sign(v) * Math.ceil(Math.abs(v)); +} + +/* +* Rounding to down to grid. +*/ +function roundDownToGrid(v) { + return Math.sign(v) * Math.floor(Math.abs(v)); +} + +/* +* Super rounding. +*/ +var roundSuper = function (v) { + var period = this.srPeriod; + var phase = this.srPhase; + var threshold = this.srThreshold; + var sign = 1; + + if (v < 0) { + v = -v; + sign = -1; + } + + v += threshold - phase; + + v = Math.trunc(v / period) * period; + + v += phase; + + // according to http://xgridfit.sourceforge.net/round.html + if (v < 0) { return phase * sign; } + + return v * sign; +}; + +/* +* Unit vector of x-axis. +*/ +var xUnitVector = { + x: 1, + + y: 0, + + axis: 'x', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.xo : p1.x) - (o2 ? p2.xo : p2.x); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.xo - rp1.xo; + do2 = p.xo - rp2.xo; + dm1 = rp1.x - rp1.xo; + dm2 = rp2.x - rp2.xo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.x = p.xo + (dm1 + dm2) / 2; + return; + } + + p.x = p.xo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + xUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + xUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this + normalSlope: Number.NEGATIVE_INFINITY, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd'. + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.x = (org ? rp.xo : rp.x) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.x = rpdx + (p.y - rpdy) / pv.normalSlope; + }, + + // Slope of vector line. + slope: 0, + + // Touches the point p. + touch: function (p) { + p.xTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.xTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.xTouched = false; + } +}; + +/* +* Unit vector of y-axis. +*/ +var yUnitVector = { + x: 0, + + y: 1, + + axis: 'y', + + // Gets the projected distance between two points. + // o1/o2 ... if true, respective original position is used. + distance: function (p1, p2, o1, o2) { + return (o1 ? p1.yo : p1.y) - (o2 ? p2.yo : p2.y); + }, + + // Moves point p so the moved position has the same relative + // position to the moved positions of rp1 and rp2 than the + // original positions had. + // + // See APPENDIX on INTERPOLATE at the bottom of this file. + interpolate: function (p, rp1, rp2, pv) { + var do1; + var do2; + var doa1; + var doa2; + var dm1; + var dm2; + var dt; + + if (!pv || pv === this) { + do1 = p.yo - rp1.yo; + do2 = p.yo - rp2.yo; + dm1 = rp1.y - rp1.yo; + dm2 = rp2.y - rp2.yo; + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + p.y = p.yo + (dm1 + dm2) / 2; + return; + } + + p.y = p.yo + (dm1 * doa2 + dm2 * doa1) / dt; + return; + } + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + yUnitVector.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + yUnitVector.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); + }, + + // Slope of line normal to this. + normalSlope: 0, + + // Sets the point 'p' relative to point 'rp' + // by the distance 'd' + // + // See APPENDIX on SETRELATIVE at the bottom of this file. + // + // p ... point to set + // rp ... reference point + // d ... distance on projection vector + // pv ... projection vector (undefined = this) + // org ... if true, uses the original position of rp as reference. + setRelative: function (p, rp, d, pv, org) { + if (!pv || pv === this) { + p.y = (org ? rp.yo : rp.y) + d; + return; + } + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + p.y = rpdy + pv.normalSlope * (p.x - rpdx); + }, + + // Slope of vector line. + slope: Number.POSITIVE_INFINITY, + + // Touches the point p. + touch: function (p) { + p.yTouched = true; + }, + + // Tests if a point p is touched. + touched: function (p) { + return p.yTouched; + }, + + // Untouches the point p. + untouch: function (p) { + p.yTouched = false; + } +}; + +Object.freeze(xUnitVector); +Object.freeze(yUnitVector); + +/* +* Creates a unit vector that is not x- or y-axis. +*/ +function UnitVector(x, y) { + this.x = x; + this.y = y; + this.axis = undefined; + this.slope = y / x; + this.normalSlope = -x / y; + Object.freeze(this); +} + +/* +* Gets the projected distance between two points. +* o1/o2 ... if true, respective original position is used. +*/ +UnitVector.prototype.distance = function(p1, p2, o1, o2) { + return ( + this.x * xUnitVector.distance(p1, p2, o1, o2) + + this.y * yUnitVector.distance(p1, p2, o1, o2) + ); +}; + +/* +* Moves point p so the moved position has the same relative +* position to the moved positions of rp1 and rp2 than the +* original positions had. +* +* See APPENDIX on INTERPOLATE at the bottom of this file. +*/ +UnitVector.prototype.interpolate = function(p, rp1, rp2, pv) { + var dm1; + var dm2; + var do1; + var do2; + var doa1; + var doa2; + var dt; + + do1 = pv.distance(p, rp1, true, true); + do2 = pv.distance(p, rp2, true, true); + dm1 = pv.distance(rp1, rp1, false, true); + dm2 = pv.distance(rp2, rp2, false, true); + doa1 = Math.abs(do1); + doa2 = Math.abs(do2); + dt = doa1 + doa2; + + if (dt === 0) { + this.setRelative(p, p, (dm1 + dm2) / 2, pv, true); + return; + } + + this.setRelative(p, p, (dm1 * doa2 + dm2 * doa1) / dt, pv, true); +}; + +/* +* Sets the point 'p' relative to point 'rp' +* by the distance 'd' +* +* See APPENDIX on SETRELATIVE at the bottom of this file. +* +* p ... point to set +* rp ... reference point +* d ... distance on projection vector +* pv ... projection vector (undefined = this) +* org ... if true, uses the original position of rp as reference. +*/ +UnitVector.prototype.setRelative = function(p, rp, d, pv, org) { + pv = pv || this; + + var rpx = org ? rp.xo : rp.x; + var rpy = org ? rp.yo : rp.y; + var rpdx = rpx + d * pv.x; + var rpdy = rpy + d * pv.y; + + var pvns = pv.normalSlope; + var fvs = this.slope; + + var px = p.x; + var py = p.y; + + p.x = (fvs * px - pvns * rpdx + rpdy - py) / (fvs - pvns); + p.y = fvs * (p.x - px) + py; +}; + +/* +* Touches the point p. +*/ +UnitVector.prototype.touch = function(p) { + p.xTouched = true; + p.yTouched = true; +}; + +/* +* Returns a unit vector with x/y coordinates. +*/ +function getUnitVector(x, y) { + var d = Math.sqrt(x * x + y * y); + + x /= d; + y /= d; + + if (x === 1 && y === 0) { return xUnitVector; } + else if (x === 0 && y === 1) { return yUnitVector; } + else { return new UnitVector(x, y); } +} + +/* +* Creates a point in the hinting engine. +*/ +function HPoint( + x, + y, + lastPointOfContour, + onCurve +) { + this.x = this.xo = Math.round(x * 64) / 64; // hinted x value and original x-value + this.y = this.yo = Math.round(y * 64) / 64; // hinted y value and original y-value + + this.lastPointOfContour = lastPointOfContour; + this.onCurve = onCurve; + this.prevPointOnContour = undefined; + this.nextPointOnContour = undefined; + this.xTouched = false; + this.yTouched = false; + + Object.preventExtensions(this); +} + +/* +* Returns the next touched point on the contour. +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.nextTouched = function(v) { + var p = this.nextPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.nextPointOnContour; } + + return p; +}; + +/* +* Returns the previous touched point on the contour +* +* v ... unit vector to test touch axis. +*/ +HPoint.prototype.prevTouched = function(v) { + var p = this.prevPointOnContour; + + while (!v.touched(p) && p !== this) { p = p.prevPointOnContour; } + + return p; +}; + +/* +* The zero point. +*/ +var HPZero = Object.freeze(new HPoint(0, 0)); + +/* +* The default state of the interpreter. +* +* Note: Freezing the defaultState and then deriving from it +* makes the V8 Javascript engine going awkward, +* so this is avoided, albeit the defaultState shouldn't +* ever change. +*/ +var defaultState = { + cvCutIn: 17 / 16, // control value cut in + deltaBase: 9, + deltaShift: 0.125, + loop: 1, // loops some instructions + minDis: 1, // minimum distance + autoFlip: true +}; + +/* +* The current state of the interpreter. +* +* env ... 'fpgm' or 'prep' or 'glyf' +* prog ... the program +*/ +function State(env, prog) { + this.env = env; + this.stack = []; + this.prog = prog; + + switch (env) { + case 'glyf' : + this.zp0 = this.zp1 = this.zp2 = 1; + this.rp0 = this.rp1 = this.rp2 = 0; + /* fall through */ + case 'prep' : + this.fv = this.pv = this.dpv = xUnitVector; + this.round = roundToGrid; + } +} + +/* +* Executes a glyph program. +* +* This does the hinting for each glyph. +* +* Returns an array of moved points. +* +* glyph: the glyph to hint +* ppem: the size the glyph is rendered for +*/ +Hinting.prototype.exec = function(glyph, ppem) { + if (typeof ppem !== 'number') { + throw new Error('Point size is not a number!'); + } + + // Received a fatal error, don't do any hinting anymore. + if (this._errorState > 2) { return; } + + var font = this.font; + var prepState = this._prepState; + + if (!prepState || prepState.ppem !== ppem) { + var fpgmState = this._fpgmState; + + if (!fpgmState) { + // Executes the fpgm state. + // This is used by fonts to define functions. + State.prototype = defaultState; + + fpgmState = + this._fpgmState = + new State('fpgm', font.tables.fpgm); + + fpgmState.funcs = [ ]; + fpgmState.font = font; + + if (exports.DEBUG) { + console.log('---EXEC FPGM---'); + fpgmState.step = -1; + } + + try { + exec(fpgmState); + } catch (e) { + console.log('Hinting error in FPGM:' + e); + this._errorState = 3; + return; + } + } + + // Executes the prep program for this ppem setting. + // This is used by fonts to set cvt values + // depending on to be rendered font size. + + State.prototype = fpgmState; + prepState = + this._prepState = + new State('prep', font.tables.prep); + + prepState.ppem = ppem; + + // Creates a copy of the cvt table + // and scales it to the current ppem setting. + var oCvt = font.tables.cvt; + if (oCvt) { + var cvt = prepState.cvt = new Array(oCvt.length); + var scale = ppem / font.unitsPerEm; + for (var c = 0; c < oCvt.length; c++) { + cvt[c] = oCvt[c] * scale; + } + } else { + prepState.cvt = []; + } + + if (exports.DEBUG) { + console.log('---EXEC PREP---'); + prepState.step = -1; + } + + try { + exec(prepState); + } catch (e) { + if (this._errorState < 2) { + console.log('Hinting error in PREP:' + e); + } + this._errorState = 2; + } + } + + if (this._errorState > 1) { return; } + + try { + return execGlyph(glyph, prepState); + } catch (e) { + if (this._errorState < 1) { + console.log('Hinting error:' + e); + console.log('Note: further hinting errors are silenced'); + } + this._errorState = 1; + return undefined; + } +}; + +/* +* Executes the hinting program for a glyph. +*/ +execGlyph = function(glyph, prepState) { + // original point positions + var xScale = prepState.ppem / prepState.font.unitsPerEm; + var yScale = xScale; + var components = glyph.components; + var contours; + var gZone; + var state; + + State.prototype = prepState; + if (!components) { + state = new State('glyf', glyph.instructions); + if (exports.DEBUG) { + console.log('---EXEC GLYPH---'); + state.step = -1; + } + execComponent(glyph, state, xScale, yScale); + gZone = state.gZone; + } else { + var font = prepState.font; + gZone = []; + contours = []; + for (var i = 0; i < components.length; i++) { + var c = components[i]; + var cg = font.glyphs.get(c.glyphIndex); + + state = new State('glyf', cg.instructions); + + if (exports.DEBUG) { + console.log('---EXEC COMP ' + i + '---'); + state.step = -1; + } + + execComponent(cg, state, xScale, yScale); + // appends the computed points to the result array + // post processes the component points + var dx = Math.round(c.dx * xScale); + var dy = Math.round(c.dy * yScale); + var gz = state.gZone; + var cc = state.contours; + for (var pi = 0; pi < gz.length; pi++) { + var p = gz[pi]; + p.xTouched = p.yTouched = false; + p.xo = p.x = p.x + dx; + p.yo = p.y = p.y + dy; + } + + var gLen = gZone.length; + gZone.push.apply(gZone, gz); + for (var j = 0; j < cc.length; j++) { + contours.push(cc[j] + gLen); + } + } + + if (glyph.instructions && !state.inhibitGridFit) { + // the composite has instructions on its own + state = new State('glyf', glyph.instructions); + + state.gZone = state.z0 = state.z1 = state.z2 = gZone; + + state.contours = contours; + + // note: HPZero cannot be used here, since + // the point might be modified + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + if (exports.DEBUG) { + console.log('---EXEC COMPOSITE---'); + state.step = -1; + } + + exec(state); + + gZone.length -= 2; + } + } + + return gZone; +}; + +/* +* Executes the hinting program for a component of a multi-component glyph +* or of the glyph itself for a non-component glyph. +*/ +execComponent = function(glyph, state, xScale, yScale) +{ + var points = glyph.points || []; + var pLen = points.length; + var gZone = state.gZone = state.z0 = state.z1 = state.z2 = []; + var contours = state.contours = []; + + // Scales the original points and + // makes copies for the hinted points. + var cp; // current point + for (var i = 0; i < pLen; i++) { + cp = points[i]; + + gZone[i] = new HPoint( + cp.x * xScale, + cp.y * yScale, + cp.lastPointOfContour, + cp.onCurve + ); + } + + // Chain links the contours. + var sp; // start point + var np; // next point + + for (var i$1 = 0; i$1 < pLen; i$1++) { + cp = gZone[i$1]; + + if (!sp) { + sp = cp; + contours.push(i$1); + } + + if (cp.lastPointOfContour) { + cp.nextPointOnContour = sp; + sp.prevPointOnContour = cp; + sp = undefined; + } else { + np = gZone[i$1 + 1]; + cp.nextPointOnContour = np; + np.prevPointOnContour = cp; + } + } + + if (state.inhibitGridFit) { return; } + + if (exports.DEBUG) { + console.log('PROCESSING GLYPH', state.stack); + for (var i$2 = 0; i$2 < pLen; i$2++) { + console.log(i$2, gZone[i$2].x, gZone[i$2].y); + } + } + + gZone.push( + new HPoint(0, 0), + new HPoint(Math.round(glyph.advanceWidth * xScale), 0) + ); + + exec(state); + + // Removes the extra points. + gZone.length -= 2; + + if (exports.DEBUG) { + console.log('FINISHED GLYPH', state.stack); + for (var i$3 = 0; i$3 < pLen; i$3++) { + console.log(i$3, gZone[i$3].x, gZone[i$3].y); + } + } +}; + +/* +* Executes the program loaded in state. +*/ +exec = function(state) { + var prog = state.prog; + + if (!prog) { return; } + + var pLen = prog.length; + var ins; + + for (state.ip = 0; state.ip < pLen; state.ip++) { + if (exports.DEBUG) { state.step++; } + ins = instructionTable[prog[state.ip]]; + + if (!ins) { + throw new Error( + 'unknown instruction: 0x' + + Number(prog[state.ip]).toString(16) + ); + } + + ins(state); + + // very extensive debugging for each step + /* + if (exports.DEBUG) { + var da; + if (state.gZone) { + da = []; + for (let i = 0; i < state.gZone.length; i++) + { + da.push(i + ' ' + + state.gZone[i].x * 64 + ' ' + + state.gZone[i].y * 64 + ' ' + + (state.gZone[i].xTouched ? 'x' : '') + + (state.gZone[i].yTouched ? 'y' : '') + ); + } + console.log('GZ', da); + } + + if (state.tZone) { + da = []; + for (let i = 0; i < state.tZone.length; i++) { + da.push(i + ' ' + + state.tZone[i].x * 64 + ' ' + + state.tZone[i].y * 64 + ' ' + + (state.tZone[i].xTouched ? 'x' : '') + + (state.tZone[i].yTouched ? 'y' : '') + ); + } + console.log('TZ', da); + } + + if (state.stack.length > 10) { + console.log( + state.stack.length, + '...', state.stack.slice(state.stack.length - 10) + ); + } else { + console.log(state.stack.length, state.stack); + } + } + */ + } +}; + +/* +* Initializes the twilight zone. +* +* This is only done if a SZPx instruction +* refers to the twilight zone. +*/ +function initTZone(state) +{ + var tZone = state.tZone = new Array(state.gZone.length); + + // no idea if this is actually correct... + for (var i = 0; i < tZone.length; i++) + { + tZone[i] = new HPoint(0, 0); + } +} + +/* +* Skips the instruction pointer ahead over an IF/ELSE block. +* handleElse .. if true breaks on matching ELSE +*/ +function skip(state, handleElse) +{ + var prog = state.prog; + var ip = state.ip; + var nesting = 1; + var ins; + + do { + ins = prog[++ip]; + if (ins === 0x58) // IF + { nesting++; } + else if (ins === 0x59) // EIF + { nesting--; } + else if (ins === 0x40) // NPUSHB + { ip += prog[ip + 1] + 1; } + else if (ins === 0x41) // NPUSHW + { ip += 2 * prog[ip + 1] + 1; } + else if (ins >= 0xB0 && ins <= 0xB7) // PUSHB + { ip += ins - 0xB0 + 1; } + else if (ins >= 0xB8 && ins <= 0xBF) // PUSHW + { ip += (ins - 0xB8 + 1) * 2; } + else if (handleElse && nesting === 1 && ins === 0x1B) // ELSE + { break; } + } while (nesting > 0); + + state.ip = ip; +} + +/*----------------------------------------------------------* +* And then a lot of instructions... * +*----------------------------------------------------------*/ + +// SVTCA[a] Set freedom and projection Vectors To Coordinate Axis +// 0x00-0x01 +function SVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SVTCA[' + v.axis + ']'); } + + state.fv = state.pv = state.dpv = v; +} + +// SPVTCA[a] Set Projection Vector to Coordinate Axis +// 0x02-0x03 +function SPVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SPVTCA[' + v.axis + ']'); } + + state.pv = state.dpv = v; +} + +// SFVTCA[a] Set Freedom Vector to Coordinate Axis +// 0x04-0x05 +function SFVTCA(v, state) { + if (exports.DEBUG) { console.log(state.step, 'SFVTCA[' + v.axis + ']'); } + + state.fv = v; +} + +// SPVTL[a] Set Projection Vector To Line +// 0x06-0x07 +function SPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.pv = state.dpv = getUnitVector(dx, dy); +} + +// SFVTL[a] Set Freedom Vector To Line +// 0x08-0x09 +function SFVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log('SFVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.fv = getUnitVector(dx, dy); +} + +// SPVFS[] Set Projection Vector From Stack +// 0x0A +function SPVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.pv = state.dpv = getUnitVector(x, y); +} + +// SFVFS[] Set Freedom Vector From Stack +// 0x0B +function SFVFS(state) { + var stack = state.stack; + var y = stack.pop(); + var x = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SPVFS[]', y, x); } + + state.fv = getUnitVector(x, y); +} + +// GPV[] Get Projection Vector +// 0x0C +function GPV(state) { + var stack = state.stack; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'GPV[]'); } + + stack.push(pv.x * 0x4000); + stack.push(pv.y * 0x4000); +} + +// GFV[] Get Freedom Vector +// 0x0C +function GFV(state) { + var stack = state.stack; + var fv = state.fv; + + if (exports.DEBUG) { console.log(state.step, 'GFV[]'); } + + stack.push(fv.x * 0x4000); + stack.push(fv.y * 0x4000); +} + +// SFVTPV[] Set Freedom Vector To Projection Vector +// 0x0E +function SFVTPV(state) { + state.fv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'SFVTPV[]'); } +} + +// ISECT[] moves point p to the InterSECTion of two lines +// 0x0F +function ISECT(state) +{ + var stack = state.stack; + var pa0i = stack.pop(); + var pa1i = stack.pop(); + var pb0i = stack.pop(); + var pb1i = stack.pop(); + var pi = stack.pop(); + var z0 = state.z0; + var z1 = state.z1; + var pa0 = z0[pa0i]; + var pa1 = z0[pa1i]; + var pb0 = z1[pb0i]; + var pb1 = z1[pb1i]; + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log('ISECT[], ', pa0i, pa1i, pb0i, pb1i, pi); } + + // math from + // en.wikipedia.org/wiki/Line%E2%80%93line_intersection#Given_two_points_on_each_line + + var x1 = pa0.x; + var y1 = pa0.y; + var x2 = pa1.x; + var y2 = pa1.y; + var x3 = pb0.x; + var y3 = pb0.y; + var x4 = pb1.x; + var y4 = pb1.y; + + var div = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); + var f1 = x1 * y2 - y1 * x2; + var f2 = x3 * y4 - y3 * x4; + + p.x = (f1 * (x3 - x4) - f2 * (x1 - x2)) / div; + p.y = (f1 * (y3 - y4) - f2 * (y1 - y2)) / div; +} + +// SRP0[] Set Reference Point 0 +// 0x10 +function SRP0(state) { + state.rp0 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP0[]', state.rp0); } +} + +// SRP1[] Set Reference Point 1 +// 0x11 +function SRP1(state) { + state.rp1 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP1[]', state.rp1); } +} + +// SRP1[] Set Reference Point 2 +// 0x12 +function SRP2(state) { + state.rp2 = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SRP2[]', state.rp2); } +} + +// SZP0[] Set Zone Pointer 0 +// 0x13 +function SZP0(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP0[]', n); } + + state.zp0 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.tZone; + break; + case 1 : + state.z0 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZP1[] Set Zone Pointer 1 +// 0x14 +function SZP1(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP1[]', n); } + + state.zp1 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z1 = state.tZone; + break; + case 1 : + state.z1 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZP2[] Set Zone Pointer 2 +// 0x15 +function SZP2(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZP2[]', n); } + + state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z2 = state.tZone; + break; + case 1 : + state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SZPS[] Set Zone PointerS +// 0x16 +function SZPS(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SZPS[]', n); } + + state.zp0 = state.zp1 = state.zp2 = n; + + switch (n) { + case 0: + if (!state.tZone) { initTZone(state); } + state.z0 = state.z1 = state.z2 = state.tZone; + break; + case 1 : + state.z0 = state.z1 = state.z2 = state.gZone; + break; + default : + throw new Error('Invalid zone pointer'); + } +} + +// SLOOP[] Set LOOP variable +// 0x17 +function SLOOP(state) { + state.loop = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SLOOP[]', state.loop); } +} + +// RTG[] Round To Grid +// 0x18 +function RTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTG[]'); } + + state.round = roundToGrid; +} + +// RTHG[] Round To Half Grid +// 0x19 +function RTHG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTHG[]'); } + + state.round = roundToHalfGrid; +} + +// SMD[] Set Minimum Distance +// 0x1A +function SMD(state) { + var d = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SMD[]', d); } + + state.minDis = d / 0x40; +} + +// ELSE[] ELSE clause +// 0x1B +function ELSE(state) { + // This instruction has been reached by executing a then branch + // so it just skips ahead until matching EIF. + // + // In case the IF was negative the IF[] instruction already + // skipped forward over the ELSE[] + + if (exports.DEBUG) { console.log(state.step, 'ELSE[]'); } + + skip(state, false); +} + +// JMPR[] JuMP Relative +// 0x1C +function JMPR(state) { + var o = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'JMPR[]', o); } + + // A jump by 1 would do nothing. + state.ip += o - 1; +} + +// SCVTCI[] Set Control Value Table Cut-In +// 0x1D +function SCVTCI(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SCVTCI[]', n); } + + state.cvCutIn = n / 0x40; +} + +// DUP[] DUPlicate top stack element +// 0x20 +function DUP(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DUP[]'); } + + stack.push(stack[stack.length - 1]); +} + +// POP[] POP top stack element +// 0x21 +function POP(state) { + if (exports.DEBUG) { console.log(state.step, 'POP[]'); } + + state.stack.pop(); +} + +// CLEAR[] CLEAR the stack +// 0x22 +function CLEAR(state) { + if (exports.DEBUG) { console.log(state.step, 'CLEAR[]'); } + + state.stack.length = 0; +} + +// SWAP[] SWAP the top two elements on the stack +// 0x23 +function SWAP(state) { + var stack = state.stack; + + var a = stack.pop(); + var b = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SWAP[]'); } + + stack.push(a); + stack.push(b); +} + +// DEPTH[] DEPTH of the stack +// 0x24 +function DEPTH(state) { + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.step, 'DEPTH[]'); } + + stack.push(stack.length); +} + +// LOOPCALL[] LOOPCALL function +// 0x2A +function LOOPCALL(state) { + var stack = state.stack; + var fn = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LOOPCALL[]', fn, c); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + for (var i = 0; i < c; i++) { + exec(state); + + if (exports.DEBUG) { console.log( + ++state.step, + i + 1 < c ? 'next loopcall' : 'done loopcall', + i + ); } + } + + // restores the callers program + state.ip = cip; + state.prog = cprog; +} + +// CALL[] CALL function +// 0x2B +function CALL(state) { + var fn = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CALL[]', fn); } + + // saves callers program + var cip = state.ip; + var cprog = state.prog; + + state.prog = state.funcs[fn]; + + // executes the function + exec(state); + + // restores the callers program + state.ip = cip; + state.prog = cprog; + + if (exports.DEBUG) { console.log(++state.step, 'returning from', fn); } +} + +// CINDEX[] Copy the INDEXed element to the top of the stack +// 0x25 +function CINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CINDEX[]', k); } + + // In case of k == 1, it copies the last element after popping + // thus stack.length - k. + stack.push(stack[stack.length - k]); +} + +// MINDEX[] Move the INDEXed element to the top of the stack +// 0x26 +function MINDEX(state) { + var stack = state.stack; + var k = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MINDEX[]', k); } + + stack.push(stack.splice(stack.length - k, 1)[0]); +} + +// FDEF[] Function DEFinition +// 0x2C +function FDEF(state) { + if (state.env !== 'fpgm') { throw new Error('FDEF not allowed here'); } + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + var fn = stack.pop(); + var ipBegin = ip; + + if (exports.DEBUG) { console.log(state.step, 'FDEF[]', fn); } + + while (prog[++ip] !== 0x2D){ } + + state.ip = ip; + state.funcs[fn] = prog.slice(ipBegin + 1, ip); +} + +// MDAP[a] Move Direct Absolute Point +// 0x2E-0x2F +function MDAP(round, state) { + var pi = state.stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + + if (exports.DEBUG) { console.log(state.step, 'MDAP[' + round + ']', pi); } + + var d = pv.distance(p, HPZero); + + if (round) { d = state.round(d); } + + fv.setRelative(p, HPZero, d, pv); + fv.touch(p); + + state.rp0 = state.rp1 = pi; +} + +// IUP[a] Interpolate Untouched Points through the outline +// 0x30 +function IUP(v, state) { + var z2 = state.z2; + var pLen = z2.length - 2; + var cp; + var pp; + var np; + + if (exports.DEBUG) { console.log(state.step, 'IUP[' + v.axis + ']'); } + + for (var i = 0; i < pLen; i++) { + cp = z2[i]; // current point + + // if this point has been touched go on + if (v.touched(cp)) { continue; } + + pp = cp.prevTouched(v); + + // no point on the contour has been touched? + if (pp === cp) { continue; } + + np = cp.nextTouched(v); + + if (pp === np) { + // only one point on the contour has been touched + // so simply moves the point like that + + v.setRelative(cp, cp, v.distance(pp, pp, false, true), v, true); + } + + v.interpolate(cp, pp, np, v); + } +} + +// SHP[] SHift Point using reference point +// 0x32-0x33 +function SHP(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var loop = state.loop; + var z2 = state.z2; + + while (loop--) + { + var pi = stack.pop(); + var p = z2[pi]; + + var d = pv.distance(rp, rp, false, true); + fv.setRelative(p, p, d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? + 'loop ' + (state.loop - loop) + ': ' : + '' + ) + + 'SHP[' + (a ? 'rp1' : 'rp2') + ']', pi + ); + } + } + + state.loop = 1; +} + +// SHC[] SHift Contour using reference point +// 0x36-0x37 +function SHC(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + var ci = stack.pop(); + var sp = state.z2[state.contours[ci]]; + var p = sp; + + if (exports.DEBUG) { console.log(state.step, 'SHC[' + a + ']', ci); } + + var d = pv.distance(rp, rp, false, true); + + do { + if (p !== rp) { fv.setRelative(p, p, d, pv); } + p = p.nextPointOnContour; + } while (p !== sp); +} + +// SHZ[] SHift Zone using reference point +// 0x36-0x37 +function SHZ(a, state) { + var stack = state.stack; + var rpi = a ? state.rp1 : state.rp2; + var rp = (a ? state.z0 : state.z1)[rpi]; + var fv = state.fv; + var pv = state.pv; + + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SHZ[' + a + ']', e); } + + var z; + switch (e) { + case 0 : z = state.tZone; break; + case 1 : z = state.gZone; break; + default : throw new Error('Invalid zone'); + } + + var p; + var d = pv.distance(rp, rp, false, true); + var pLen = z.length - 2; + for (var i = 0; i < pLen; i++) + { + p = z[i]; + fv.setRelative(p, p, d, pv); + //if (p !== rp) fv.setRelative(p, p, d, pv); + } +} + +// SHPIX[] SHift point by a PIXel amount +// 0x38 +function SHPIX(state) { + var stack = state.stack; + var loop = state.loop; + var fv = state.fv; + var d = stack.pop() / 0x40; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'SHPIX[]', pi, d + ); + } + + fv.setRelative(p, p, d); + fv.touch(p); + } + + state.loop = 1; +} + +// IP[] Interpolate Point +// 0x39 +function IP(state) { + var stack = state.stack; + var rp1i = state.rp1; + var rp2i = state.rp2; + var loop = state.loop; + var rp1 = state.z0[rp1i]; + var rp2 = state.z1[rp2i]; + var fv = state.fv; + var pv = state.dpv; + var z2 = state.z2; + + while (loop--) { + var pi = stack.pop(); + var p = z2[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'IP[]', pi, rp1i, '<->', rp2i + ); + } + + fv.interpolate(p, rp1, rp2, pv); + + fv.touch(p); + } + + state.loop = 1; +} + +// MSIRP[a] Move Stack Indirect Relative Point +// 0x3A-0x3B +function MSIRP(a, state) { + var stack = state.stack; + var d = stack.pop() / 64; + var pi = stack.pop(); + var p = state.z1[pi]; + var rp0 = state.z0[state.rp0]; + var fv = state.fv; + var pv = state.pv; + + fv.setRelative(p, rp0, d, pv); + fv.touch(p); + + if (exports.DEBUG) { console.log(state.step, 'MSIRP[' + a + ']', d, pi); } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (a) { state.rp0 = pi; } +} + +// ALIGNRP[] Align to reference point. +// 0x3C +function ALIGNRP(state) { + var stack = state.stack; + var rp0i = state.rp0; + var rp0 = state.z0[rp0i]; + var loop = state.loop; + var fv = state.fv; + var pv = state.pv; + var z1 = state.z1; + + while (loop--) { + var pi = stack.pop(); + var p = z1[pi]; + + if (exports.DEBUG) { + console.log( + state.step, + (state.loop > 1 ? 'loop ' + (state.loop - loop) + ': ' : '') + + 'ALIGNRP[]', pi + ); + } + + fv.setRelative(p, rp0, 0, pv); + fv.touch(p); + } + + state.loop = 1; +} + +// RTG[] Round To Double Grid +// 0x3D +function RTDG(state) { + if (exports.DEBUG) { console.log(state.step, 'RTDG[]'); } + + state.round = roundToDoubleGrid; +} + +// MIAP[a] Move Indirect Absolute Point +// 0x3E-0x3F +function MIAP(round, state) { + var stack = state.stack; + var n = stack.pop(); + var pi = stack.pop(); + var p = state.z0[pi]; + var fv = state.fv; + var pv = state.pv; + var cv = state.cvt[n]; + + if (exports.DEBUG) { + console.log( + state.step, + 'MIAP[' + round + ']', + n, '(', cv, ')', pi + ); + } + + var d = pv.distance(p, HPZero); + + if (round) { + if (Math.abs(d - cv) < state.cvCutIn) { d = cv; } + + d = state.round(d); + } + + fv.setRelative(p, HPZero, d, pv); + + if (state.zp0 === 0) { + p.xo = p.x; + p.yo = p.y; + } + + fv.touch(p); + + state.rp0 = state.rp1 = pi; +} + +// NPUSB[] PUSH N Bytes +// 0x40 +function NPUSHB(state) { + var prog = state.prog; + var ip = state.ip; + var stack = state.stack; + + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHB[]', n); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; +} + +// NPUSHW[] PUSH N Words +// 0x41 +function NPUSHW(state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + var n = prog[++ip]; + + if (exports.DEBUG) { console.log(state.step, 'NPUSHW[]', n); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; +} + +// WS[] Write Store +// 0x42 +function WS(state) { + var stack = state.stack; + var store = state.store; + + if (!store) { store = state.store = []; } + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WS', v, l); } + + store[l] = v; +} + +// RS[] Read Store +// 0x43 +function RS(state) { + var stack = state.stack; + var store = state.store; + + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RS', l); } + + var v = (store && store[l]) || 0; + + stack.push(v); +} + +// WCVTP[] Write Control Value Table in Pixel units +// 0x44 +function WCVTP(state) { + var stack = state.stack; + + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTP', v, l); } + + state.cvt[l] = v / 0x40; +} + +// RCVT[] Read Control Value Table entry +// 0x45 +function RCVT(state) { + var stack = state.stack; + var cvte = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'RCVT', cvte); } + + stack.push(state.cvt[cvte] * 0x40); +} + +// GC[] Get Coordinate projected onto the projection vector +// 0x46-0x47 +function GC(a, state) { + var stack = state.stack; + var pi = stack.pop(); + var p = state.z2[pi]; + + if (exports.DEBUG) { console.log(state.step, 'GC[' + a + ']', pi); } + + stack.push(state.dpv.distance(p, HPZero, a, false) * 0x40); +} + +// MD[a] Measure Distance +// 0x49-0x4A +function MD(a, state) { + var stack = state.stack; + var pi2 = stack.pop(); + var pi1 = stack.pop(); + var p2 = state.z1[pi2]; + var p1 = state.z0[pi1]; + var d = state.dpv.distance(p1, p2, a, a); + + if (exports.DEBUG) { console.log(state.step, 'MD[' + a + ']', pi2, pi1, '->', d); } + + state.stack.push(Math.round(d * 64)); +} + +// MPPEM[] Measure Pixels Per EM +// 0x4B +function MPPEM(state) { + if (exports.DEBUG) { console.log(state.step, 'MPPEM[]'); } + state.stack.push(state.ppem); +} + +// FLIPON[] set the auto FLIP Boolean to ON +// 0x4D +function FLIPON(state) { + if (exports.DEBUG) { console.log(state.step, 'FLIPON[]'); } + state.autoFlip = true; +} + +// LT[] Less Than +// 0x50 +function LT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LT[]', e2, e1); } + + stack.push(e1 < e2 ? 1 : 0); +} + +// LTEQ[] Less Than or EQual +// 0x53 +function LTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'LTEQ[]', e2, e1); } + + stack.push(e1 <= e2 ? 1 : 0); +} + +// GTEQ[] Greater Than +// 0x52 +function GT(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GT[]', e2, e1); } + + stack.push(e1 > e2 ? 1 : 0); +} + +// GTEQ[] Greater Than or EQual +// 0x53 +function GTEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'GTEQ[]', e2, e1); } + + stack.push(e1 >= e2 ? 1 : 0); +} + +// EQ[] EQual +// 0x54 +function EQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EQ[]', e2, e1); } + + stack.push(e2 === e1 ? 1 : 0); +} + +// NEQ[] Not EQual +// 0x55 +function NEQ(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEQ[]', e2, e1); } + + stack.push(e2 !== e1 ? 1 : 0); +} + +// ODD[] ODD +// 0x56 +function ODD(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ODD[]', n); } + + stack.push(Math.trunc(n) % 2 ? 1 : 0); +} + +// EVEN[] EVEN +// 0x57 +function EVEN(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'EVEN[]', n); } + + stack.push(Math.trunc(n) % 2 ? 0 : 1); +} + +// IF[] IF test +// 0x58 +function IF(state) { + var test = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'IF[]', test); } + + // if test is true it just continues + // if not the ip is skipped until matching ELSE or EIF + if (!test) { + skip(state, true); + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } + } +} + +// EIF[] End IF +// 0x59 +function EIF(state) { + // this can be reached normally when + // executing an else branch. + // -> just ignore it + + if (exports.DEBUG) { console.log(state.step, 'EIF[]'); } +} + +// AND[] logical AND +// 0x5A +function AND(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'AND[]', e2, e1); } + + stack.push(e2 && e1 ? 1 : 0); +} + +// OR[] logical OR +// 0x5B +function OR(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'OR[]', e2, e1); } + + stack.push(e2 || e1 ? 1 : 0); +} + +// NOT[] logical NOT +// 0x5C +function NOT(state) { + var stack = state.stack; + var e = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NOT[]', e); } + + stack.push(e ? 0 : 1); +} + +// DELTAP1[] DELTA exception P1 +// DELTAP2[] DELTA exception P2 +// DELTAP3[] DELTA exception P3 +// 0x5D, 0x71, 0x72 +function DELTAP123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var fv = state.fv; + var pv = state.pv; + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + var z0 = state.z0; + + if (exports.DEBUG) { console.log(state.step, 'DELTAP[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var pi = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + if (exports.DEBUG) { console.log(state.step, 'DELTAPFIX', pi, 'by', mag * ds); } + + var p = z0[pi]; + fv.setRelative(p, p, mag * ds, pv); + } +} + +// SDB[] Set Delta Base in the graphics state +// 0x5E +function SDB(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDB[]', n); } + + state.deltaBase = n; +} + +// SDS[] Set Delta Shift in the graphics state +// 0x5F +function SDS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SDS[]', n); } + + state.deltaShift = Math.pow(0.5, n); +} + +// ADD[] ADD +// 0x60 +function ADD(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ADD[]', n2, n1); } + + stack.push(n1 + n2); +} + +// SUB[] SUB +// 0x61 +function SUB(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SUB[]', n2, n1); } + + stack.push(n1 - n2); +} + +// DIV[] DIV +// 0x62 +function DIV(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'DIV[]', n2, n1); } + + stack.push(n1 * 64 / n2); +} + +// MUL[] MUL +// 0x63 +function MUL(state) { + var stack = state.stack; + var n2 = stack.pop(); + var n1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MUL[]', n2, n1); } + + stack.push(n1 * n2 / 64); +} + +// ABS[] ABSolute value +// 0x64 +function ABS(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ABS[]', n); } + + stack.push(Math.abs(n)); +} + +// NEG[] NEGate +// 0x65 +function NEG(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'NEG[]', n); } + + stack.push(-n); +} + +// FLOOR[] FLOOR +// 0x66 +function FLOOR(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'FLOOR[]', n); } + + stack.push(Math.floor(n / 0x40) * 0x40); +} + +// CEILING[] CEILING +// 0x67 +function CEILING(state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'CEILING[]', n); } + + stack.push(Math.ceil(n / 0x40) * 0x40); +} + +// ROUND[ab] ROUND value +// 0x68-0x6B +function ROUND(dt, state) { + var stack = state.stack; + var n = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROUND[]'); } + + stack.push(state.round(n / 0x40) * 0x40); +} + +// WCVTF[] Write Control Value Table in Funits +// 0x70 +function WCVTF(state) { + var stack = state.stack; + var v = stack.pop(); + var l = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'WCVTF[]', v, l); } + + state.cvt[l] = v * state.ppem / state.font.unitsPerEm; +} + +// DELTAC1[] DELTA exception C1 +// DELTAC2[] DELTA exception C2 +// DELTAC3[] DELTA exception C3 +// 0x73, 0x74, 0x75 +function DELTAC123(b, state) { + var stack = state.stack; + var n = stack.pop(); + var ppem = state.ppem; + var base = state.deltaBase + (b - 1) * 16; + var ds = state.deltaShift; + + if (exports.DEBUG) { console.log(state.step, 'DELTAC[' + b + ']', n, stack); } + + for (var i = 0; i < n; i++) { + var c = stack.pop(); + var arg = stack.pop(); + var appem = base + ((arg & 0xF0) >> 4); + if (appem !== ppem) { continue; } + + var mag = (arg & 0x0F) - 8; + if (mag >= 0) { mag++; } + + var delta = mag * ds; + + if (exports.DEBUG) { console.log(state.step, 'DELTACFIX', c, 'by', delta); } + + state.cvt[c] += delta; + } +} + +// SROUND[] Super ROUND +// 0x76 +function SROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'SROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = 0.5; + break; + case 0x40: + period = 1; + break; + case 0x80: + period = 2; + break; + default: + throw new Error('invalid SROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: throw new Error('invalid SROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} + +// S45ROUND[] Super ROUND 45 degrees +// 0x77 +function S45ROUND(state) { + var n = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'S45ROUND[]', n); } + + state.round = roundSuper; + + var period; + + switch (n & 0xC0) { + case 0x00: + period = Math.sqrt(2) / 2; + break; + case 0x40: + period = Math.sqrt(2); + break; + case 0x80: + period = 2 * Math.sqrt(2); + break; + default: + throw new Error('invalid S45ROUND value'); + } + + state.srPeriod = period; + + switch (n & 0x30) { + case 0x00: + state.srPhase = 0; + break; + case 0x10: + state.srPhase = 0.25 * period; + break; + case 0x20: + state.srPhase = 0.5 * period; + break; + case 0x30: + state.srPhase = 0.75 * period; + break; + default: + throw new Error('invalid S45ROUND value'); + } + + n &= 0x0F; + + if (n === 0) { state.srThreshold = 0; } + else { state.srThreshold = (n / 8 - 0.5) * period; } +} + +// ROFF[] Round Off +// 0x7A +function ROFF(state) { + if (exports.DEBUG) { console.log(state.step, 'ROFF[]'); } + + state.round = roundOff; +} + +// RUTG[] Round Up To Grid +// 0x7C +function RUTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RUTG[]'); } + + state.round = roundUpToGrid; +} + +// RDTG[] Round Down To Grid +// 0x7D +function RDTG(state) { + if (exports.DEBUG) { console.log(state.step, 'RDTG[]'); } + + state.round = roundDownToGrid; +} + +// SCANCTRL[] SCAN conversion ConTRoL +// 0x85 +function SCANCTRL(state) { + var n = state.stack.pop(); + + // ignored by opentype.js + + if (exports.DEBUG) { console.log(state.step, 'SCANCTRL[]', n); } +} + +// SDPVTL[a] Set Dual Projection Vector To Line +// 0x86-0x87 +function SDPVTL(a, state) { + var stack = state.stack; + var p2i = stack.pop(); + var p1i = stack.pop(); + var p2 = state.z2[p2i]; + var p1 = state.z1[p1i]; + + if (exports.DEBUG) { console.log(state.step, 'SDPVTL[' + a + ']', p2i, p1i); } + + var dx; + var dy; + + if (!a) { + dx = p1.x - p2.x; + dy = p1.y - p2.y; + } else { + dx = p2.y - p1.y; + dy = p1.x - p2.x; + } + + state.dpv = getUnitVector(dx, dy); +} + +// GETINFO[] GET INFOrmation +// 0x88 +function GETINFO(state) { + var stack = state.stack; + var sel = stack.pop(); + var r = 0; + + if (exports.DEBUG) { console.log(state.step, 'GETINFO[]', sel); } + + // v35 as in no subpixel hinting + if (sel & 0x01) { r = 35; } + + // TODO rotation and stretch currently not supported + // and thus those GETINFO are always 0. + + // opentype.js is always gray scaling + if (sel & 0x20) { r |= 0x1000; } + + stack.push(r); +} + +// ROLL[] ROLL the top three stack elements +// 0x8A +function ROLL(state) { + var stack = state.stack; + var a = stack.pop(); + var b = stack.pop(); + var c = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'ROLL[]'); } + + stack.push(b); + stack.push(a); + stack.push(c); +} + +// MAX[] MAXimum of top two stack elements +// 0x8B +function MAX(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MAX[]', e2, e1); } + + stack.push(Math.max(e1, e2)); +} + +// MIN[] MINimum of top two stack elements +// 0x8C +function MIN(state) { + var stack = state.stack; + var e2 = stack.pop(); + var e1 = stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'MIN[]', e2, e1); } + + stack.push(Math.min(e1, e2)); +} + +// SCANTYPE[] SCANTYPE +// 0x8D +function SCANTYPE(state) { + var n = state.stack.pop(); + // ignored by opentype.js + if (exports.DEBUG) { console.log(state.step, 'SCANTYPE[]', n); } +} + +// INSTCTRL[] INSTCTRL +// 0x8D +function INSTCTRL(state) { + var s = state.stack.pop(); + var v = state.stack.pop(); + + if (exports.DEBUG) { console.log(state.step, 'INSTCTRL[]', s, v); } + + switch (s) { + case 1 : state.inhibitGridFit = !!v; return; + case 2 : state.ignoreCvt = !!v; return; + default: throw new Error('invalid INSTCTRL[] selector'); + } +} + +// PUSHB[abc] PUSH Bytes +// 0xB0-0xB7 +function PUSHB(n, state) { + var stack = state.stack; + var prog = state.prog; + var ip = state.ip; + + if (exports.DEBUG) { console.log(state.step, 'PUSHB[' + n + ']'); } + + for (var i = 0; i < n; i++) { stack.push(prog[++ip]); } + + state.ip = ip; +} + +// PUSHW[abc] PUSH Words +// 0xB8-0xBF +function PUSHW(n, state) { + var ip = state.ip; + var prog = state.prog; + var stack = state.stack; + + if (exports.DEBUG) { console.log(state.ip, 'PUSHW[' + n + ']'); } + + for (var i = 0; i < n; i++) { + var w = (prog[++ip] << 8) | prog[++ip]; + if (w & 0x8000) { w = -((w ^ 0xffff) + 1); } + stack.push(w); + } + + state.ip = ip; +} + +// MDRP[abcde] Move Direct Relative Point +// 0xD0-0xEF +// (if indirect is 0) +// +// and +// +// MIRP[abcde] Move Indirect Relative Point +// 0xE0-0xFF +// (if indirect is 1) + +function MDRP_MIRP(indirect, setRp0, keepD, ro, dt, state) { + var stack = state.stack; + var cvte = indirect && stack.pop(); + var pi = stack.pop(); + var rp0i = state.rp0; + var rp = state.z0[rp0i]; + var p = state.z1[pi]; + + var md = state.minDis; + var fv = state.fv; + var pv = state.dpv; + var od; // original distance + var d; // moving distance + var sign; // sign of distance + var cv; + + d = od = pv.distance(p, rp, true, true); + sign = d >= 0 ? 1 : -1; // Math.sign would be 0 in case of 0 + + // TODO consider autoFlip + d = Math.abs(d); + + if (indirect) { + cv = state.cvt[cvte]; + + if (ro && Math.abs(d - cv) < state.cvCutIn) { d = cv; } + } + + if (keepD && d < md) { d = md; } + + if (ro) { d = state.round(d); } + + fv.setRelative(p, rp, sign * d, pv); + fv.touch(p); + + if (exports.DEBUG) { + console.log( + state.step, + (indirect ? 'MIRP[' : 'MDRP[') + + (setRp0 ? 'M' : 'm') + + (keepD ? '>' : '_') + + (ro ? 'R' : '_') + + (dt === 0 ? 'Gr' : (dt === 1 ? 'Bl' : (dt === 2 ? 'Wh' : ''))) + + ']', + indirect ? + cvte + '(' + state.cvt[cvte] + ',' + cv + ')' : + '', + pi, + '(d =', od, '->', sign * d, ')' + ); + } + + state.rp1 = state.rp0; + state.rp2 = pi; + if (setRp0) { state.rp0 = pi; } +} + +/* +* The instruction table. +*/ +instructionTable = [ + /* 0x00 */ SVTCA.bind(undefined, yUnitVector), + /* 0x01 */ SVTCA.bind(undefined, xUnitVector), + /* 0x02 */ SPVTCA.bind(undefined, yUnitVector), + /* 0x03 */ SPVTCA.bind(undefined, xUnitVector), + /* 0x04 */ SFVTCA.bind(undefined, yUnitVector), + /* 0x05 */ SFVTCA.bind(undefined, xUnitVector), + /* 0x06 */ SPVTL.bind(undefined, 0), + /* 0x07 */ SPVTL.bind(undefined, 1), + /* 0x08 */ SFVTL.bind(undefined, 0), + /* 0x09 */ SFVTL.bind(undefined, 1), + /* 0x0A */ SPVFS, + /* 0x0B */ SFVFS, + /* 0x0C */ GPV, + /* 0x0D */ GFV, + /* 0x0E */ SFVTPV, + /* 0x0F */ ISECT, + /* 0x10 */ SRP0, + /* 0x11 */ SRP1, + /* 0x12 */ SRP2, + /* 0x13 */ SZP0, + /* 0x14 */ SZP1, + /* 0x15 */ SZP2, + /* 0x16 */ SZPS, + /* 0x17 */ SLOOP, + /* 0x18 */ RTG, + /* 0x19 */ RTHG, + /* 0x1A */ SMD, + /* 0x1B */ ELSE, + /* 0x1C */ JMPR, + /* 0x1D */ SCVTCI, + /* 0x1E */ undefined, // TODO SSWCI + /* 0x1F */ undefined, // TODO SSW + /* 0x20 */ DUP, + /* 0x21 */ POP, + /* 0x22 */ CLEAR, + /* 0x23 */ SWAP, + /* 0x24 */ DEPTH, + /* 0x25 */ CINDEX, + /* 0x26 */ MINDEX, + /* 0x27 */ undefined, // TODO ALIGNPTS + /* 0x28 */ undefined, + /* 0x29 */ undefined, // TODO UTP + /* 0x2A */ LOOPCALL, + /* 0x2B */ CALL, + /* 0x2C */ FDEF, + /* 0x2D */ undefined, // ENDF (eaten by FDEF) + /* 0x2E */ MDAP.bind(undefined, 0), + /* 0x2F */ MDAP.bind(undefined, 1), + /* 0x30 */ IUP.bind(undefined, yUnitVector), + /* 0x31 */ IUP.bind(undefined, xUnitVector), + /* 0x32 */ SHP.bind(undefined, 0), + /* 0x33 */ SHP.bind(undefined, 1), + /* 0x34 */ SHC.bind(undefined, 0), + /* 0x35 */ SHC.bind(undefined, 1), + /* 0x36 */ SHZ.bind(undefined, 0), + /* 0x37 */ SHZ.bind(undefined, 1), + /* 0x38 */ SHPIX, + /* 0x39 */ IP, + /* 0x3A */ MSIRP.bind(undefined, 0), + /* 0x3B */ MSIRP.bind(undefined, 1), + /* 0x3C */ ALIGNRP, + /* 0x3D */ RTDG, + /* 0x3E */ MIAP.bind(undefined, 0), + /* 0x3F */ MIAP.bind(undefined, 1), + /* 0x40 */ NPUSHB, + /* 0x41 */ NPUSHW, + /* 0x42 */ WS, + /* 0x43 */ RS, + /* 0x44 */ WCVTP, + /* 0x45 */ RCVT, + /* 0x46 */ GC.bind(undefined, 0), + /* 0x47 */ GC.bind(undefined, 1), + /* 0x48 */ undefined, // TODO SCFS + /* 0x49 */ MD.bind(undefined, 0), + /* 0x4A */ MD.bind(undefined, 1), + /* 0x4B */ MPPEM, + /* 0x4C */ undefined, // TODO MPS + /* 0x4D */ FLIPON, + /* 0x4E */ undefined, // TODO FLIPOFF + /* 0x4F */ undefined, // TODO DEBUG + /* 0x50 */ LT, + /* 0x51 */ LTEQ, + /* 0x52 */ GT, + /* 0x53 */ GTEQ, + /* 0x54 */ EQ, + /* 0x55 */ NEQ, + /* 0x56 */ ODD, + /* 0x57 */ EVEN, + /* 0x58 */ IF, + /* 0x59 */ EIF, + /* 0x5A */ AND, + /* 0x5B */ OR, + /* 0x5C */ NOT, + /* 0x5D */ DELTAP123.bind(undefined, 1), + /* 0x5E */ SDB, + /* 0x5F */ SDS, + /* 0x60 */ ADD, + /* 0x61 */ SUB, + /* 0x62 */ DIV, + /* 0x63 */ MUL, + /* 0x64 */ ABS, + /* 0x65 */ NEG, + /* 0x66 */ FLOOR, + /* 0x67 */ CEILING, + /* 0x68 */ ROUND.bind(undefined, 0), + /* 0x69 */ ROUND.bind(undefined, 1), + /* 0x6A */ ROUND.bind(undefined, 2), + /* 0x6B */ ROUND.bind(undefined, 3), + /* 0x6C */ undefined, // TODO NROUND[ab] + /* 0x6D */ undefined, // TODO NROUND[ab] + /* 0x6E */ undefined, // TODO NROUND[ab] + /* 0x6F */ undefined, // TODO NROUND[ab] + /* 0x70 */ WCVTF, + /* 0x71 */ DELTAP123.bind(undefined, 2), + /* 0x72 */ DELTAP123.bind(undefined, 3), + /* 0x73 */ DELTAC123.bind(undefined, 1), + /* 0x74 */ DELTAC123.bind(undefined, 2), + /* 0x75 */ DELTAC123.bind(undefined, 3), + /* 0x76 */ SROUND, + /* 0x77 */ S45ROUND, + /* 0x78 */ undefined, // TODO JROT[] + /* 0x79 */ undefined, // TODO JROF[] + /* 0x7A */ ROFF, + /* 0x7B */ undefined, + /* 0x7C */ RUTG, + /* 0x7D */ RDTG, + /* 0x7E */ POP, // actually SANGW, supposed to do only a pop though + /* 0x7F */ POP, // actually AA, supposed to do only a pop though + /* 0x80 */ undefined, // TODO FLIPPT + /* 0x81 */ undefined, // TODO FLIPRGON + /* 0x82 */ undefined, // TODO FLIPRGOFF + /* 0x83 */ undefined, + /* 0x84 */ undefined, + /* 0x85 */ SCANCTRL, + /* 0x86 */ SDPVTL.bind(undefined, 0), + /* 0x87 */ SDPVTL.bind(undefined, 1), + /* 0x88 */ GETINFO, + /* 0x89 */ undefined, // TODO IDEF + /* 0x8A */ ROLL, + /* 0x8B */ MAX, + /* 0x8C */ MIN, + /* 0x8D */ SCANTYPE, + /* 0x8E */ INSTCTRL, + /* 0x8F */ undefined, + /* 0x90 */ undefined, + /* 0x91 */ undefined, + /* 0x92 */ undefined, + /* 0x93 */ undefined, + /* 0x94 */ undefined, + /* 0x95 */ undefined, + /* 0x96 */ undefined, + /* 0x97 */ undefined, + /* 0x98 */ undefined, + /* 0x99 */ undefined, + /* 0x9A */ undefined, + /* 0x9B */ undefined, + /* 0x9C */ undefined, + /* 0x9D */ undefined, + /* 0x9E */ undefined, + /* 0x9F */ undefined, + /* 0xA0 */ undefined, + /* 0xA1 */ undefined, + /* 0xA2 */ undefined, + /* 0xA3 */ undefined, + /* 0xA4 */ undefined, + /* 0xA5 */ undefined, + /* 0xA6 */ undefined, + /* 0xA7 */ undefined, + /* 0xA8 */ undefined, + /* 0xA9 */ undefined, + /* 0xAA */ undefined, + /* 0xAB */ undefined, + /* 0xAC */ undefined, + /* 0xAD */ undefined, + /* 0xAE */ undefined, + /* 0xAF */ undefined, + /* 0xB0 */ PUSHB.bind(undefined, 1), + /* 0xB1 */ PUSHB.bind(undefined, 2), + /* 0xB2 */ PUSHB.bind(undefined, 3), + /* 0xB3 */ PUSHB.bind(undefined, 4), + /* 0xB4 */ PUSHB.bind(undefined, 5), + /* 0xB5 */ PUSHB.bind(undefined, 6), + /* 0xB6 */ PUSHB.bind(undefined, 7), + /* 0xB7 */ PUSHB.bind(undefined, 8), + /* 0xB8 */ PUSHW.bind(undefined, 1), + /* 0xB9 */ PUSHW.bind(undefined, 2), + /* 0xBA */ PUSHW.bind(undefined, 3), + /* 0xBB */ PUSHW.bind(undefined, 4), + /* 0xBC */ PUSHW.bind(undefined, 5), + /* 0xBD */ PUSHW.bind(undefined, 6), + /* 0xBE */ PUSHW.bind(undefined, 7), + /* 0xBF */ PUSHW.bind(undefined, 8), + /* 0xC0 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 0), + /* 0xC1 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 1), + /* 0xC2 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 2), + /* 0xC3 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 0, 3), + /* 0xC4 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 0), + /* 0xC5 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 1), + /* 0xC6 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 2), + /* 0xC7 */ MDRP_MIRP.bind(undefined, 0, 0, 0, 1, 3), + /* 0xC8 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 0), + /* 0xC9 */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 1), + /* 0xCA */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 2), + /* 0xCB */ MDRP_MIRP.bind(undefined, 0, 0, 1, 0, 3), + /* 0xCC */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 0), + /* 0xCD */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 1), + /* 0xCE */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 2), + /* 0xCF */ MDRP_MIRP.bind(undefined, 0, 0, 1, 1, 3), + /* 0xD0 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 0), + /* 0xD1 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 1), + /* 0xD2 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 2), + /* 0xD3 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 0, 3), + /* 0xD4 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 0), + /* 0xD5 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 1), + /* 0xD6 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 2), + /* 0xD7 */ MDRP_MIRP.bind(undefined, 0, 1, 0, 1, 3), + /* 0xD8 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 0), + /* 0xD9 */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 1), + /* 0xDA */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 2), + /* 0xDB */ MDRP_MIRP.bind(undefined, 0, 1, 1, 0, 3), + /* 0xDC */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 0), + /* 0xDD */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 1), + /* 0xDE */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 2), + /* 0xDF */ MDRP_MIRP.bind(undefined, 0, 1, 1, 1, 3), + /* 0xE0 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 0), + /* 0xE1 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 1), + /* 0xE2 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 2), + /* 0xE3 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 0, 3), + /* 0xE4 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 0), + /* 0xE5 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 1), + /* 0xE6 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 2), + /* 0xE7 */ MDRP_MIRP.bind(undefined, 1, 0, 0, 1, 3), + /* 0xE8 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 0), + /* 0xE9 */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 1), + /* 0xEA */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 2), + /* 0xEB */ MDRP_MIRP.bind(undefined, 1, 0, 1, 0, 3), + /* 0xEC */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 0), + /* 0xED */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 1), + /* 0xEE */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 2), + /* 0xEF */ MDRP_MIRP.bind(undefined, 1, 0, 1, 1, 3), + /* 0xF0 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 0), + /* 0xF1 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 1), + /* 0xF2 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 2), + /* 0xF3 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 0, 3), + /* 0xF4 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 0), + /* 0xF5 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 1), + /* 0xF6 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 2), + /* 0xF7 */ MDRP_MIRP.bind(undefined, 1, 1, 0, 1, 3), + /* 0xF8 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 0), + /* 0xF9 */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 1), + /* 0xFA */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 2), + /* 0xFB */ MDRP_MIRP.bind(undefined, 1, 1, 1, 0, 3), + /* 0xFC */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 0), + /* 0xFD */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 1), + /* 0xFE */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 2), + /* 0xFF */ MDRP_MIRP.bind(undefined, 1, 1, 1, 1, 3) +]; + +/***************************** + Mathematical Considerations +****************************** + +fv ... refers to freedom vector +pv ... refers to projection vector +rp ... refers to reference point +p ... refers to to point being operated on +d ... refers to distance + +SETRELATIVE: +============ + +case freedom vector == x-axis: +------------------------------ + + (pv) + .-' + rpd .-' + .-* + d .-'90°' + .-' ' + .-' ' + *-' ' b + rp ' + ' + ' + p *----------*-------------- (fv) + pm + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b + + y - rpdy = pvns * (x- rpdx) + + y = p.y + + x = rpdx + ( p.y - rpdy ) / pvns + + +case freedom vector == y-axis: +------------------------------ + + * pm + |\ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ + | \ b + | \ + | \ + | \ .-' (pv) + | 90° \.-' + | .-'* rpd + | .-' + * *-' d + p rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns ... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + x = p.x + + y = rpdy + pvns * (p.x - rpdx) + + + +generic case: +------------- + + + .'(fv) + .' + .* pm + .' ! + .' . + .' ! + .' . b + .' ! + * . + p ! + 90° . ... (pv) + ...-*-''' + ...---''' rpd + ...---''' d + *--''' + rp + + rpdx = rpx + d * pv.x + rpdy = rpy + d * pv.y + + equation of line b: + pvns... normal slope to pv + + y - rpdy = pvns * (x - rpdx) + + equation of freedom vector line: + fvs ... slope of freedom vector (=fy/fx) + + y - py = fvs * (x - px) + + + on pm both equations are true for same x/y + + y - rpdy = pvns * (x - rpdx) + + y - py = fvs * (x - px) + + form to y and set equal: + + pvns * (x - rpdx) + rpdy = fvs * (x - px) + py + + expand: + + pvns * x - pvns * rpdx + rpdy = fvs * x - fvs * px + py + + switch: + + fvs * x - fvs * px + py = pvns * x - pvns * rpdx + rpdy + + solve for x: + + fvs * x - pvns * x = fvs * px - pvns * rpdx - py + rpdy + + + + fvs * px - pvns * rpdx + rpdy - py + x = ----------------------------------- + fvs - pvns + + and: + + y = fvs * (x - px) + py + + + +INTERPOLATE: +============ + +Examples of point interpolation. + +The weight of the movement of the reference point gets bigger +the further the other reference point is away, thus the safest +option (that is avoiding 0/0 divisions) is to weight the +original distance of the other point by the sum of both distances. + +If the sum of both distances is 0, then move the point by the +arithmetic average of the movement of both reference points. + + + + + (+6) + rp1o *---->*rp1 + . . (+12) + . . rp2o *---------->* rp2 + . . . . + . . . . + . 10 20 . . + |.........|...................| . + . . . + . . (+8) . + po *------>*p . + . . . + . 12 . 24 . + |...........|.......................| + 36 + + +------- + + + + (+10) + rp1o *-------->*rp1 + . . (-10) + . . rp2 *<---------* rpo2 + . . . . + . . . . + . 10 . 30 . . + |.........|.............................| + . . + . (+5) . + po *--->* p . + . . . + . . 20 . + |....|..............| + 5 15 + + +------- + + + (+10) + rp1o *-------->*rp1 + . . + . . + rp2o *-------->*rp2 + + + (+10) + po *-------->* p + +------- + + + (+10) + rp1o *-------->*rp1 + . . + . .(+30) + rp2o *---------------------------->*rp2 + + + (+25) + po *----------------------->* p + + + +vim: set ts=4 sw=4 expandtab: +*****/ + +/** + * Converts a string into a list of tokens. + */ + +/** + * Create a new token + * @param {string} char a single char + */ +function Token(char) { + this.char = char; + this.state = {}; + this.activeState = null; +} + +/** + * Create a new context range + * @param {number} startIndex range start index + * @param {number} endOffset range end index offset + * @param {string} contextName owner context name + */ +function ContextRange(startIndex, endOffset, contextName) { + this.contextName = contextName; + this.startIndex = startIndex; + this.endOffset = endOffset; +} + +/** + * Check context start and end + * @param {string} contextName a unique context name + * @param {function} checkStart a predicate function the indicates a context's start + * @param {function} checkEnd a predicate function the indicates a context's end + */ +function ContextChecker(contextName, checkStart, checkEnd) { + this.contextName = contextName; + this.openRange = null; + this.ranges = []; + this.checkStart = checkStart; + this.checkEnd = checkEnd; +} + +/** + * @typedef ContextParams + * @type Object + * @property {array} context context items + * @property {number} currentIndex current item index + */ + +/** + * Create a context params + * @param {array} context a list of items + * @param {number} currentIndex current item index + */ +function ContextParams(context, currentIndex) { + this.context = context; + this.index = currentIndex; + this.length = context.length; + this.current = context[currentIndex]; + this.backtrack = context.slice(0, currentIndex); + this.lookahead = context.slice(currentIndex + 1); +} + +/** + * Create an event instance + * @param {string} eventId event unique id + */ +function Event(eventId) { + this.eventId = eventId; + this.subscribers = []; +} + +/** + * Initialize a core events and auto subscribe required event handlers + * @param {any} events an object that enlists core events handlers + */ +function initializeCoreEvents(events) { + var this$1 = this; + + var coreEvents = [ + 'start', 'end', 'next', 'newToken', 'contextStart', + 'contextEnd', 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD', 'updateContextsRanges' + ]; + + coreEvents.forEach(function (eventId) { + Object.defineProperty(this$1.events, eventId, { + value: new Event(eventId) + }); + }); + + if (!!events) { + coreEvents.forEach(function (eventId) { + var event = events[eventId]; + if (typeof event === 'function') { + this$1.events[eventId].subscribe(event); + } + }); + } + var requiresContextUpdate = [ + 'insertToken', 'removeToken', 'removeRange', + 'replaceToken', 'replaceRange', 'composeRUD' + ]; + requiresContextUpdate.forEach(function (eventId) { + this$1.events[eventId].subscribe( + this$1.updateContextsRanges + ); + }); +} + +/** + * Converts a string into a list of tokens + * @param {any} events tokenizer core events + */ +function Tokenizer(events) { + this.tokens = []; + this.registeredContexts = {}; + this.contextCheckers = []; + this.events = {}; + this.registeredModifiers = []; + + initializeCoreEvents.call(this, events); +} + +/** + * Sets the state of a token, usually called by a state modifier. + * @param {string} key state item key + * @param {any} value state item value + */ +Token.prototype.setState = function(key, value) { + this.state[key] = value; + this.activeState = { key: key, value: this.state[key] }; + return this.activeState; +}; + +Token.prototype.getState = function (stateId) { + return this.state[stateId] || null; +}; + +/** + * Checks if an index exists in the tokens list. + * @param {number} index token index + */ +Tokenizer.prototype.inboundIndex = function(index) { + return index >= 0 && index < this.tokens.length; +}; + +/** + * Compose and apply a list of operations (replace, update, delete) + * @param {array} RUDs replace, update and delete operations + * TODO: Perf. Optimization (lengthBefore === lengthAfter ? dispatch once) + */ +Tokenizer.prototype.composeRUD = function (RUDs) { + var this$1 = this; + + var silent = true; + var state = RUDs.map(function (RUD) { return ( + this$1[RUD[0]].apply(this$1, RUD.slice(1).concat(silent)) + ); }); + var hasFAILObject = function (obj) { return ( + typeof obj === 'object' && + obj.hasOwnProperty('FAIL') + ); }; + if (state.every(hasFAILObject)) { + return { + FAIL: "composeRUD: one or more operations hasn't completed successfully", + report: state.filter(hasFAILObject) + }; + } + this.dispatch('composeRUD', [state.filter(function (op) { return !hasFAILObject(op); })]); +}; + +/** + * Replace a range of tokens with a list of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {token} tokens a list of tokens to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceRange = function (startIndex, offset, tokens, silent) { + offset = offset !== null ? offset : this.tokens.length; + var isTokenType = tokens.every(function (token) { return token instanceof Token; }); + if (!isNaN(startIndex) && this.inboundIndex(startIndex) && isTokenType) { + var replaced = this.tokens.splice.apply( + this.tokens, [startIndex, offset].concat(tokens) + ); + if (!silent) { this.dispatch('replaceToken', [startIndex, offset, tokens]); } + return [replaced, tokens]; + } else { + return { FAIL: 'replaceRange: invalid tokens or startIndex.' }; + } +}; + +/** + * Replace a token with another token + * @param {number} index token index + * @param {token} token a token to replace + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.replaceToken = function (index, token, silent) { + if (!isNaN(index) && this.inboundIndex(index) && token instanceof Token) { + var replaced = this.tokens.splice(index, 1, token); + if (!silent) { this.dispatch('replaceToken', [index, token]); } + return [replaced[0], token]; + } else { + return { FAIL: 'replaceToken: invalid token or index.' }; + } +}; + +/** + * Removes a range of tokens + * @param {number} startIndex range start index + * @param {number} offset range offset + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeRange = function(startIndex, offset, silent) { + offset = !isNaN(offset) ? offset : this.tokens.length; + var tokens = this.tokens.splice(startIndex, offset); + if (!silent) { this.dispatch('removeRange', [tokens, startIndex, offset]); } + return tokens; +}; + +/** + * Remove a token at a certain index + * @param {number} index token index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.removeToken = function(index, silent) { + if (!isNaN(index) && this.inboundIndex(index)) { + var token = this.tokens.splice(index, 1); + if (!silent) { this.dispatch('removeToken', [token, index]); } + return token; + } else { + return { FAIL: 'removeToken: invalid token index.' }; + } +}; + +/** + * Insert a list of tokens at a certain index + * @param {array} tokens a list of tokens to insert + * @param {number} index insert the list of tokens at index + * @param {boolean} silent dispatch events and update context ranges + */ +Tokenizer.prototype.insertToken = function (tokens, index, silent) { + var tokenType = tokens.every( + function (token) { return token instanceof Token; } + ); + if (tokenType) { + this.tokens.splice.apply( + this.tokens, [index, 0].concat(tokens) + ); + if (!silent) { this.dispatch('insertToken', [tokens, index]); } + return tokens; + } else { + return { FAIL: 'insertToken: invalid token(s).' }; + } +}; + +/** + * A state modifier that is called on 'newToken' event + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a function to update token state + */ +Tokenizer.prototype.registerModifier = function(modifierId, condition, modifier) { + this.events.newToken.subscribe(function(token, contextParams) { + var conditionParams = [token, contextParams]; + var canApplyModifier = ( + condition === null || + condition.apply(this, conditionParams) === true + ); + var modifierParams = [token, contextParams]; + if (canApplyModifier) { + var newStateValue = modifier.apply(this, modifierParams); + token.setState(modifierId, newStateValue); + } + }); + this.registeredModifiers.push(modifierId); +}; + +/** + * Subscribe a handler to an event + * @param {function} eventHandler an event handler function + */ +Event.prototype.subscribe = function (eventHandler) { + if (typeof eventHandler === 'function') { + return ((this.subscribers.push(eventHandler)) - 1); + } else { + return { FAIL: ("invalid '" + (this.eventId) + "' event handler")}; + } +}; + +/** + * Unsubscribe an event handler + * @param {string} subsId subscription id + */ +Event.prototype.unsubscribe = function (subsId) { + this.subscribers.splice(subsId, 1); +}; + +/** + * Sets context params current value index + * @param {number} index context params current value index + */ +ContextParams.prototype.setCurrentIndex = function(index) { + this.index = index; + this.current = this.context[index]; + this.backtrack = this.context.slice(0, index); + this.lookahead = this.context.slice(index + 1); +}; + +/** + * Get an item at an offset from the current value + * example (current value is 3): + * 1 2 [3] 4 5 | items values + * -2 -1 0 1 2 | offset values + * @param {number} offset an offset from current value index + */ +ContextParams.prototype.get = function (offset) { + switch (true) { + case (offset === 0): + return this.current; + case (offset < 0 && Math.abs(offset) <= this.backtrack.length): + return this.backtrack.slice(offset)[0]; + case (offset > 0 && offset <= this.lookahead.length): + return this.lookahead[offset - 1]; + default: + return null; + } +}; + +/** + * Converts a context range into a string value + * @param {contextRange} range a context range + */ +Tokenizer.prototype.rangeToText = function (range) { + if (range instanceof ContextRange) { + return ( + this.getRangeTokens(range) + .map(function (token) { return token.char; }).join('') + ); + } +}; + +/** + * Converts all tokens into a string + */ +Tokenizer.prototype.getText = function () { + return this.tokens.map(function (token) { return token.char; }).join(''); +}; + +/** + * Get a context by name + * @param {string} contextName context name to get + */ +Tokenizer.prototype.getContext = function (contextName) { + var context = this.registeredContexts[contextName]; + return !!context ? context : null; +}; + +/** + * Subscribes a new event handler to an event + * @param {string} eventName event name to subscribe to + * @param {function} eventHandler a function to be invoked on event + */ +Tokenizer.prototype.on = function(eventName, eventHandler) { + var event = this.events[eventName]; + if (!!event) { + return event.subscribe(eventHandler); + } else { + return null; + } +}; + +/** + * Dispatches an event + * @param {string} eventName event name + * @param {any} args event handler arguments + */ +Tokenizer.prototype.dispatch = function(eventName, args) { + var this$1 = this; + + var event = this.events[eventName]; + if (event instanceof Event) { + event.subscribers.forEach(function (subscriber) { + subscriber.apply(this$1, args || []); + }); + } +}; + +/** + * Register a new context checker + * @param {string} contextName a unique context name + * @param {function} contextStartCheck a predicate function that returns true on context start + * @param {function} contextEndCheck a predicate function that returns true on context end + * TODO: call tokenize on registration to update context ranges with the new context. + */ +Tokenizer.prototype.registerContextChecker = function(contextName, contextStartCheck, contextEndCheck) { + if (!!this.getContext(contextName)) { return { + FAIL: + ("context name '" + contextName + "' is already registered.") + }; } + if (typeof contextStartCheck !== 'function') { return { + FAIL: + "missing context start check." + }; } + if (typeof contextEndCheck !== 'function') { return { + FAIL: + "missing context end check." + }; } + var contextCheckers = new ContextChecker( + contextName, contextStartCheck, contextEndCheck + ); + this.registeredContexts[contextName] = contextCheckers; + this.contextCheckers.push(contextCheckers); + return contextCheckers; +}; + +/** + * Gets a context range tokens + * @param {contextRange} range a context range + */ +Tokenizer.prototype.getRangeTokens = function(range) { + var endIndex = range.startIndex + range.endOffset; + return [].concat( + this.tokens + .slice(range.startIndex, endIndex) + ); +}; + +/** + * Gets the ranges of a context + * @param {string} contextName context name + */ +Tokenizer.prototype.getContextRanges = function(contextName) { + var context = this.getContext(contextName); + if (!!context) { + return context.ranges; + } else { + return { FAIL: ("context checker '" + contextName + "' is not registered.") }; + } +}; + +/** + * Resets context ranges to run context update + */ +Tokenizer.prototype.resetContextsRanges = function () { + var registeredContexts = this.registeredContexts; + for (var contextName in registeredContexts) { + if (registeredContexts.hasOwnProperty(contextName)) { + var context = registeredContexts[contextName]; + context.ranges = []; + } + } +}; + +/** + * Updates context ranges + */ +Tokenizer.prototype.updateContextsRanges = function () { + this.resetContextsRanges(); + var chars = this.tokens.map(function (token) { return token.char; }); + for (var i = 0; i < chars.length; i++) { + var contextParams = new ContextParams(chars, i); + this.runContextCheck(contextParams); + } + this.dispatch('updateContextsRanges', [this.registeredContexts]); +}; + +/** + * Sets the end offset of an open range + * @param {number} offset range end offset + * @param {string} contextName context name + */ +Tokenizer.prototype.setEndOffset = function (offset, contextName) { + var startIndex = this.getContext(contextName).openRange.startIndex; + var range = new ContextRange(startIndex, offset, contextName); + var ranges = this.getContext(contextName).ranges; + range.rangeId = contextName + "." + (ranges.length); + ranges.push(range); + this.getContext(contextName).openRange = null; + return range; +}; + +/** + * Runs a context check on the current context + * @param {contextParams} contextParams current context params + */ +Tokenizer.prototype.runContextCheck = function(contextParams) { + var this$1 = this; + + var index = contextParams.index; + this.contextCheckers.forEach(function (contextChecker) { + var contextName = contextChecker.contextName; + var openRange = this$1.getContext(contextName).openRange; + if (!openRange && contextChecker.checkStart(contextParams)) { + openRange = new ContextRange(index, null, contextName); + this$1.getContext(contextName).openRange = openRange; + this$1.dispatch('contextStart', [contextName, index]); + } + if (!!openRange && contextChecker.checkEnd(contextParams)) { + var offset = (index - openRange.startIndex) + 1; + var range = this$1.setEndOffset(offset, contextName); + this$1.dispatch('contextEnd', [contextName, range]); + } + }); +}; + +/** + * Converts a text into a list of tokens + * @param {string} text a text to tokenize + */ +Tokenizer.prototype.tokenize = function (text) { + this.tokens = []; + this.resetContextsRanges(); + var chars = Array.from(text); + this.dispatch('start'); + for (var i = 0; i < chars.length; i++) { + var char = chars[i]; + var contextParams = new ContextParams(chars, i); + this.dispatch('next', [contextParams]); + this.runContextCheck(contextParams); + var token = new Token(char); + this.tokens.push(token); + this.dispatch('newToken', [token, contextParams]); + } + this.dispatch('end', [this.tokens]); + return this.tokens; +}; + +// ╭─┄┄┄────────────────────────┄─────────────────────────────────────────────╮ +// ┊ Character Class Assertions ┊ Checks if a char belongs to a certain class ┊ +// ╰─╾──────────────────────────┄─────────────────────────────────────────────╯ +// jscs:disable maximumLineLength +/** + * Check if a char is Arabic + * @param {string} c a single char + */ +function isArabicChar(c) { + return /[\u0600-\u065F\u066A-\u06D2\u06FA-\u06FF]/.test(c); +} + +/** + * Check if a char is an isolated arabic char + * @param {string} c a single char + */ +function isIsolatedArabicChar(char) { + return /[\u0630\u0690\u0621\u0631\u0661\u0671\u0622\u0632\u0672\u0692\u06C2\u0623\u0673\u0693\u06C3\u0624\u0694\u06C4\u0625\u0675\u0695\u06C5\u06E5\u0676\u0696\u06C6\u0627\u0677\u0697\u06C7\u0648\u0688\u0698\u06C8\u0689\u0699\u06C9\u068A\u06CA\u066B\u068B\u06CB\u068C\u068D\u06CD\u06FD\u068E\u06EE\u06FE\u062F\u068F\u06CF\u06EF]/.test(char); +} + +/** + * Check if a char is an Arabic Tashkeel char + * @param {string} c a single char + */ +function isTashkeelArabicChar(char) { + return /[\u0600-\u0605\u060C-\u060E\u0610-\u061B\u061E\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED]/.test(char); +} + +/** + * Check if a char is Latin + * @param {string} c a single char + */ +function isLatinChar(c) { + return /[A-z]/.test(c); +} + +/** + * Check if a char is whitespace char + * @param {string} c a single char + */ +function isWhiteSpace(c) { + return /\s/.test(c); +} + +/** + * Query a feature by some of it's properties to lookup a glyph substitution. + */ + +/** + * Create feature query instance + * @param {Font} font opentype font instance + */ +function FeatureQuery(font) { + this.font = font; + this.features = {}; +} + +/** + * @typedef SubstitutionAction + * @type Object + * @property {number} id substitution type + * @property {string} tag feature tag + * @property {any} substitution substitution value(s) + */ + +/** + * Create a substitution action instance + * @param {SubstitutionAction} action + */ +function SubstitutionAction(action) { + this.id = action.id; + this.tag = action.tag; + this.substitution = action.substitution; +} + +/** + * Lookup a coverage table + * @param {number} glyphIndex glyph index + * @param {CoverageTable} coverage coverage table + */ +function lookupCoverage(glyphIndex, coverage) { + if (!glyphIndex) { return -1; } + switch (coverage.format) { + case 1: + return coverage.glyphs.indexOf(glyphIndex); + + case 2: + var ranges = coverage.ranges; + for (var i = 0; i < ranges.length; i++) { + var range = ranges[i]; + if (glyphIndex >= range.start && glyphIndex <= range.end) { + var offset = glyphIndex - range.start; + return range.index + offset; + } + } + break; + default: + return -1; // not found + } + return -1; +} + +/** + * Handle a single substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return glyphIndex + subtable.deltaGlyphId; +} + +/** + * Handle a single substitution - format 2 + * @param {ContextParams} contextParams context params to lookup + */ +function singleSubstitutionFormat2(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.substitute[substituteIndex]; +} + +/** + * Lookup a list of coverage tables + * @param {any} coverageList a list of coverage tables + * @param {ContextParams} contextParams context params to lookup + */ +function lookupCoverageList(coverageList, contextParams) { + var lookupList = []; + for (var i = 0; i < coverageList.length; i++) { + var coverage = coverageList[i]; + var glyphIndex = contextParams.current; + glyphIndex = Array.isArray(glyphIndex) ? glyphIndex[0] : glyphIndex; + var lookupIndex = lookupCoverage(glyphIndex, coverage); + if (lookupIndex !== -1) { + lookupList.push(lookupIndex); + } + } + if (lookupList.length !== coverageList.length) { return -1; } + return lookupList; +} + +/** + * Handle chaining context substitution - format 3 + * @param {ContextParams} contextParams context params to lookup + */ +function chainingSubstitutionFormat3(contextParams, subtable) { + var lookupsCount = ( + subtable.inputCoverage.length + + subtable.lookaheadCoverage.length + + subtable.backtrackCoverage.length + ); + if (contextParams.context.length < lookupsCount) { return []; } + // INPUT LOOKUP // + var inputLookups = lookupCoverageList( + subtable.inputCoverage, contextParams + ); + if (inputLookups === -1) { return []; } + // LOOKAHEAD LOOKUP // + var lookaheadOffset = subtable.inputCoverage.length - 1; + if (contextParams.lookahead.length < subtable.lookaheadCoverage.length) { return []; } + var lookaheadContext = contextParams.lookahead.slice(lookaheadOffset); + while (lookaheadContext.length && isTashkeelArabicChar(lookaheadContext[0].char)) { + lookaheadContext.shift(); + } + var lookaheadParams = new ContextParams(lookaheadContext, 0); + var lookaheadLookups = lookupCoverageList( + subtable.lookaheadCoverage, lookaheadParams + ); + // BACKTRACK LOOKUP // + var backtrackContext = [].concat(contextParams.backtrack); + backtrackContext.reverse(); + while (backtrackContext.length && isTashkeelArabicChar(backtrackContext[0].char)) { + backtrackContext.shift(); + } + if (backtrackContext.length < subtable.backtrackCoverage.length) { return []; } + var backtrackParams = new ContextParams(backtrackContext, 0); + var backtrackLookups = lookupCoverageList( + subtable.backtrackCoverage, backtrackParams + ); + var contextRulesMatch = ( + inputLookups.length === subtable.inputCoverage.length && + lookaheadLookups.length === subtable.lookaheadCoverage.length && + backtrackLookups.length === subtable.backtrackCoverage.length + ); + var substitutions = []; + if (contextRulesMatch) { + for (var i = 0; i < subtable.lookupRecords.length; i++) { + var lookupRecord = subtable.lookupRecords[i]; + var lookupListIndex = lookupRecord.lookupListIndex; + var lookupTable = this.getLookupByIndex(lookupListIndex); + for (var s = 0; s < lookupTable.subtables.length; s++) { + var subtable$1 = lookupTable.subtables[s]; + var lookup = this.getLookupMethod(lookupTable, subtable$1); + var substitutionType = this.getSubstitutionType(lookupTable, subtable$1); + if (substitutionType === '12') { + for (var n = 0; n < inputLookups.length; n++) { + var glyphIndex = contextParams.get(n); + var substitution = lookup(glyphIndex); + if (substitution) { substitutions.push(substitution); } + } + } + } + } + } + return substitutions; +} + +/** + * Handle ligature substitution - format 1 + * @param {ContextParams} contextParams context params to lookup + */ +function ligatureSubstitutionFormat1(contextParams, subtable) { + // COVERAGE LOOKUP // + var glyphIndex = contextParams.current; + var ligSetIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (ligSetIndex === -1) { return null; } + // COMPONENTS LOOKUP + // (!) note, components are ordered in the written direction. + var ligature; + var ligatureSet = subtable.ligatureSets[ligSetIndex]; + for (var s = 0; s < ligatureSet.length; s++) { + ligature = ligatureSet[s]; + for (var l = 0; l < ligature.components.length; l++) { + var lookaheadItem = contextParams.lookahead[l]; + var component = ligature.components[l]; + if (lookaheadItem !== component) { break; } + if (l === ligature.components.length - 1) { return ligature; } + } + } + return null; +} + +/** + * Handle decomposition substitution - format 1 + * @param {number} glyphIndex glyph index + * @param {any} subtable subtable + */ +function decompositionSubstitutionFormat1(glyphIndex, subtable) { + var substituteIndex = lookupCoverage(glyphIndex, subtable.coverage); + if (substituteIndex === -1) { return null; } + return subtable.sequences[substituteIndex]; +} + +/** + * Get default script features indexes + */ +FeatureQuery.prototype.getDefaultScriptFeaturesIndexes = function () { + var scripts = this.font.tables.gsub.scripts; + for (var s = 0; s < scripts.length; s++) { + var script = scripts[s]; + if (script.tag === 'DFLT') { return ( + script.script.defaultLangSys.featureIndexes + ); } + } + return []; +}; + +/** + * Get feature indexes of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeaturesIndexes = function(scriptTag) { + var tables = this.font.tables; + if (!tables.gsub) { return []; } + if (!scriptTag) { return this.getDefaultScriptFeaturesIndexes(); } + var scripts = this.font.tables.gsub.scripts; + for (var i = 0; i < scripts.length; i++) { + var script = scripts[i]; + if (script.tag === scriptTag && script.script.defaultLangSys) { + return script.script.defaultLangSys.featureIndexes; + } else { + var langSysRecords = script.langSysRecords; + if (!!langSysRecords) { + for (var j = 0; j < langSysRecords.length; j++) { + var langSysRecord = langSysRecords[j]; + if (langSysRecord.tag === scriptTag) { + var langSys = langSysRecord.langSys; + return langSys.featureIndexes; + } + } + } + } + } + return this.getDefaultScriptFeaturesIndexes(); +}; + +/** + * Map a feature tag to a gsub feature + * @param {any} features gsub features + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.mapTagsToFeatures = function (features, scriptTag) { + var tags = {}; + for (var i = 0; i < features.length; i++) { + var tag = features[i].tag; + var feature = features[i].feature; + tags[tag] = feature; + } + this.features[scriptTag].tags = tags; +}; + +/** + * Get features of a specific script + * @param {string} scriptTag script tag + */ +FeatureQuery.prototype.getScriptFeatures = function (scriptTag) { + var features = this.features[scriptTag]; + if (this.features.hasOwnProperty(scriptTag)) { return features; } + var featuresIndexes = this.getScriptFeaturesIndexes(scriptTag); + if (!featuresIndexes) { return null; } + var gsub = this.font.tables.gsub; + features = featuresIndexes.map(function (index) { return gsub.features[index]; }); + this.features[scriptTag] = features; + this.mapTagsToFeatures(features, scriptTag); + return features; +}; + +/** + * Get substitution type + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getSubstitutionType = function(lookupTable, subtable) { + var lookupType = lookupTable.lookupType.toString(); + var substFormat = subtable.substFormat.toString(); + return lookupType + substFormat; +}; + +/** + * Get lookup method + * @param {any} lookupTable lookup table + * @param {any} subtable subtable + */ +FeatureQuery.prototype.getLookupMethod = function(lookupTable, subtable) { + var this$1 = this; + + var substitutionType = this.getSubstitutionType(lookupTable, subtable); + switch (substitutionType) { + case '11': + return function (glyphIndex) { return singleSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + case '12': + return function (glyphIndex) { return singleSubstitutionFormat2.apply( + this$1, [glyphIndex, subtable] + ); }; + case '63': + return function (contextParams) { return chainingSubstitutionFormat3.apply( + this$1, [contextParams, subtable] + ); }; + case '41': + return function (contextParams) { return ligatureSubstitutionFormat1.apply( + this$1, [contextParams, subtable] + ); }; + case '21': + return function (glyphIndex) { return decompositionSubstitutionFormat1.apply( + this$1, [glyphIndex, subtable] + ); }; + default: + throw new Error( + "lookupType: " + (lookupTable.lookupType) + " - " + + "substFormat: " + (subtable.substFormat) + " " + + "is not yet supported" + ); + } +}; + +/** + * [ LOOKUP TYPES ] + * ------------------------------- + * Single 1; + * Multiple 2; + * Alternate 3; + * Ligature 4; + * Context 5; + * ChainingContext 6; + * ExtensionSubstitution 7; + * ReverseChainingContext 8; + * ------------------------------- + * + */ + +/** + * @typedef FQuery + * @type Object + * @param {string} tag feature tag + * @param {string} script feature script + * @param {ContextParams} contextParams context params + */ + +/** + * Lookup a feature using a query parameters + * @param {FQuery} query feature query + */ +FeatureQuery.prototype.lookupFeature = function (query) { + var contextParams = query.contextParams; + var currentIndex = contextParams.index; + var feature = this.getFeature({ + tag: query.tag, script: query.script + }); + if (!feature) { return new Error( + "font '" + (this.font.names.fullName.en) + "' " + + "doesn't support feature '" + (query.tag) + "' " + + "for script '" + (query.script) + "'." + ); } + var lookups = this.getFeatureLookups(feature); + var substitutions = [].concat(contextParams.context); + for (var l = 0; l < lookups.length; l++) { + var lookupTable = lookups[l]; + var subtables = this.getLookupSubtables(lookupTable); + for (var s = 0; s < subtables.length; s++) { + var subtable = subtables[s]; + var substType = this.getSubstitutionType(lookupTable, subtable); + var lookup = this.getLookupMethod(lookupTable, subtable); + var substitution = (void 0); + switch (substType) { + case '11': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 11, tag: query.tag, substitution: substitution + })); + } + break; + case '12': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 12, tag: query.tag, substitution: substitution + })); + } + break; + case '63': + substitution = lookup(contextParams); + if (Array.isArray(substitution) && substitution.length) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 63, tag: query.tag, substitution: substitution + })); + } + break; + case '41': + substitution = lookup(contextParams); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 41, tag: query.tag, substitution: substitution + })); + } + break; + case '21': + substitution = lookup(contextParams.current); + if (substitution) { + substitutions.splice(currentIndex, 1, new SubstitutionAction({ + id: 21, tag: query.tag, substitution: substitution + })); + } + break; + } + contextParams = new ContextParams(substitutions, currentIndex); + if (Array.isArray(substitution) && !substitution.length) { continue; } + substitution = null; + } + } + return substitutions.length ? substitutions : null; +}; + +/** + * Checks if a font supports a specific features + * @param {FQuery} query feature query object + */ +FeatureQuery.prototype.supports = function (query) { + if (!query.script) { return false; } + this.getScriptFeatures(query.script); + var supportedScript = this.features.hasOwnProperty(query.script); + if (!query.tag) { return supportedScript; } + var supportedFeature = ( + this.features[query.script].some(function (feature) { return feature.tag === query.tag; }) + ); + return supportedScript && supportedFeature; +}; + +/** + * Get lookup table subtables + * @param {any} lookupTable lookup table + */ +FeatureQuery.prototype.getLookupSubtables = function (lookupTable) { + return lookupTable.subtables || null; +}; + +/** + * Get lookup table by index + * @param {number} index lookup table index + */ +FeatureQuery.prototype.getLookupByIndex = function (index) { + var lookups = this.font.tables.gsub.lookups; + return lookups[index] || null; +}; + +/** + * Get lookup tables for a feature + * @param {string} feature + */ +FeatureQuery.prototype.getFeatureLookups = function (feature) { + // TODO: memoize + return feature.lookupListIndexes.map(this.getLookupByIndex.bind(this)); +}; + +/** + * Query a feature by it's properties + * @param {any} query an object that describes the properties of a query + */ +FeatureQuery.prototype.getFeature = function getFeature(query) { + if (!this.font) { return { FAIL: "No font was found"}; } + if (!this.features.hasOwnProperty(query.script)) { + this.getScriptFeatures(query.script); + } + var scriptFeatures = this.features[query.script]; + if (!scriptFeatures) { return ( + { FAIL: ("No feature for script " + (query.script))} + ); } + if (!scriptFeatures.tags[query.tag]) { return null; } + return this.features[query.script].tags[query.tag]; +}; + +/** + * Arabic word context checkers + */ + +function arabicWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? arabic first char + (prevChar === null && isArabicChar(char)) || + // ? arabic char preceded with a non arabic char + (!isArabicChar(prevChar) && isArabicChar(char)) + ); +} + +function arabicWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last arabic char + (nextChar === null) || + // ? next char is not arabic + (!isArabicChar(nextChar)) + ); +} + +var arabicWordCheck = { + startCheck: arabicWordStartCheck, + endCheck: arabicWordEndCheck +}; + +/** + * Arabic sentence context checkers + */ + +function arabicSentenceStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? an arabic char preceded with a non arabic char + (isArabicChar(char) || isTashkeelArabicChar(char)) && + !isArabicChar(prevChar) + ); +} + +function arabicSentenceEndCheck(contextParams) { + var nextChar = contextParams.get(1); + switch (true) { + case nextChar === null: + return true; + case (!isArabicChar(nextChar) && !isTashkeelArabicChar(nextChar)): + var nextIsWhitespace = isWhiteSpace(nextChar); + if (!nextIsWhitespace) { return true; } + if (nextIsWhitespace) { + var arabicCharAhead = false; + arabicCharAhead = ( + contextParams.lookahead.some( + function (c) { return isArabicChar(c) || isTashkeelArabicChar(c); } + ) + ); + if (!arabicCharAhead) { return true; } + } + break; + default: + return false; + } +} + +var arabicSentenceCheck = { + startCheck: arabicSentenceStartCheck, + endCheck: arabicSentenceEndCheck +}; + +/** + * Apply single substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat1$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} + +/** + * Apply single substitution format 2 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function singleSubstitutionFormat2$1(action, tokens, index) { + tokens[index].setState(action.tag, action.substitution); +} + +/** + * Apply chaining context substitution format 3 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function chainingSubstitutionFormat3$1(action, tokens, index) { + action.substitution.forEach(function (subst, offset) { + var token = tokens[index + offset]; + token.setState(action.tag, subst); + }); +} + +/** + * Apply ligature substitution format 1 + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function ligatureSubstitutionFormat1$1(action, tokens, index) { + var token = tokens[index]; + token.setState(action.tag, action.substitution.ligGlyph); + var compsCount = action.substitution.components.length; + for (var i = 0; i < compsCount; i++) { + token = tokens[index + i + 1]; + token.setState('deleted', true); + } +} + +/** + * Supported substitutions + */ +var SUBSTITUTIONS = { + 11: singleSubstitutionFormat1$1, + 12: singleSubstitutionFormat2$1, + 63: chainingSubstitutionFormat3$1, + 41: ligatureSubstitutionFormat1$1 +}; + +/** + * Apply substitutions to a list of tokens + * @param {Array} substitutions substitutions + * @param {any} tokens a list of tokens + * @param {number} index token index + */ +function applySubstitution(action, tokens, index) { + if (action instanceof SubstitutionAction && SUBSTITUTIONS[action.id]) { + SUBSTITUTIONS[action.id](action, tokens, index); + } +} + +/** + * Apply Arabic presentation forms to a range of tokens + */ + +/** + * Check if a char can be connected to it's preceding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectPrev(charContextParams) { + var backtrack = [].concat(charContextParams.backtrack); + for (var i = backtrack.length - 1; i >= 0; i--) { + var prevChar = backtrack[i]; + var isolated = isIsolatedArabicChar(prevChar); + var tashkeel = isTashkeelArabicChar(prevChar); + if (!isolated && !tashkeel) { return true; } + if (isolated) { return false; } + } + return false; +} + +/** + * Check if a char can be connected to it's proceeding char + * @param {ContextParams} charContextParams context params of a char + */ +function willConnectNext(charContextParams) { + if (isIsolatedArabicChar(charContextParams.current)) { return false; } + for (var i = 0; i < charContextParams.lookahead.length; i++) { + var nextChar = charContextParams.lookahead[i]; + var tashkeel = isTashkeelArabicChar(nextChar); + if (!tashkeel) { return true; } + } + return false; +} + +/** + * Apply arabic presentation forms to a list of tokens + * @param {ContextRange} range a range of tokens + */ +function arabicPresentationForms(range) { + var this$1 = this; + + var script = 'arab'; + var tags = this.featuresTags[script]; + var tokens = this.tokenizer.getRangeTokens(range); + if (tokens.length === 1) { return; } + var contextParams = new ContextParams( + tokens.map(function (token) { return token.getState('glyphIndex'); } + ), 0); + var charContextParams = new ContextParams( + tokens.map(function (token) { return token.char; } + ), 0); + tokens.forEach(function (token, index) { + if (isTashkeelArabicChar(token.char)) { return; } + contextParams.setCurrentIndex(index); + charContextParams.setCurrentIndex(index); + var CONNECT = 0; // 2 bits 00 (10: can connect next) (01: can connect prev) + if (willConnectPrev(charContextParams)) { CONNECT |= 1; } + if (willConnectNext(charContextParams)) { CONNECT |= 2; } + var tag; + switch (CONNECT) { + case 1: (tag = 'fina'); break; + case 2: (tag = 'init'); break; + case 3: (tag = 'medi'); break; + } + if (tags.indexOf(tag) === -1) { return; } + var substitutions = this$1.query.lookupFeature({ + tag: tag, script: script, contextParams: contextParams + }); + if (substitutions instanceof Error) { return console.info(substitutions.message); } + substitutions.forEach(function (action, index) { + if (action instanceof SubstitutionAction) { + applySubstitution(action, tokens, index); + contextParams.context[index] = action.substitution; + } + }); + }); +} + +/** + * Apply Arabic required ligatures feature to a range of tokens + */ + +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} + +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function arabicRequiredLigatures(range) { + var this$1 = this; + + var script = 'arab'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'rlig', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams(tokens); + } + }); +} + +/** + * Latin word context checkers + */ + +function latinWordStartCheck(contextParams) { + var char = contextParams.current; + var prevChar = contextParams.get(-1); + return ( + // ? latin first char + (prevChar === null && isLatinChar(char)) || + // ? latin char preceded with a non latin char + (!isLatinChar(prevChar) && isLatinChar(char)) + ); +} + +function latinWordEndCheck(contextParams) { + var nextChar = contextParams.get(1); + return ( + // ? last latin char + (nextChar === null) || + // ? next char is not latin + (!isLatinChar(nextChar)) + ); +} + +var latinWordCheck = { + startCheck: latinWordStartCheck, + endCheck: latinWordEndCheck +}; + +/** + * Apply Latin ligature feature to a range of tokens + */ + +/** + * Update context params + * @param {any} tokens a list of tokens + * @param {number} index current item index + */ +function getContextParams$1(tokens, index) { + var context = tokens.map(function (token) { return token.activeState.value; }); + return new ContextParams(context, index || 0); +} + +/** + * Apply Arabic required ligatures to a context range + * @param {ContextRange} range a range of tokens + */ +function latinLigature(range) { + var this$1 = this; + + var script = 'latn'; + var tokens = this.tokenizer.getRangeTokens(range); + var contextParams = getContextParams$1(tokens); + contextParams.context.forEach(function (glyphIndex, index) { + contextParams.setCurrentIndex(index); + var substitutions = this$1.query.lookupFeature({ + tag: 'liga', script: script, contextParams: contextParams + }); + if (substitutions.length) { + substitutions.forEach( + function (action) { return applySubstitution(action, tokens, index); } + ); + contextParams = getContextParams$1(tokens); + } + }); +} + +/** + * Infer bidirectional properties for a given text and apply + * the corresponding layout rules. + */ + +/** + * Create Bidi. features + * @param {string} baseDir text base direction. value either 'ltr' or 'rtl' + */ +function Bidi(baseDir) { + this.baseDir = baseDir || 'ltr'; + this.tokenizer = new Tokenizer(); + this.featuresTags = {}; +} + +/** + * Sets Bidi text + * @param {string} text a text input + */ +Bidi.prototype.setText = function (text) { + this.text = text; +}; + +/** + * Store essential context checks: + * arabic word check for applying gsub features + * arabic sentence check for adjusting arabic layout + */ +Bidi.prototype.contextChecks = ({ + latinWordCheck: latinWordCheck, + arabicWordCheck: arabicWordCheck, + arabicSentenceCheck: arabicSentenceCheck +}); + +/** + * Register arabic word check + */ +function registerContextChecker(checkId) { + var check = this.contextChecks[(checkId + "Check")]; + return this.tokenizer.registerContextChecker( + checkId, check.startCheck, check.endCheck + ); +} + +/** + * Perform pre tokenization procedure then + * tokenize text input + */ +function tokenizeText() { + registerContextChecker.call(this, 'latinWord'); + registerContextChecker.call(this, 'arabicWord'); + registerContextChecker.call(this, 'arabicSentence'); + return this.tokenizer.tokenize(this.text); +} + +/** + * Reverse arabic sentence layout + * TODO: check base dir before applying adjustments - priority low + */ +function reverseArabicSentences() { + var this$1 = this; + + var ranges = this.tokenizer.getContextRanges('arabicSentence'); + ranges.forEach(function (range) { + var rangeTokens = this$1.tokenizer.getRangeTokens(range); + this$1.tokenizer.replaceRange( + range.startIndex, + range.endOffset, + rangeTokens.reverse() + ); + }); +} + +/** + * Register supported features tags + * @param {script} script script tag + * @param {Array} tags features tags list + */ +Bidi.prototype.registerFeatures = function (script, tags) { + var this$1 = this; + + var supportedTags = tags.filter( + function (tag) { return this$1.query.supports({script: script, tag: tag}); } + ); + if (!this.featuresTags.hasOwnProperty(script)) { + this.featuresTags[script] = supportedTags; + } else { + this.featuresTags[script] = + this.featuresTags[script].concat(supportedTags); + } +}; + +/** + * Apply GSUB features + * @param {Array} tagsList a list of features tags + * @param {string} script a script tag + * @param {Font} font opentype font instance + */ +Bidi.prototype.applyFeatures = function (font, features) { + if (!font) { throw new Error( + 'No valid font was provided to apply features' + ); } + if (!this.query) { this.query = new FeatureQuery(font); } + for (var f = 0; f < features.length; f++) { + var feature = features[f]; + if (!this.query.supports({script: feature.script})) { continue; } + this.registerFeatures(feature.script, feature.tags); + } +}; + +/** + * Register a state modifier + * @param {string} modifierId state modifier id + * @param {function} condition a predicate function that returns true or false + * @param {function} modifier a modifier function to set token state + */ +Bidi.prototype.registerModifier = function (modifierId, condition, modifier) { + this.tokenizer.registerModifier(modifierId, condition, modifier); +}; + +/** + * Check if 'glyphIndex' is registered + */ +function checkGlyphIndexStatus() { + if (this.tokenizer.registeredModifiers.indexOf('glyphIndex') === -1) { + throw new Error( + 'glyphIndex modifier is required to apply ' + + 'arabic presentation features.' + ); + } +} + +/** + * Apply arabic presentation forms features + */ +function applyArabicPresentationForms() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicPresentationForms.call(this$1, range); + }); +} + +/** + * Apply required arabic ligatures + */ +function applyArabicRequireLigatures() { + var this$1 = this; + + var script = 'arab'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('rlig') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('arabicWord'); + ranges.forEach(function (range) { + arabicRequiredLigatures.call(this$1, range); + }); +} + +/** + * Apply required arabic ligatures + */ +function applyLatinLigatures() { + var this$1 = this; + + var script = 'latn'; + if (!this.featuresTags.hasOwnProperty(script)) { return; } + var tags = this.featuresTags[script]; + if (tags.indexOf('liga') === -1) { return; } + checkGlyphIndexStatus.call(this); + var ranges = this.tokenizer.getContextRanges('latinWord'); + ranges.forEach(function (range) { + latinLigature.call(this$1, range); + }); +} + +/** + * Check if a context is registered + * @param {string} contextId context id + */ +Bidi.prototype.checkContextReady = function (contextId) { + return !!this.tokenizer.getContext(contextId); +}; + +/** + * Apply features to registered contexts + */ +Bidi.prototype.applyFeaturesToContexts = function () { + if (this.checkContextReady('arabicWord')) { + applyArabicPresentationForms.call(this); + applyArabicRequireLigatures.call(this); + } + if (this.checkContextReady('latinWord')) { + applyLatinLigatures.call(this); + } + if (this.checkContextReady('arabicSentence')) { + reverseArabicSentences.call(this); + } +}; + +/** + * process text input + * @param {string} text an input text + */ +Bidi.prototype.processText = function(text) { + if (!this.text || this.text !== text) { + this.setText(text); + tokenizeText.call(this); + this.applyFeaturesToContexts(); + } +}; + +/** + * Process a string of text to identify and adjust + * bidirectional text entities. + * @param {string} text input text + */ +Bidi.prototype.getBidiText = function (text) { + this.processText(text); + return this.tokenizer.getText(); +}; + +/** + * Get the current state index of each token + * @param {text} text an input text + */ +Bidi.prototype.getTextGlyphs = function (text) { + this.processText(text); + var indexes = []; + for (var i = 0; i < this.tokenizer.tokens.length; i++) { + var token = this.tokenizer.tokens[i]; + if (token.state.deleted) { continue; } + var index = token.activeState.value; + indexes.push(Array.isArray(index) ? index[0] : index); + } + return indexes; +}; + +// The Font object + +/** + * @typedef FontOptions + * @type Object + * @property {Boolean} empty - whether to create a new empty font + * @property {string} familyName + * @property {string} styleName + * @property {string=} fullName + * @property {string=} postScriptName + * @property {string=} designer + * @property {string=} designerURL + * @property {string=} manufacturer + * @property {string=} manufacturerURL + * @property {string=} license + * @property {string=} licenseURL + * @property {string=} version + * @property {string=} description + * @property {string=} copyright + * @property {string=} trademark + * @property {Number} unitsPerEm + * @property {Number} ascender + * @property {Number} descender + * @property {Number} createdTimestamp + * @property {string=} weightClass + * @property {string=} widthClass + * @property {string=} fsSelection + */ + +/** + * A Font represents a loaded OpenType font file. + * It contains a set of glyphs and methods to draw text on a drawing context, + * or to get a path representing the text. + * @exports opentype.Font + * @class + * @param {FontOptions} + * @constructor + */ +function Font(options) { + options = options || {}; + options.tables = options.tables || {}; + + if (!options.empty) { + // Check that we've provided the minimum set of names. + checkArgument(options.familyName, 'When creating a new Font object, familyName is required.'); + checkArgument(options.styleName, 'When creating a new Font object, styleName is required.'); + checkArgument(options.unitsPerEm, 'When creating a new Font object, unitsPerEm is required.'); + checkArgument(options.ascender, 'When creating a new Font object, ascender is required.'); + checkArgument(options.descender <= 0, 'When creating a new Font object, negative descender value is required.'); + + // OS X will complain if the names are empty, so we put a single space everywhere by default. + this.names = { + fontFamily: {en: options.familyName || ' '}, + fontSubfamily: {en: options.styleName || ' '}, + fullName: {en: options.fullName || options.familyName + ' ' + options.styleName}, + // postScriptName may not contain any whitespace + postScriptName: {en: options.postScriptName || (options.familyName + options.styleName).replace(/\s/g, '')}, + designer: {en: options.designer || ' '}, + designerURL: {en: options.designerURL || ' '}, + manufacturer: {en: options.manufacturer || ' '}, + manufacturerURL: {en: options.manufacturerURL || ' '}, + license: {en: options.license || ' '}, + licenseURL: {en: options.licenseURL || ' '}, + version: {en: options.version || 'Version 0.1'}, + description: {en: options.description || ' '}, + copyright: {en: options.copyright || ' '}, + trademark: {en: options.trademark || ' '} + }; + this.unitsPerEm = options.unitsPerEm || 1000; + this.ascender = options.ascender; + this.descender = options.descender; + this.createdTimestamp = options.createdTimestamp; + this.tables = Object.assign(options.tables, { + os2: Object.assign({ + usWeightClass: options.weightClass || this.usWeightClasses.MEDIUM, + usWidthClass: options.widthClass || this.usWidthClasses.MEDIUM, + fsSelection: options.fsSelection || this.fsSelectionValues.REGULAR, + }, options.tables.os2) + }); + } + + this.supported = true; // Deprecated: parseBuffer will throw an error if font is not supported. + this.glyphs = new glyphset.GlyphSet(this, options.glyphs || []); + this.encoding = new DefaultEncoding(this); + this.position = new Position(this); + this.substitution = new Substitution(this); + this.tables = this.tables || {}; + + // needed for low memory mode only. + this._push = null; + this._hmtxTableData = {}; + + Object.defineProperty(this, 'hinting', { + get: function() { + if (this._hinting) { return this._hinting; } + if (this.outlinesFormat === 'truetype') { + return (this._hinting = new Hinting(this)); + } + } + }); +} + +/** + * Check if the font has a glyph for the given character. + * @param {string} + * @return {Boolean} + */ +Font.prototype.hasChar = function(c) { + return this.encoding.charToGlyphIndex(c) !== null; +}; + +/** + * Convert the given character to a single glyph index. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {Number} + */ +Font.prototype.charToGlyphIndex = function(s) { + return this.encoding.charToGlyphIndex(s); +}; + +/** + * Convert the given character to a single Glyph object. + * Note that this function assumes that there is a one-to-one mapping between + * the given character and a glyph; for complex scripts this might not be the case. + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.charToGlyph = function(c) { + var glyphIndex = this.charToGlyphIndex(c); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +/** + * Update features + * @param {any} options features options + */ +Font.prototype.updateFeatures = function (options) { + // TODO: update all features options not only 'latn'. + return this.defaultRenderOptions.features.map(function (feature) { + if (feature.script === 'latn') { + return { + script: 'latn', + tags: feature.tags.filter(function (tag) { return options[tag]; }) + }; + } else { + return feature; + } + }); +}; + +/** + * Convert the given text to a list of Glyph objects. + * Note that there is no strict one-to-one mapping between characters and + * glyphs, so the list of returned glyphs can be larger or smaller than the + * length of the given string. + * @param {string} + * @param {GlyphRenderOptions} [options] + * @return {opentype.Glyph[]} + */ +Font.prototype.stringToGlyphs = function(s, options) { + var this$1 = this; + + + var bidi = new Bidi(); + + // Create and register 'glyphIndex' state modifier + var charToGlyphIndexMod = function (token) { return this$1.charToGlyphIndex(token.char); }; + bidi.registerModifier('glyphIndex', null, charToGlyphIndexMod); + + // roll-back to default features + var features = options ? + this.updateFeatures(options.features) : + this.defaultRenderOptions.features; + + bidi.applyFeatures(this, features); + + var indexes = bidi.getTextGlyphs(s); + + var length = indexes.length; + + // convert glyph indexes to glyph objects + var glyphs = new Array(length); + var notdef = this.glyphs.get(0); + for (var i = 0; i < length; i += 1) { + glyphs[i] = this.glyphs.get(indexes[i]) || notdef; + } + return glyphs; +}; + +/** + * @param {string} + * @return {Number} + */ +Font.prototype.nameToGlyphIndex = function(name) { + return this.glyphNames.nameToGlyphIndex(name); +}; + +/** + * @param {string} + * @return {opentype.Glyph} + */ +Font.prototype.nameToGlyph = function(name) { + var glyphIndex = this.nameToGlyphIndex(name); + var glyph = this.glyphs.get(glyphIndex); + if (!glyph) { + // .notdef + glyph = this.glyphs.get(0); + } + + return glyph; +}; + +/** + * @param {Number} + * @return {String} + */ +Font.prototype.glyphIndexToName = function(gid) { + if (!this.glyphNames.glyphIndexToName) { + return ''; + } + + return this.glyphNames.glyphIndexToName(gid); +}; + +/** + * Retrieve the value of the kerning pair between the left glyph (or its index) + * and the right glyph (or its index). If no kerning pair is found, return 0. + * The kerning value gets added to the advance width when calculating the spacing + * between glyphs. + * For GPOS kerning, this method uses the default script and language, which covers + * most use cases. To have greater control, use font.position.getKerningValue . + * @param {opentype.Glyph} leftGlyph + * @param {opentype.Glyph} rightGlyph + * @return {Number} + */ +Font.prototype.getKerningValue = function(leftGlyph, rightGlyph) { + leftGlyph = leftGlyph.index || leftGlyph; + rightGlyph = rightGlyph.index || rightGlyph; + var gposKerning = this.position.defaultKerningTables; + if (gposKerning) { + return this.position.getKerningValue(gposKerning, leftGlyph, rightGlyph); + } + // "kern" table + return this.kerningPairs[leftGlyph + ',' + rightGlyph] || 0; +}; + +/** + * @typedef GlyphRenderOptions + * @type Object + * @property {string} [script] - script used to determine which features to apply. By default, 'DFLT' or 'latn' is used. + * See https://www.microsoft.com/typography/otspec/scripttags.htm + * @property {string} [language='dflt'] - language system used to determine which features to apply. + * See https://www.microsoft.com/typography/developers/opentype/languagetags.aspx + * @property {boolean} [kerning=true] - whether to include kerning values + * @property {object} [features] - OpenType Layout feature tags. Used to enable or disable the features of the given script/language system. + * See https://www.microsoft.com/typography/otspec/featuretags.htm + */ +Font.prototype.defaultRenderOptions = { + kerning: true, + features: [ + /** + * these 4 features are required to render Arabic text properly + * and shouldn't be turned off when rendering arabic text. + */ + { script: 'arab', tags: ['init', 'medi', 'fina', 'rlig'] }, + { script: 'latn', tags: ['liga', 'rlig'] } + ] +}; + +/** + * Helper function that invokes the given callback for each glyph in the given text. + * The callback gets `(glyph, x, y, fontSize, options)`.* @param {string} text + * @param {string} text - The text to apply. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @param {Function} callback + */ +Font.prototype.forEachGlyph = function(text, x, y, fontSize, options, callback) { + x = x !== undefined ? x : 0; + y = y !== undefined ? y : 0; + fontSize = fontSize !== undefined ? fontSize : 72; + options = Object.assign({}, this.defaultRenderOptions, options); + var fontScale = 1 / this.unitsPerEm * fontSize; + var glyphs = this.stringToGlyphs(text, options); + var kerningLookups; + if (options.kerning) { + var script = options.script || this.position.getDefaultScriptName(); + kerningLookups = this.position.getKerningTables(script, options.language); + } + for (var i = 0; i < glyphs.length; i += 1) { + var glyph = glyphs[i]; + callback.call(this, glyph, x, y, fontSize, options); + if (glyph.advanceWidth) { + x += glyph.advanceWidth * fontScale; + } + + if (options.kerning && i < glyphs.length - 1) { + // We should apply position adjustment lookups in a more generic way. + // Here we only use the xAdvance value. + var kerningValue = kerningLookups ? + this.position.getKerningValue(kerningLookups, glyph.index, glyphs[i + 1].index) : + this.getKerningValue(glyph, glyphs[i + 1]); + x += kerningValue * fontScale; + } + + if (options.letterSpacing) { + x += options.letterSpacing * fontSize; + } else if (options.tracking) { + x += (options.tracking / 1000) * fontSize; + } + } + return x; +}; + +/** + * Create a Path object that represents the given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path} + */ +Font.prototype.getPath = function(text, x, y, fontSize, options) { + var fullPath = new Path(); + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + fullPath.extend(glyphPath); + }); + return fullPath; +}; + +/** + * Create an array of Path objects that represent the glyphs of a given text. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return {opentype.Path[]} + */ +Font.prototype.getPaths = function(text, x, y, fontSize, options) { + var glyphPaths = []; + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + var glyphPath = glyph.getPath(gX, gY, gFontSize, options, this); + glyphPaths.push(glyphPath); + }); + + return glyphPaths; +}; + +/** + * Returns the advance width of a text. + * + * This is something different than Path.getBoundingBox() as for example a + * suffixed whitespace increases the advanceWidth but not the bounding box + * or an overhanging letter like a calligraphic 'f' might have a quite larger + * bounding box than its advance width. + * + * This corresponds to canvas2dContext.measureText(text).width + * + * @param {string} text - The text to create. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + * @return advance width + */ +Font.prototype.getAdvanceWidth = function(text, fontSize, options) { + return this.forEachGlyph(text, 0, 0, fontSize, options, function() {}); +}; + +/** + * Draw the text on the given drawing context. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.draw = function(ctx, text, x, y, fontSize, options) { + this.getPath(text, x, y, fontSize, options).draw(ctx); +}; + +/** + * Draw the points of all glyphs in the text. + * On-curve points will be drawn in blue, off-curve points will be drawn in red. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawPoints = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawPoints(ctx, gX, gY, gFontSize); + }); +}; + +/** + * Draw lines indicating important font measurements for all glyphs in the text. + * Black lines indicate the origin of the coordinate system (point 0,0). + * Blue lines indicate the glyph bounding box. + * Green line indicates the advance width of the glyph. + * @param {CanvasRenderingContext2D} ctx - A 2D drawing context, like Canvas. + * @param {string} text - The text to create. + * @param {number} [x=0] - Horizontal position of the beginning of the text. + * @param {number} [y=0] - Vertical position of the *baseline* of the text. + * @param {number} [fontSize=72] - Font size in pixels. We scale the glyph units by `1 / unitsPerEm * fontSize`. + * @param {GlyphRenderOptions=} options + */ +Font.prototype.drawMetrics = function(ctx, text, x, y, fontSize, options) { + this.forEachGlyph(text, x, y, fontSize, options, function(glyph, gX, gY, gFontSize) { + glyph.drawMetrics(ctx, gX, gY, gFontSize); + }); +}; + +/** + * @param {string} + * @return {string} + */ +Font.prototype.getEnglishName = function(name) { + var translations = this.names[name]; + if (translations) { + return translations.en; + } +}; + +/** + * Validate + */ +Font.prototype.validate = function() { + var _this = this; + + function assert(predicate, message) { + } + + function assertNamePresent(name) { + var englishName = _this.getEnglishName(name); + assert(englishName && englishName.trim().length > 0); + } + + // Identification information + assertNamePresent('fontFamily'); + assertNamePresent('weightName'); + assertNamePresent('manufacturer'); + assertNamePresent('copyright'); + assertNamePresent('version'); + + // Dimension information + assert(this.unitsPerEm > 0); +}; + +/** + * Convert the font object to a SFNT data structure. + * This structure contains all the necessary tables and metadata to create a binary OTF file. + * @return {opentype.Table} + */ +Font.prototype.toTables = function() { + return sfnt.fontToTable(this); +}; +/** + * @deprecated Font.toBuffer is deprecated. Use Font.toArrayBuffer instead. + */ +Font.prototype.toBuffer = function() { + console.warn('Font.toBuffer is deprecated. Use Font.toArrayBuffer instead.'); + return this.toArrayBuffer(); +}; +/** + * Converts a `opentype.Font` into an `ArrayBuffer` + * @return {ArrayBuffer} + */ +Font.prototype.toArrayBuffer = function() { + var sfntTable = this.toTables(); + var bytes = sfntTable.encode(); + var buffer = new ArrayBuffer(bytes.length); + var intArray = new Uint8Array(buffer); + for (var i = 0; i < bytes.length; i++) { + intArray[i] = bytes[i]; + } + + return buffer; +}; + +/** + * Initiate a download of the OpenType font. + */ +Font.prototype.download = function(fileName) { + var familyName = this.getEnglishName('fontFamily'); + var styleName = this.getEnglishName('fontSubfamily'); + fileName = fileName || familyName.replace(/\s/g, '') + '-' + styleName + '.otf'; + var arrayBuffer = this.toArrayBuffer(); + + if (isBrowser()) { + window.URL = window.URL || window.webkitURL; + + if (window.URL) { + var dataView = new DataView(arrayBuffer); + var blob = new Blob([dataView], {type: 'font/opentype'}); + + var link = document.createElement('a'); + link.href = window.URL.createObjectURL(blob); + link.download = fileName; + + var event = document.createEvent('MouseEvents'); + event.initEvent('click', true, false); + link.dispatchEvent(event); + } else { + console.warn('Font file could not be downloaded. Try using a different browser.'); + } + } else { + var fs = require('fs'); + var buffer = arrayBufferToNodeBuffer(arrayBuffer); + fs.writeFileSync(fileName, buffer); + } +}; +/** + * @private + */ +Font.prototype.fsSelectionValues = { + ITALIC: 0x001, //1 + UNDERSCORE: 0x002, //2 + NEGATIVE: 0x004, //4 + OUTLINED: 0x008, //8 + STRIKEOUT: 0x010, //16 + BOLD: 0x020, //32 + REGULAR: 0x040, //64 + USER_TYPO_METRICS: 0x080, //128 + WWS: 0x100, //256 + OBLIQUE: 0x200 //512 +}; + +/** + * @private + */ +Font.prototype.usWidthClasses = { + ULTRA_CONDENSED: 1, + EXTRA_CONDENSED: 2, + CONDENSED: 3, + SEMI_CONDENSED: 4, + MEDIUM: 5, + SEMI_EXPANDED: 6, + EXPANDED: 7, + EXTRA_EXPANDED: 8, + ULTRA_EXPANDED: 9 +}; + +/** + * @private + */ +Font.prototype.usWeightClasses = { + THIN: 100, + EXTRA_LIGHT: 200, + LIGHT: 300, + NORMAL: 400, + MEDIUM: 500, + SEMI_BOLD: 600, + BOLD: 700, + EXTRA_BOLD: 800, + BLACK: 900 +}; + +// The `fvar` table stores font variation axes and instances. + +function addName(name, names) { + var nameString = JSON.stringify(name); + var nameID = 256; + for (var nameKey in names) { + var n = parseInt(nameKey); + if (!n || n < 256) { + continue; + } + + if (JSON.stringify(names[nameKey]) === nameString) { + return n; + } + + if (nameID <= n) { + nameID = n + 1; + } + } + + names[nameID] = name; + return nameID; +} + +function makeFvarAxis(n, axis, names) { + var nameID = addName(axis.name, names); + return [ + {name: 'tag_' + n, type: 'TAG', value: axis.tag}, + {name: 'minValue_' + n, type: 'FIXED', value: axis.minValue << 16}, + {name: 'defaultValue_' + n, type: 'FIXED', value: axis.defaultValue << 16}, + {name: 'maxValue_' + n, type: 'FIXED', value: axis.maxValue << 16}, + {name: 'flags_' + n, type: 'USHORT', value: 0}, + {name: 'nameID_' + n, type: 'USHORT', value: nameID} + ]; +} + +function parseFvarAxis(data, start, names) { + var axis = {}; + var p = new parse.Parser(data, start); + axis.tag = p.parseTag(); + axis.minValue = p.parseFixed(); + axis.defaultValue = p.parseFixed(); + axis.maxValue = p.parseFixed(); + p.skip('uShort', 1); // reserved for flags; no values defined + axis.name = names[p.parseUShort()] || {}; + return axis; +} + +function makeFvarInstance(n, inst, axes, names) { + var nameID = addName(inst.name, names); + var fields = [ + {name: 'nameID_' + n, type: 'USHORT', value: nameID}, + {name: 'flags_' + n, type: 'USHORT', value: 0} + ]; + + for (var i = 0; i < axes.length; ++i) { + var axisTag = axes[i].tag; + fields.push({ + name: 'axis_' + n + ' ' + axisTag, + type: 'FIXED', + value: inst.coordinates[axisTag] << 16 + }); + } + + return fields; +} + +function parseFvarInstance(data, start, axes, names) { + var inst = {}; + var p = new parse.Parser(data, start); + inst.name = names[p.parseUShort()] || {}; + p.skip('uShort', 1); // reserved for flags; no values defined + + inst.coordinates = {}; + for (var i = 0; i < axes.length; ++i) { + inst.coordinates[axes[i].tag] = p.parseFixed(); + } + + return inst; +} + +function makeFvarTable(fvar, names) { + var result = new table.Table('fvar', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'offsetToData', type: 'USHORT', value: 0}, + {name: 'countSizePairs', type: 'USHORT', value: 2}, + {name: 'axisCount', type: 'USHORT', value: fvar.axes.length}, + {name: 'axisSize', type: 'USHORT', value: 20}, + {name: 'instanceCount', type: 'USHORT', value: fvar.instances.length}, + {name: 'instanceSize', type: 'USHORT', value: 4 + fvar.axes.length * 4} + ]); + result.offsetToData = result.sizeOf(); + + for (var i = 0; i < fvar.axes.length; i++) { + result.fields = result.fields.concat(makeFvarAxis(i, fvar.axes[i], names)); + } + + for (var j = 0; j < fvar.instances.length; j++) { + result.fields = result.fields.concat(makeFvarInstance(j, fvar.instances[j], fvar.axes, names)); + } + + return result; +} + +function parseFvarTable(data, start, names) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseULong(); + check.argument(tableVersion === 0x00010000, 'Unsupported fvar table version.'); + var offsetToData = p.parseOffset16(); + // Skip countSizePairs. + p.skip('uShort', 1); + var axisCount = p.parseUShort(); + var axisSize = p.parseUShort(); + var instanceCount = p.parseUShort(); + var instanceSize = p.parseUShort(); + + var axes = []; + for (var i = 0; i < axisCount; i++) { + axes.push(parseFvarAxis(data, start + offsetToData + i * axisSize, names)); + } + + var instances = []; + var instanceStart = start + offsetToData + axisCount * axisSize; + for (var j = 0; j < instanceCount; j++) { + instances.push(parseFvarInstance(data, instanceStart + j * instanceSize, axes, names)); + } + + return {axes: axes, instances: instances}; +} + +var fvar = { make: makeFvarTable, parse: parseFvarTable }; + +// The `GDEF` table contains various glyph properties + +var attachList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + attachPoints: this.parseList(Parser.pointer(Parser.uShortList)) + }; +}; + +var caretValue = function() { + var format = this.parseUShort(); + check.argument(format === 1 || format === 2 || format === 3, + 'Unsupported CaretValue table version.'); + if (format === 1) { + return { coordinate: this.parseShort() }; + } else if (format === 2) { + return { pointindex: this.parseShort() }; + } else if (format === 3) { + // Device / Variation Index tables unsupported + return { coordinate: this.parseShort() }; + } +}; + +var ligGlyph = function() { + return this.parseList(Parser.pointer(caretValue)); +}; + +var ligCaretList = function() { + return { + coverage: this.parsePointer(Parser.coverage), + ligGlyphs: this.parseList(Parser.pointer(ligGlyph)) + }; +}; + +var markGlyphSets = function() { + this.parseUShort(); // Version + return this.parseList(Parser.pointer(Parser.coverage)); +}; + +function parseGDEFTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.2 || tableVersion === 1.3, + 'Unsupported GDEF table version.'); + var gdef = { + version: tableVersion, + classDef: p.parsePointer(Parser.classDef), + attachList: p.parsePointer(attachList), + ligCaretList: p.parsePointer(ligCaretList), + markAttachClassDef: p.parsePointer(Parser.classDef) + }; + if (tableVersion >= 1.2) { + gdef.markGlyphSets = p.parsePointer(markGlyphSets); + } + return gdef; +} +var gdef = { parse: parseGDEFTable }; + +// The `GPOS` table contains kerning pairs, among other things. + +var subtableParsers$1 = new Array(10); // subtableParsers[0] is unused + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-1-single-adjustment-positioning-subtable +// this = Parser instance +subtableParsers$1[1] = function parseLookup1() { + var start = this.offset + this.relativeOffset; + var posformat = this.parseUShort(); + if (posformat === 1) { + return { + posFormat: 1, + coverage: this.parsePointer(Parser.coverage), + value: this.parseValueRecord() + }; + } else if (posformat === 2) { + return { + posFormat: 2, + coverage: this.parsePointer(Parser.coverage), + values: this.parseValueRecordList() + }; + } + check.assert(false, '0x' + start.toString(16) + ': GPOS lookup type 1 format must be 1 or 2.'); +}; + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos#lookup-type-2-pair-adjustment-positioning-subtable +subtableParsers$1[2] = function parseLookup2() { + var start = this.offset + this.relativeOffset; + var posFormat = this.parseUShort(); + check.assert(posFormat === 1 || posFormat === 2, '0x' + start.toString(16) + ': GPOS lookup type 2 format must be 1 or 2.'); + var coverage = this.parsePointer(Parser.coverage); + var valueFormat1 = this.parseUShort(); + var valueFormat2 = this.parseUShort(); + if (posFormat === 1) { + // Adjustments for Glyph Pairs + return { + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + pairSets: this.parseList(Parser.pointer(Parser.list(function() { + return { // pairValueRecord + secondGlyph: this.parseUShort(), + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + }))) + }; + } else if (posFormat === 2) { + var classDef1 = this.parsePointer(Parser.classDef); + var classDef2 = this.parsePointer(Parser.classDef); + var class1Count = this.parseUShort(); + var class2Count = this.parseUShort(); + return { + // Class Pair Adjustment + posFormat: posFormat, + coverage: coverage, + valueFormat1: valueFormat1, + valueFormat2: valueFormat2, + classDef1: classDef1, + classDef2: classDef2, + class1Count: class1Count, + class2Count: class2Count, + classRecords: this.parseList(class1Count, Parser.list(class2Count, function() { + return { + value1: this.parseValueRecord(valueFormat1), + value2: this.parseValueRecord(valueFormat2) + }; + })) + }; + } +}; + +subtableParsers$1[3] = function parseLookup3() { return { error: 'GPOS Lookup 3 not supported' }; }; +subtableParsers$1[4] = function parseLookup4() { return { error: 'GPOS Lookup 4 not supported' }; }; +subtableParsers$1[5] = function parseLookup5() { return { error: 'GPOS Lookup 5 not supported' }; }; +subtableParsers$1[6] = function parseLookup6() { return { error: 'GPOS Lookup 6 not supported' }; }; +subtableParsers$1[7] = function parseLookup7() { return { error: 'GPOS Lookup 7 not supported' }; }; +subtableParsers$1[8] = function parseLookup8() { return { error: 'GPOS Lookup 8 not supported' }; }; +subtableParsers$1[9] = function parseLookup9() { return { error: 'GPOS Lookup 9 not supported' }; }; + +// https://docs.microsoft.com/en-us/typography/opentype/spec/gpos +function parseGposTable(data, start) { + start = start || 0; + var p = new Parser(data, start); + var tableVersion = p.parseVersion(1); + check.argument(tableVersion === 1 || tableVersion === 1.1, 'Unsupported GPOS table version ' + tableVersion); + + if (tableVersion === 1) { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1) + }; + } else { + return { + version: tableVersion, + scripts: p.parseScriptList(), + features: p.parseFeatureList(), + lookups: p.parseLookupList(subtableParsers$1), + variations: p.parseFeatureVariationsList() + }; + } + +} + +// GPOS Writing ////////////////////////////////////////////// +// NOT SUPPORTED +var subtableMakers$1 = new Array(10); + +function makeGposTable(gpos) { + return new table.Table('GPOS', [ + {name: 'version', type: 'ULONG', value: 0x10000}, + {name: 'scripts', type: 'TABLE', value: new table.ScriptList(gpos.scripts)}, + {name: 'features', type: 'TABLE', value: new table.FeatureList(gpos.features)}, + {name: 'lookups', type: 'TABLE', value: new table.LookupList(gpos.lookups, subtableMakers$1)} + ]); +} + +var gpos = { parse: parseGposTable, make: makeGposTable }; + +// The `kern` table contains kerning pairs. + +function parseWindowsKernTable(p) { + var pairs = {}; + // Skip nTables. + p.skip('uShort'); + var subtableVersion = p.parseUShort(); + check.argument(subtableVersion === 0, 'Unsupported kern sub-table version.'); + // Skip subtableLength, subtableCoverage + p.skip('uShort', 2); + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + return pairs; +} + +function parseMacKernTable(p) { + var pairs = {}; + // The Mac kern table stores the version as a fixed (32 bits) but we only loaded the first 16 bits. + // Skip the rest. + p.skip('uShort'); + var nTables = p.parseULong(); + //check.argument(nTables === 1, 'Only 1 subtable is supported (got ' + nTables + ').'); + if (nTables > 1) { + console.warn('Only the first kern subtable is supported.'); + } + p.skip('uLong'); + var coverage = p.parseUShort(); + var subtableVersion = coverage & 0xFF; + p.skip('uShort'); + if (subtableVersion === 0) { + var nPairs = p.parseUShort(); + // Skip searchRange, entrySelector, rangeShift. + p.skip('uShort', 3); + for (var i = 0; i < nPairs; i += 1) { + var leftIndex = p.parseUShort(); + var rightIndex = p.parseUShort(); + var value = p.parseShort(); + pairs[leftIndex + ',' + rightIndex] = value; + } + } + return pairs; +} + +// Parse the `kern` table which contains kerning pairs. +function parseKernTable(data, start) { + var p = new parse.Parser(data, start); + var tableVersion = p.parseUShort(); + if (tableVersion === 0) { + return parseWindowsKernTable(p); + } else if (tableVersion === 1) { + return parseMacKernTable(p); + } else { + throw new Error('Unsupported kern table version (' + tableVersion + ').'); + } +} + +var kern = { parse: parseKernTable }; + +// The `loca` table stores the offsets to the locations of the glyphs in the font. + +// Parse the `loca` table. This table stores the offsets to the locations of the glyphs in the font, +// relative to the beginning of the glyphData table. +// The number of glyphs stored in the `loca` table is specified in the `maxp` table (under numGlyphs) +// The loca table has two versions: a short version where offsets are stored as uShorts, and a long +// version where offsets are stored as uLongs. The `head` table specifies which version to use +// (under indexToLocFormat). +function parseLocaTable(data, start, numGlyphs, shortVersion) { + var p = new parse.Parser(data, start); + var parseFn = shortVersion ? p.parseUShort : p.parseULong; + // There is an extra entry after the last index element to compute the length of the last glyph. + // That's why we use numGlyphs + 1. + var glyphOffsets = []; + for (var i = 0; i < numGlyphs + 1; i += 1) { + var glyphOffset = parseFn.call(p); + if (shortVersion) { + // The short table version stores the actual offset divided by 2. + glyphOffset *= 2; + } + + glyphOffsets.push(glyphOffset); + } + + return glyphOffsets; +} + +var loca = { parse: parseLocaTable }; + +// opentype.js + +/** + * The opentype library. + * @namespace opentype + */ + +// File loaders ///////////////////////////////////////////////////////// +/** + * Loads a font from a file. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} path - The path of the file + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromFile(path, callback) { + var fs = require('fs'); + fs.readFile(path, function(err, buffer) { + if (err) { + return callback(err.message); + } + + callback(null, nodeBufferToArrayBuffer(buffer)); + }); +} +/** + * Loads a font from a URL. The callback throws an error message as the first parameter if it fails + * and the font as an ArrayBuffer in the second parameter if it succeeds. + * @param {string} url - The URL of the font file. + * @param {Function} callback - The function to call when the font load completes + */ +function loadFromUrl(url, callback) { + var request = new XMLHttpRequest(); + request.open('get', url, true); + request.responseType = 'arraybuffer'; + request.onload = function() { + if (request.response) { + return callback(null, request.response); + } else { + return callback('Font could not be loaded: ' + request.statusText); + } + }; + + request.onerror = function () { + callback('Font could not be loaded'); + }; + + request.send(); +} + +// Table Directory Entries ////////////////////////////////////////////// +/** + * Parses OpenType table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseOpenTypeTableEntries(data, numTables) { + var tableEntries = []; + var p = 12; + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var checksum = parse.getULong(data, p + 4); + var offset = parse.getULong(data, p + 8); + var length = parse.getULong(data, p + 12); + tableEntries.push({tag: tag, checksum: checksum, offset: offset, length: length, compression: false}); + p += 16; + } + + return tableEntries; +} + +/** + * Parses WOFF table entries. + * @param {DataView} + * @param {Number} + * @return {Object[]} + */ +function parseWOFFTableEntries(data, numTables) { + var tableEntries = []; + var p = 44; // offset to the first table directory entry. + for (var i = 0; i < numTables; i += 1) { + var tag = parse.getTag(data, p); + var offset = parse.getULong(data, p + 4); + var compLength = parse.getULong(data, p + 8); + var origLength = parse.getULong(data, p + 12); + var compression = (void 0); + if (compLength < origLength) { + compression = 'WOFF'; + } else { + compression = false; + } + + tableEntries.push({tag: tag, offset: offset, compression: compression, + compressedLength: compLength, length: origLength}); + p += 20; + } + + return tableEntries; +} + +/** + * @typedef TableData + * @type Object + * @property {DataView} data - The DataView + * @property {number} offset - The data offset. + */ + +/** + * @param {DataView} + * @param {Object} + * @return {TableData} + */ +function uncompressTable(data, tableEntry) { + if (tableEntry.compression === 'WOFF') { + var inBuffer = new Uint8Array(data.buffer, tableEntry.offset + 2, tableEntry.compressedLength - 2); + var outBuffer = new Uint8Array(tableEntry.length); + tinyInflate(inBuffer, outBuffer); + if (outBuffer.byteLength !== tableEntry.length) { + throw new Error('Decompression error: ' + tableEntry.tag + ' decompressed length doesn\'t match recorded length'); + } + + var view = new DataView(outBuffer.buffer, 0); + return {data: view, offset: 0}; + } else { + return {data: data, offset: tableEntry.offset}; + } +} + +// Public API /////////////////////////////////////////////////////////// + +/** + * Parse the OpenType file data (as an ArrayBuffer) and return a Font object. + * Throws an error if the font could not be parsed. + * @param {ArrayBuffer} + * @param {Object} opt - options for parsing + * @return {opentype.Font} + */ +function parseBuffer(buffer, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + + var indexToLocFormat; + var ltagTable; + + // Since the constructor can also be called to create new fonts from scratch, we indicate this + // should be an empty font that we'll fill with our own data. + var font = new Font({empty: true}); + + // OpenType fonts use big endian byte ordering. + // We can't rely on typed array view types, because they operate with the endianness of the host computer. + // Instead we use DataViews where we can specify endianness. + var data = new DataView(buffer, 0); + var numTables; + var tableEntries = []; + var signature = parse.getTag(data, 0); + if (signature === String.fromCharCode(0, 1, 0, 0) || signature === 'true' || signature === 'typ1') { + font.outlinesFormat = 'truetype'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'OTTO') { + font.outlinesFormat = 'cff'; + numTables = parse.getUShort(data, 4); + tableEntries = parseOpenTypeTableEntries(data, numTables); + } else if (signature === 'wOFF') { + var flavor = parse.getTag(data, 4); + if (flavor === String.fromCharCode(0, 1, 0, 0)) { + font.outlinesFormat = 'truetype'; + } else if (flavor === 'OTTO') { + font.outlinesFormat = 'cff'; + } else { + throw new Error('Unsupported OpenType flavor ' + signature); + } + + numTables = parse.getUShort(data, 12); + tableEntries = parseWOFFTableEntries(data, numTables); + } else { + throw new Error('Unsupported OpenType signature ' + signature); + } + + var cffTableEntry; + var fvarTableEntry; + var glyfTableEntry; + var gdefTableEntry; + var gposTableEntry; + var gsubTableEntry; + var hmtxTableEntry; + var kernTableEntry; + var locaTableEntry; + var nameTableEntry; + var metaTableEntry; + var p; + + for (var i = 0; i < numTables; i += 1) { + var tableEntry = tableEntries[i]; + var table = (void 0); + switch (tableEntry.tag) { + case 'cmap': + table = uncompressTable(data, tableEntry); + font.tables.cmap = cmap.parse(table.data, table.offset); + font.encoding = new CmapEncoding(font.tables.cmap); + break; + case 'cvt ' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.cvt = p.parseShortList(tableEntry.length / 2); + break; + case 'fvar': + fvarTableEntry = tableEntry; + break; + case 'fpgm' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.fpgm = p.parseByteList(tableEntry.length); + break; + case 'head': + table = uncompressTable(data, tableEntry); + font.tables.head = head.parse(table.data, table.offset); + font.unitsPerEm = font.tables.head.unitsPerEm; + indexToLocFormat = font.tables.head.indexToLocFormat; + break; + case 'hhea': + table = uncompressTable(data, tableEntry); + font.tables.hhea = hhea.parse(table.data, table.offset); + font.ascender = font.tables.hhea.ascender; + font.descender = font.tables.hhea.descender; + font.numberOfHMetrics = font.tables.hhea.numberOfHMetrics; + break; + case 'hmtx': + hmtxTableEntry = tableEntry; + break; + case 'ltag': + table = uncompressTable(data, tableEntry); + ltagTable = ltag.parse(table.data, table.offset); + break; + case 'COLR': + table = uncompressTable(data, tableEntry); + font.tables.colr = colr.parse(table.data, table.offset); + break; + case 'CPAL': + table = uncompressTable(data, tableEntry); + font.tables.cpal = cpal.parse(table.data, table.offset); + break; + case 'maxp': + table = uncompressTable(data, tableEntry); + font.tables.maxp = maxp.parse(table.data, table.offset); + font.numGlyphs = font.tables.maxp.numGlyphs; + break; + case 'name': + nameTableEntry = tableEntry; + break; + case 'OS/2': + table = uncompressTable(data, tableEntry); + font.tables.os2 = os2.parse(table.data, table.offset); + break; + case 'post': + table = uncompressTable(data, tableEntry); + font.tables.post = post.parse(table.data, table.offset); + font.glyphNames = new GlyphNames(font.tables.post); + break; + case 'prep' : + table = uncompressTable(data, tableEntry); + p = new parse.Parser(table.data, table.offset); + font.tables.prep = p.parseByteList(tableEntry.length); + break; + case 'glyf': + glyfTableEntry = tableEntry; + break; + case 'loca': + locaTableEntry = tableEntry; + break; + case 'CFF ': + cffTableEntry = tableEntry; + break; + case 'kern': + kernTableEntry = tableEntry; + break; + case 'GDEF': + gdefTableEntry = tableEntry; + break; + case 'GPOS': + gposTableEntry = tableEntry; + break; + case 'GSUB': + gsubTableEntry = tableEntry; + break; + case 'meta': + metaTableEntry = tableEntry; + break; + } + } + + var nameTable = uncompressTable(data, nameTableEntry); + font.tables.name = _name.parse(nameTable.data, nameTable.offset, ltagTable); + font.names = font.tables.name; + + if (glyfTableEntry && locaTableEntry) { + var shortVersion = indexToLocFormat === 0; + var locaTable = uncompressTable(data, locaTableEntry); + var locaOffsets = loca.parse(locaTable.data, locaTable.offset, font.numGlyphs, shortVersion); + var glyfTable = uncompressTable(data, glyfTableEntry); + font.glyphs = glyf.parse(glyfTable.data, glyfTable.offset, locaOffsets, font, opt); + } else if (cffTableEntry) { + var cffTable = uncompressTable(data, cffTableEntry); + cff.parse(cffTable.data, cffTable.offset, font, opt); + } else { + throw new Error('Font doesn\'t contain TrueType or CFF outlines.'); + } + + var hmtxTable = uncompressTable(data, hmtxTableEntry); + hmtx.parse(font, hmtxTable.data, hmtxTable.offset, font.numberOfHMetrics, font.numGlyphs, font.glyphs, opt); + addGlyphNames(font, opt); + + if (kernTableEntry) { + var kernTable = uncompressTable(data, kernTableEntry); + font.kerningPairs = kern.parse(kernTable.data, kernTable.offset); + } else { + font.kerningPairs = {}; + } + + if (gdefTableEntry) { + var gdefTable = uncompressTable(data, gdefTableEntry); + font.tables.gdef = gdef.parse(gdefTable.data, gdefTable.offset); + } + + if (gposTableEntry) { + var gposTable = uncompressTable(data, gposTableEntry); + font.tables.gpos = gpos.parse(gposTable.data, gposTable.offset); + font.position.init(); + } + + if (gsubTableEntry) { + var gsubTable = uncompressTable(data, gsubTableEntry); + font.tables.gsub = gsub.parse(gsubTable.data, gsubTable.offset); + } + + if (fvarTableEntry) { + var fvarTable = uncompressTable(data, fvarTableEntry); + font.tables.fvar = fvar.parse(fvarTable.data, fvarTable.offset, font.names); + } + + if (metaTableEntry) { + var metaTable = uncompressTable(data, metaTableEntry); + font.tables.meta = meta.parse(metaTable.data, metaTable.offset); + font.metas = font.tables.meta; + } + + return font; +} + +/** + * Asynchronously load the font from a URL or a filesystem. When done, call the callback + * with two arguments `(err, font)`. The `err` will be null on success, + * the `font` is a Font object. + * We use the node.js callback convention so that + * opentype.js can integrate with frameworks like async.js. + * @alias opentype.load + * @param {string} url - The URL of the font to load. + * @param {Function} callback - The callback. + */ +function load(url, callback, opt) { + opt = (opt === undefined || opt === null) ? {} : opt; + var isNode = typeof window === 'undefined'; + var loadFn = isNode && !opt.isUrl ? loadFromFile : loadFromUrl; + + return new Promise(function (resolve, reject) { + loadFn(url, function(err, arrayBuffer) { + if (err) { + if (callback) { + return callback(err); + } else { + reject(err); + } + } + var font; + try { + font = parseBuffer(arrayBuffer, opt); + } catch (e) { + if (callback) { + return callback(e, null); + } else { + reject(e); + } + } + if (callback) { + return callback(null, font); + } else { + resolve(font); + } + }); + }); +} + +/** + * Synchronously load the font from a URL or file. + * When done, returns the font object or throws an error. + * @alias opentype.loadSync + * @param {string} url - The URL of the font to load. + * @param {Object} opt - opt.lowMemory + * @return {opentype.Font} + */ +function loadSync(url, opt) { + var fs = require('fs'); + var buffer = fs.readFileSync(url); + return parseBuffer(nodeBufferToArrayBuffer(buffer), opt); +} + +var opentype = /*#__PURE__*/Object.freeze({ + __proto__: null, + Font: Font, + Glyph: Glyph, + Path: Path, + BoundingBox: BoundingBox, + _parse: parse, + parse: parseBuffer, + load: load, + loadSync: loadSync +}); + +export default opentype; +export { BoundingBox, Font, Glyph, Path, parse as _parse, load, loadSync, parseBuffer as parse }; diff --git a/jsm/libs/potpack.module.js b/jsm/libs/potpack.module.js new file mode 100644 index 0000000..efba9a4 --- /dev/null +++ b/jsm/libs/potpack.module.js @@ -0,0 +1,125 @@ +/** + * potpack - by [@mourner](https://github.com/mourner) + * + * A tiny JavaScript function for packing 2D rectangles into a near-square container, + * which is useful for generating CSS sprites and WebGL textures. Similar to + * [shelf-pack](https://github.com/mapbox/shelf-pack), but static (you can't add items + * once a layout is generated), and aims for maximal space utilization. + * + * A variation of algorithms used in [rectpack2D](https://github.com/TeamHypersomnia/rectpack2D) + * and [bin-pack](https://github.com/bryanburgers/bin-pack), which are in turn based + * on [this article by Blackpawn](http://blackpawn.com/texts/lightmaps/default.html). + * + * @license + * ISC License + * + * Copyright (c) 2018, Mapbox + * + * Permission to use, copy, modify, and/or distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright notice + * and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER + * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + * THIS SOFTWARE. + */ + +function potpack(boxes) { + + // calculate total box area and maximum box width + let area = 0; + let maxWidth = 0; + + for (const box of boxes) { + area += box.w * box.h; + maxWidth = Math.max(maxWidth, box.w); + } + + // sort the boxes for insertion by height, descending + boxes.sort((a, b) => b.h - a.h); + + // aim for a squarish resulting container, + // slightly adjusted for sub-100% space utilization + const startWidth = Math.max(Math.ceil(Math.sqrt(area / 0.95)), maxWidth); + + // start with a single empty space, unbounded at the bottom + const spaces = [{x: 0, y: 0, w: startWidth, h: Infinity}]; + + let width = 0; + let height = 0; + + for (const box of boxes) { + // look through spaces backwards so that we check smaller spaces first + for (let i = spaces.length - 1; i >= 0; i--) { + const space = spaces[i]; + + // look for empty spaces that can accommodate the current box + if (box.w > space.w || box.h > space.h) continue; + + // found the space; add the box to its top-left corner + // |-------|-------| + // | box | | + // |_______| | + // | space | + // |_______________| + box.x = space.x; + box.y = space.y; + + height = Math.max(height, box.y + box.h); + width = Math.max(width, box.x + box.w); + + if (box.w === space.w && box.h === space.h) { + // space matches the box exactly; remove it + const last = spaces.pop(); + if (i < spaces.length) spaces[i] = last; + + } else if (box.h === space.h) { + // space matches the box height; update it accordingly + // |-------|---------------| + // | box | updated space | + // |_______|_______________| + space.x += box.w; + space.w -= box.w; + + } else if (box.w === space.w) { + // space matches the box width; update it accordingly + // |---------------| + // | box | + // |_______________| + // | updated space | + // |_______________| + space.y += box.h; + space.h -= box.h; + + } else { + // otherwise the box splits the space into two spaces + // |-------|-----------| + // | box | new space | + // |_______|___________| + // | updated space | + // |___________________| + spaces.push({ + x: space.x + box.w, + y: space.y, + w: space.w - box.w, + h: box.h + }); + space.y += box.h; + space.h -= box.h; + } + break; + } + } + + return { + w: width, // container width + h: height, // container height + fill: (area / (width * height)) || 0 // space utilization + }; +} + +export { potpack }; \ No newline at end of file diff --git a/jsm/libs/rhino3dm/rhino3dm.js b/jsm/libs/rhino3dm/rhino3dm.js new file mode 100644 index 0000000..8cf7ae3 --- /dev/null +++ b/jsm/libs/rhino3dm/rhino3dm.js @@ -0,0 +1,21 @@ + +var rhino3dm = (function() { + var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined; + if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename; + return ( +function(rhino3dm) { + rhino3dm = rhino3dm || {}; + +var Module=typeof rhino3dm!=="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function convertJsFunctionToWasm(func,sig){if(typeof WebAssembly.Function==="function"){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["memory"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["__indirect_function_table"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}var ExceptionInfoAttrs={DESTRUCTOR_OFFSET:0,REFCOUNT_OFFSET:4,TYPE_OFFSET:8,CAUGHT_OFFSET:12,RETHROWN_OFFSET:13,SIZE:16};function ___cxa_allocate_exception(size){return _malloc(size+ExceptionInfoAttrs.SIZE)+ExceptionInfoAttrs.SIZE}function _atexit(func,arg){}function ___cxa_atexit(a0,a1){return _atexit(a0,a1)}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-ExceptionInfoAttrs.SIZE;this.set_type=function(type){HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]=type};this.get_type=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]};this.set_destructor=function(destructor){HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]=destructor};this.get_destructor=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]!=0};this.init=function(type,destructor){this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=prev-1;return prev===1}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;if(!_gmtime_r.GMTString)_gmtime_r.GMTString=allocateUTF8("GMT");HEAP32[tmPtr+40>>2]=_gmtime_r.GMTString;return tmPtr}function ___gmtime_r(a0,a1){return _gmtime_r(a0,a1)}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){var alignedSize=alignMemory(size,16384);var ptr=_malloc(alignedSize);while(size=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){FS.forceLoadFile(node);var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___sys_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2)+i])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_as(handle,returnType,destructorsRef){handle=requireHandle(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=__emval_register(destructors);HEAP32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_allocateDestructors(destructorsRef){var destructors=[];HEAP32[destructorsRef>>2]=__emval_register(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,__emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=requireHandle(first);second=requireHandle(second);return first==second}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function __emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2)+i],"parameter "+i)}return a}function __emval_get_method_caller(argCount,argTypes){var types=__emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=requireHandle(object);constructor=requireHandle(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=requireHandle(handle);return typeof handle==="number"}function __emval_is_string(handle){handle=requireHandle(handle);return typeof handle==="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;i>> 2) + "+i+'], "parameter '+i+'");\n'+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return __emval_register(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","__emval_register",functionBody)(requireRegisteredType,Module,__emval_register)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return __emval_register([])}function __emval_new_cstring(v){return __emval_register(getStringOrSymbol(v))}function __emval_new_object(){return __emval_register({})}function __emval_run_destructors(handle){var destructors=emval_handle_array[handle].value;runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=requireHandle(handle);key=requireHandle(key);value=requireHandle(value);handle[key]=value}function __emval_take_value(type,argv){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](argv);return __emval_register(v)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance.now()};function _emscripten_thread_sleep(msecs){var start=_emscripten_get_now();while(_emscripten_get_now()-start>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _setTempRet0($i){setTempRet0($i|0)}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _uuid_generate(out){var uuid=null;if(ENVIRONMENT_IS_NODE){try{var rb=require("crypto")["randomBytes"];uuid=rb(16)}catch(e){}}else if(ENVIRONMENT_IS_WEB&&typeof window.crypto!=="undefined"&&typeof window.crypto.getRandomValues!=="undefined"){uuid=new Uint8Array(16);window.crypto.getRandomValues(uuid)}if(!uuid){uuid=new Array(16);var d=(new Date).getTime();for(var i=0;i<16;i++){var r=(d+Math.random()*256)%256|0;d=d/256|0;uuid[i]=r}}uuid[6]=uuid[6]&15|64;uuid[8]=uuid[8]&127|128;writeArrayToMemory(uuid,out)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"__cxa_allocate_exception":___cxa_allocate_exception,"__cxa_atexit":___cxa_atexit,"__cxa_throw":___cxa_throw,"__gmtime_r":___gmtime_r,"__sys_fcntl64":___sys_fcntl64,"__sys_fstat64":___sys_fstat64,"__sys_ioctl":___sys_ioctl,"__sys_open":___sys_open,"__sys_stat64":___sys_stat64,"_embind_finalize_value_array":__embind_finalize_value_array,"_embind_finalize_value_object":__embind_finalize_value_object,"_embind_register_bool":__embind_register_bool,"_embind_register_class":__embind_register_class,"_embind_register_class_class_function":__embind_register_class_class_function,"_embind_register_class_constructor":__embind_register_class_constructor,"_embind_register_class_function":__embind_register_class_function,"_embind_register_class_property":__embind_register_class_property,"_embind_register_emval":__embind_register_emval,"_embind_register_enum":__embind_register_enum,"_embind_register_enum_value":__embind_register_enum_value,"_embind_register_float":__embind_register_float,"_embind_register_integer":__embind_register_integer,"_embind_register_memory_view":__embind_register_memory_view,"_embind_register_std_string":__embind_register_std_string,"_embind_register_std_wstring":__embind_register_std_wstring,"_embind_register_value_array":__embind_register_value_array,"_embind_register_value_array_element":__embind_register_value_array_element,"_embind_register_value_object":__embind_register_value_object,"_embind_register_value_object_field":__embind_register_value_object_field,"_embind_register_void":__embind_register_void,"_emval_as":__emval_as,"_emval_call_method":__emval_call_method,"_emval_call_void_method":__emval_call_void_method,"_emval_decref":__emval_decref,"_emval_equals":__emval_equals,"_emval_get_global":__emval_get_global,"_emval_get_method_caller":__emval_get_method_caller,"_emval_get_module_property":__emval_get_module_property,"_emval_get_property":__emval_get_property,"_emval_incref":__emval_incref,"_emval_instanceof":__emval_instanceof,"_emval_is_number":__emval_is_number,"_emval_is_string":__emval_is_string,"_emval_new":__emval_new,"_emval_new_array":__emval_new_array,"_emval_new_cstring":__emval_new_cstring,"_emval_new_object":__emval_new_object,"_emval_run_destructors":__emval_run_destructors,"_emval_set_property":__emval_set_property,"_emval_take_value":__emval_take_value,"abort":_abort,"emscripten_memcpy_big":_emscripten_memcpy_big,"emscripten_resize_heap":_emscripten_resize_heap,"emscripten_thread_sleep":_emscripten_thread_sleep,"environ_get":_environ_get,"environ_sizes_get":_environ_sizes_get,"fd_close":_fd_close,"fd_fdstat_get":_fd_fdstat_get,"fd_read":_fd_read,"fd_seek":_fd_seek,"fd_write":_fd_write,"setTempRet0":_setTempRet0,"time":_time,"uuid_generate":_uuid_generate};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["__wasm_call_ctors"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["malloc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["free"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["__getTypeName"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["__embind_register_native_and_builtin_types"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["__errno_location"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["stackSave"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["stackRestore"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["stackAlloc"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["setThrew"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["dynCall_ji"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["dynCall_jiji"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run(); + + + return rhino3dm.ready +} +); +})(); +if (typeof exports === 'object' && typeof module === 'object') + module.exports = rhino3dm; +else if (typeof define === 'function' && define['amd']) + define([], function() { return rhino3dm; }); +else if (typeof exports === 'object') + exports["rhino3dm"] = rhino3dm; diff --git a/jsm/libs/rhino3dm/rhino3dm.module.js b/jsm/libs/rhino3dm/rhino3dm.module.js new file mode 100644 index 0000000..57a6f56 --- /dev/null +++ b/jsm/libs/rhino3dm/rhino3dm.module.js @@ -0,0 +1,16 @@ + +var rhino3dm = (function() { + var _scriptDir = import.meta.url; + + return ( +function(rhino3dm) { + rhino3dm = rhino3dm || {}; + +var Module=typeof rhino3dm!=="undefined"?rhino3dm:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!=="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}function warnOnce(text){if(!warnOnce.shown)warnOnce.shown={};if(!warnOnce.shown[text]){warnOnce.shown[text]=1;err(text)}}function convertJsFunctionToWasm(func,sig){if(typeof WebAssembly.Function==="function"){var typeNames={"i":"i32","j":"i64","f":"f32","d":"f64"};var type={parameters:[],results:sig[0]=="v"?[]:[typeNames[sig[0]]]};for(var i=1;i=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var str="";for(var i=0;!(i>=maxBytesToRead/2);++i){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0)break;str+=String.fromCharCode(codeUnit)}return str}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_MEMORY=Module["INITIAL_MEMORY"]||16777216;var wasmTable;var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="rhino3dm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"env":asmLibraryArg,"wasi_snapshot_preview1":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;wasmMemory=Module["asm"]["memory"];updateGlobalBufferAndViews(wasmMemory.buffer);wasmTable=Module["asm"]["__indirect_function_table"];removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync().catch(readyPromiseReject);return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}var ExceptionInfoAttrs={DESTRUCTOR_OFFSET:0,REFCOUNT_OFFSET:4,TYPE_OFFSET:8,CAUGHT_OFFSET:12,RETHROWN_OFFSET:13,SIZE:16};function ___cxa_allocate_exception(size){return _malloc(size+ExceptionInfoAttrs.SIZE)+ExceptionInfoAttrs.SIZE}function _atexit(func,arg){}function ___cxa_atexit(a0,a1){return _atexit(a0,a1)}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-ExceptionInfoAttrs.SIZE;this.set_type=function(type){HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]=type};this.get_type=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]};this.set_destructor=function(destructor){HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]=destructor};this.get_destructor=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]!=0};this.init=function(type,destructor){this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=prev-1;return prev===1}}var exceptionLast=0;var uncaughtExceptionCount=0;function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw ptr}function _gmtime_r(time,tmPtr){var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getUTCSeconds();HEAP32[tmPtr+4>>2]=date.getUTCMinutes();HEAP32[tmPtr+8>>2]=date.getUTCHours();HEAP32[tmPtr+12>>2]=date.getUTCDate();HEAP32[tmPtr+16>>2]=date.getUTCMonth();HEAP32[tmPtr+20>>2]=date.getUTCFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getUTCDay();HEAP32[tmPtr+36>>2]=0;HEAP32[tmPtr+32>>2]=0;var start=Date.UTC(date.getUTCFullYear(),0,1,0,0,0,0);var yday=(date.getTime()-start)/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;if(!_gmtime_r.GMTString)_gmtime_r.GMTString=allocateUTF8("GMT");HEAP32[tmPtr+40>>2]=_gmtime_r.GMTString;return tmPtr}function ___gmtime_r(a0,a1){return _gmtime_r(a0,a1)}function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};function getRandomDevice(){if(typeof crypto==="object"&&typeof crypto["getRandomValues"]==="function"){var randomBuffer=new Uint8Array(1);return function(){crypto.getRandomValues(randomBuffer);return randomBuffer[0]}}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");return function(){return crypto_module["randomBytes"](1)[0]}}catch(e){}}return function(){abort("randomDevice")}}var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};function mmapAlloc(size){var alignedSize=alignMemory(size,16384);var ptr=_malloc(alignedSize);while(size=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"r+":2,"w":577,"w+":578,"a":1089,"a+":1090},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){FS.forceLoadFile(node);return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){FS.forceLoadFile(node);var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___sys_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}var tupleRegistrations={};function runDestructors(destructors){while(destructors.length){var ptr=destructors.pop();var del=destructors.pop();del(ptr)}}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return new Function("body","return function "+name+"() {\n"+' "use strict";'+" return body.apply(this, arguments);\n"+"};\n")(body)}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i0?", ":"")+argsListWired}invokerFnBody+=(returns?"var rv = ":"")+"invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n";if(needsDestructorStack){invokerFnBody+="runDestructors(destructors);\n"}else{for(var i=isClassMethodFunc?1:2;i>2)+i])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_value_array(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){tupleRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),elements:[]}}function __embind_register_value_array_element(rawTupleType,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){tupleRegistrations[rawTupleType].elements.push({getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_value_object(rawType,name,constructorSignature,rawConstructor,destructorSignature,rawDestructor){structRegistrations[rawType]={name:readLatin1String(name),rawConstructor:embind__requireFunction(constructorSignature,rawConstructor),rawDestructor:embind__requireFunction(destructorSignature,rawDestructor),fields:[]}}function __embind_register_value_object_field(structType,fieldName,getterReturnType,getterSignature,getter,getterContext,setterArgumentType,setterSignature,setter,setterContext){structRegistrations[structType].fields.push({fieldName:readLatin1String(fieldName),getterReturnType:getterReturnType,getter:embind__requireFunction(getterSignature,getter),getterContext:getterContext,setterArgumentType:setterArgumentType,setter:embind__requireFunction(setterSignature,setter),setterContext:setterContext})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function requireHandle(handle){if(!handle){throwBindingError("Cannot use deleted val. handle = "+handle)}return emval_handle_array[handle].value}function __emval_as(handle,returnType,destructorsRef){handle=requireHandle(handle);returnType=requireRegisteredType(returnType,"emval::as");var destructors=[];var rd=__emval_register(destructors);HEAP32[destructorsRef>>2]=rd;return returnType["toWireType"](destructors,handle)}function __emval_allocateDestructors(destructorsRef){var destructors=[];HEAP32[destructorsRef>>2]=__emval_register(destructors);return destructors}var emval_symbols={};function getStringOrSymbol(address){var symbol=emval_symbols[address];if(symbol===undefined){return readLatin1String(address)}else{return symbol}}var emval_methodCallers=[];function __emval_call_method(caller,handle,methodName,destructorsRef,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);return caller(handle,methodName,__emval_allocateDestructors(destructorsRef),args)}function __emval_call_void_method(caller,handle,methodName,args){caller=emval_methodCallers[caller];handle=requireHandle(handle);methodName=getStringOrSymbol(methodName);caller(handle,methodName,null,args)}function __emval_equals(first,second){first=requireHandle(first);second=requireHandle(second);return first==second}function emval_get_global(){if(typeof globalThis==="object"){return globalThis}return function(){return Function}()("return this")()}function __emval_get_global(name){if(name===0){return __emval_register(emval_get_global())}else{name=getStringOrSymbol(name);return __emval_register(emval_get_global()[name])}}function __emval_addMethodCaller(caller){var id=emval_methodCallers.length;emval_methodCallers.push(caller);return id}function __emval_lookupTypes(argCount,argTypes){var a=new Array(argCount);for(var i=0;i>2)+i],"parameter "+i)}return a}function __emval_get_method_caller(argCount,argTypes){var types=__emval_lookupTypes(argCount,argTypes);var retType=types[0];var signatureName=retType.name+"_$"+types.slice(1).map(function(t){return t.name}).join("_")+"$";var params=["retType"];var args=[retType];var argsList="";for(var i=0;i4){emval_handle_array[handle].refcount+=1}}function __emval_instanceof(object,constructor){object=requireHandle(object);constructor=requireHandle(constructor);return object instanceof constructor}function __emval_is_number(handle){handle=requireHandle(handle);return typeof handle==="number"}function __emval_is_string(handle){handle=requireHandle(handle);return typeof handle==="string"}function craftEmvalAllocator(argCount){var argsList="";for(var i=0;i>> 2) + "+i+'], "parameter '+i+'");\n'+"var arg"+i+" = argType"+i+".readValueFromPointer(args);\n"+"args += argType"+i+"['argPackAdvance'];\n"}functionBody+="var obj = new constructor("+argsList+");\n"+"return __emval_register(obj);\n"+"}\n";return new Function("requireRegisteredType","Module","__emval_register",functionBody)(requireRegisteredType,Module,__emval_register)}var emval_newers={};function __emval_new(handle,argCount,argTypes,args){handle=requireHandle(handle);var newer=emval_newers[argCount];if(!newer){newer=craftEmvalAllocator(argCount);emval_newers[argCount]=newer}return newer(handle,argTypes,args)}function __emval_new_array(){return __emval_register([])}function __emval_new_cstring(v){return __emval_register(getStringOrSymbol(v))}function __emval_new_object(){return __emval_register({})}function __emval_run_destructors(handle){var destructors=emval_handle_array[handle].value;runDestructors(destructors);__emval_decref(handle)}function __emval_set_property(handle,key,value){handle=requireHandle(handle);key=requireHandle(key);value=requireHandle(value);handle[key]=value}function __emval_take_value(type,argv){type=requireRegisteredType(type,"_emval_take_value");var v=type["readValueFromPointer"](argv);return __emval_register(v)}function _abort(){abort()}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance.now()};function _emscripten_thread_sleep(msecs){var start=_emscripten_get_now();while(_emscripten_get_now()-start>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?(Math.min(+Math.floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _setTempRet0($i){setTempRet0($i|0)}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _uuid_generate(out){var uuid=null;if(ENVIRONMENT_IS_NODE){try{var rb=require("crypto")["randomBytes"];uuid=rb(16)}catch(e){}}else if(ENVIRONMENT_IS_WEB&&typeof window.crypto!=="undefined"&&typeof window.crypto.getRandomValues!=="undefined"){uuid=new Uint8Array(16);window.crypto.getRandomValues(uuid)}if(!uuid){uuid=new Array(16);var d=(new Date).getTime();for(var i=0;i<16;i++){var r=(d+Math.random()*256)%256|0;d=d/256|0;uuid[i]=r}}uuid[6]=uuid[6]&15|64;uuid[8]=uuid[8]&127|128;writeArrayToMemory(uuid,out)}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();InternalError=Module["InternalError"]=extendError(Error,"InternalError");embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var ASSERTIONS=false;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"__cxa_allocate_exception":___cxa_allocate_exception,"__cxa_atexit":___cxa_atexit,"__cxa_throw":___cxa_throw,"__gmtime_r":___gmtime_r,"__sys_fcntl64":___sys_fcntl64,"__sys_fstat64":___sys_fstat64,"__sys_ioctl":___sys_ioctl,"__sys_open":___sys_open,"__sys_stat64":___sys_stat64,"_embind_finalize_value_array":__embind_finalize_value_array,"_embind_finalize_value_object":__embind_finalize_value_object,"_embind_register_bool":__embind_register_bool,"_embind_register_class":__embind_register_class,"_embind_register_class_class_function":__embind_register_class_class_function,"_embind_register_class_constructor":__embind_register_class_constructor,"_embind_register_class_function":__embind_register_class_function,"_embind_register_class_property":__embind_register_class_property,"_embind_register_emval":__embind_register_emval,"_embind_register_enum":__embind_register_enum,"_embind_register_enum_value":__embind_register_enum_value,"_embind_register_float":__embind_register_float,"_embind_register_integer":__embind_register_integer,"_embind_register_memory_view":__embind_register_memory_view,"_embind_register_std_string":__embind_register_std_string,"_embind_register_std_wstring":__embind_register_std_wstring,"_embind_register_value_array":__embind_register_value_array,"_embind_register_value_array_element":__embind_register_value_array_element,"_embind_register_value_object":__embind_register_value_object,"_embind_register_value_object_field":__embind_register_value_object_field,"_embind_register_void":__embind_register_void,"_emval_as":__emval_as,"_emval_call_method":__emval_call_method,"_emval_call_void_method":__emval_call_void_method,"_emval_decref":__emval_decref,"_emval_equals":__emval_equals,"_emval_get_global":__emval_get_global,"_emval_get_method_caller":__emval_get_method_caller,"_emval_get_module_property":__emval_get_module_property,"_emval_get_property":__emval_get_property,"_emval_incref":__emval_incref,"_emval_instanceof":__emval_instanceof,"_emval_is_number":__emval_is_number,"_emval_is_string":__emval_is_string,"_emval_new":__emval_new,"_emval_new_array":__emval_new_array,"_emval_new_cstring":__emval_new_cstring,"_emval_new_object":__emval_new_object,"_emval_run_destructors":__emval_run_destructors,"_emval_set_property":__emval_set_property,"_emval_take_value":__emval_take_value,"abort":_abort,"emscripten_memcpy_big":_emscripten_memcpy_big,"emscripten_resize_heap":_emscripten_resize_heap,"emscripten_thread_sleep":_emscripten_thread_sleep,"environ_get":_environ_get,"environ_sizes_get":_environ_sizes_get,"fd_close":_fd_close,"fd_fdstat_get":_fd_fdstat_get,"fd_read":_fd_read,"fd_seek":_fd_seek,"fd_write":_fd_write,"setTempRet0":_setTempRet0,"time":_time,"uuid_generate":_uuid_generate};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["__wasm_call_ctors"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["malloc"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["free"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["__getTypeName"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["__embind_register_native_and_builtin_types"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["__errno_location"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["stackSave"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["stackRestore"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["stackAlloc"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["setThrew"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["dynCall_ji"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["dynCall_jiji"]).apply(null,arguments)};var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run(); + + + return rhino3dm.ready +} +); +})(); +export default rhino3dm; \ No newline at end of file diff --git a/jsm/libs/rhino3dm/rhino3dm.wasm b/jsm/libs/rhino3dm/rhino3dm.wasm new file mode 100644 index 0000000..fbbf481 Binary files /dev/null and b/jsm/libs/rhino3dm/rhino3dm.wasm differ diff --git a/jsm/libs/stats.module.js b/jsm/libs/stats.module.js new file mode 100644 index 0000000..0d372ba --- /dev/null +++ b/jsm/libs/stats.module.js @@ -0,0 +1,167 @@ +var Stats = function () { + + var mode = 0; + + var container = document.createElement( 'div' ); + container.style.cssText = 'position:fixed;top:0;left:0;cursor:pointer;opacity:0.9;z-index:10000'; + container.addEventListener( 'click', function ( event ) { + + event.preventDefault(); + showPanel( ++ mode % container.children.length ); + + }, false ); + + // + + function addPanel( panel ) { + + container.appendChild( panel.dom ); + return panel; + + } + + function showPanel( id ) { + + for ( var i = 0; i < container.children.length; i ++ ) { + + container.children[ i ].style.display = i === id ? 'block' : 'none'; + + } + + mode = id; + + } + + // + + var beginTime = ( performance || Date ).now(), prevTime = beginTime, frames = 0; + + var fpsPanel = addPanel( new Stats.Panel( 'FPS', '#0ff', '#002' ) ); + var msPanel = addPanel( new Stats.Panel( 'MS', '#0f0', '#020' ) ); + + if ( self.performance && self.performance.memory ) { + + var memPanel = addPanel( new Stats.Panel( 'MB', '#f08', '#201' ) ); + + } + + showPanel( 0 ); + + return { + + REVISION: 16, + + dom: container, + + addPanel: addPanel, + showPanel: showPanel, + + begin: function () { + + beginTime = ( performance || Date ).now(); + + }, + + end: function () { + + frames ++; + + var time = ( performance || Date ).now(); + + msPanel.update( time - beginTime, 200 ); + + if ( time >= prevTime + 1000 ) { + + fpsPanel.update( ( frames * 1000 ) / ( time - prevTime ), 100 ); + + prevTime = time; + frames = 0; + + if ( memPanel ) { + + var memory = performance.memory; + memPanel.update( memory.usedJSHeapSize / 1048576, memory.jsHeapSizeLimit / 1048576 ); + + } + + } + + return time; + + }, + + update: function () { + + beginTime = this.end(); + + }, + + // Backwards Compatibility + + domElement: container, + setMode: showPanel + + }; + +}; + +Stats.Panel = function ( name, fg, bg ) { + + var min = Infinity, max = 0, round = Math.round; + var PR = round( window.devicePixelRatio || 1 ); + + var WIDTH = 80 * PR, HEIGHT = 48 * PR, + TEXT_X = 3 * PR, TEXT_Y = 2 * PR, + GRAPH_X = 3 * PR, GRAPH_Y = 15 * PR, + GRAPH_WIDTH = 74 * PR, GRAPH_HEIGHT = 30 * PR; + + var canvas = document.createElement( 'canvas' ); + canvas.width = WIDTH; + canvas.height = HEIGHT; + canvas.style.cssText = 'width:80px;height:48px'; + + var context = canvas.getContext( '2d' ); + context.font = 'bold ' + ( 9 * PR ) + 'px Helvetica,Arial,sans-serif'; + context.textBaseline = 'top'; + + context.fillStyle = bg; + context.fillRect( 0, 0, WIDTH, HEIGHT ); + + context.fillStyle = fg; + context.fillText( name, TEXT_X, TEXT_Y ); + context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT ); + + context.fillStyle = bg; + context.globalAlpha = 0.9; + context.fillRect( GRAPH_X, GRAPH_Y, GRAPH_WIDTH, GRAPH_HEIGHT ); + + return { + + dom: canvas, + + update: function ( value, maxValue ) { + + min = Math.min( min, value ); + max = Math.max( max, value ); + + context.fillStyle = bg; + context.globalAlpha = 1; + context.fillRect( 0, 0, WIDTH, GRAPH_Y ); + context.fillStyle = fg; + context.fillText( round( value ) + ' ' + name + ' (' + round( min ) + '-' + round( max ) + ')', TEXT_X, TEXT_Y ); + + context.drawImage( canvas, GRAPH_X + PR, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT, GRAPH_X, GRAPH_Y, GRAPH_WIDTH - PR, GRAPH_HEIGHT ); + + context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, GRAPH_HEIGHT ); + + context.fillStyle = bg; + context.globalAlpha = 0.9; + context.fillRect( GRAPH_X + GRAPH_WIDTH - PR, GRAPH_Y, PR, round( ( 1 - ( value / maxValue ) ) * GRAPH_HEIGHT ) ); + + } + + }; + +}; + +export default Stats; diff --git a/jsm/libs/tween.module.min.js b/jsm/libs/tween.module.min.js new file mode 100644 index 0000000..c29d8f5 --- /dev/null +++ b/jsm/libs/tween.module.min.js @@ -0,0 +1,3 @@ +// tween.js 17.3.5 - https://github.com/tweenjs/tween.js +var _Group=function(){this._tweens={},this._tweensAddedDuringUpdate={}};_Group.prototype={getAll:function(){return Object.keys(this._tweens).map(function(t){return this._tweens[t]}.bind(this))},removeAll:function(){this._tweens={}},add:function(t){this._tweens[t.getId()]=t,this._tweensAddedDuringUpdate[t.getId()]=t},remove:function(t){delete this._tweens[t.getId()],delete this._tweensAddedDuringUpdate[t.getId()]},update:function(t,n){var e=Object.keys(this._tweens);if(0===e.length)return!1;for(t=void 0!==t?t:TWEEN.now();0, + * linewidth: , + * dashed: , + * dashScale: , + * dashSize: , + * dashOffset: , + * gapSize: , + * resolution: , // to be set by renderer + * } + */ + +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; + + attribute vec3 instanceStart; + attribute vec3 instanceEnd; + + attribute vec3 instanceColorStart; + attribute vec3 instanceColorEnd; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + + varying vec2 vUv; + + #endif + + #else + + varying vec2 vUv; + + #endif + + #ifdef USE_DASH + + uniform float dashScale; + attribute float instanceDistanceStart; + attribute float instanceDistanceEnd; + varying float vLineDistance; + + #endif + + void trimSegment( const in vec4 start, inout vec4 end ) { + + // trim end segment so it terminates between the camera plane and the near plane + + // 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; + + float alpha = ( nearEstimate - start.z ) / ( end.z - start.z ); + + end.xyz = mix( start.xyz, end.xyz, alpha ); + + } + + void main() { + + #ifdef USE_COLOR + + vColor.xyz = ( position.y < 0.5 ) ? instanceColorStart : instanceColorEnd; + + #endif + + #ifdef USE_DASH + + vLineDistance = ( position.y < 0.5 ) ? dashScale * instanceDistanceStart : dashScale * instanceDistanceEnd; + vUv = uv; + + #endif + + float aspect = resolution.x / resolution.y; + + // camera space + vec4 start = modelViewMatrix * vec4( instanceStart, 1.0 ); + vec4 end = modelViewMatrix * vec4( instanceEnd, 1.0 ); + + #ifdef WORLD_UNITS + + worldStart = start.xyz; + worldEnd = end.xyz; + + #else + + vUv = uv; + + #endif + + // 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 + + bool perspective = ( projectionMatrix[ 2 ][ 3 ] == - 1.0 ); // 4th entry in the 3rd column + + if ( perspective ) { + + if ( start.z < 0.0 && end.z >= 0.0 ) { + + trimSegment( start, end ); + + } else if ( end.z < 0.0 && start.z >= 0.0 ) { + + trimSegment( end, start ); + + } + + } + + // clip space + vec4 clipStart = projectionMatrix * start; + vec4 clipEnd = projectionMatrix * end; + + // ndc space + vec3 ndcStart = clipStart.xyz / clipStart.w; + vec3 ndcEnd = clipEnd.xyz / clipEnd.w; + + // direction + vec2 dir = ndcEnd.xy - ndcStart.xy; + + // account for clip-space aspect ratio + dir.x *= aspect; + dir = normalize( dir ); + + #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 ) { + + offset = normalize( cross( start.xyz, worldDir ) ); + + } else { + + offset = normalize( cross( end.xyz, worldDir ) ); + + } + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + float forwardOffset = dot( worldDir, vec3( 0.0, 0.0, 1.0 ) ); + + // don't extend the line if we're rendering dashes because we + // won't be rendering the endcaps + #ifndef USE_DASH + + // 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; + + #endif + + // endcaps + if ( position.y > 1.0 || position.y < 0.0 ) { + + offset.xy += dir * 2.0 * forwardOffset; + + } + + // adjust for linewidth + offset *= linewidth * 0.5; + + // set the world position + worldPos = ( position.y < 0.5 ) ? start : end; + worldPos.xyz += offset; + + // project the worldpos + vec4 clip = projectionMatrix * worldPos; + + // 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 + + vec2 offset = vec2( dir.y, - dir.x ); + // undo aspect ratio adjustment + dir.x /= aspect; + offset.x /= aspect; + + // sign flip + if ( position.x < 0.0 ) offset *= - 1.0; + + // endcaps + if ( position.y < 0.0 ) { + + offset += - dir; + + } else if ( position.y > 1.0 ) { + + offset += dir; + + } + + // adjust for linewidth + offset *= linewidth; + + // adjust for clip-space to screen-space conversion // maybe resolution should be based on viewport ... + offset /= resolution.y; + + // select end + vec4 clip = ( position.y < 0.5 ) ? clipStart : clipEnd; + + // back to clip space + offset *= clip.w; + + clip.xy += offset; + + #endif + + gl_Position = clip; + + vec4 mvPosition = ( position.y < 0.5 ) ? start : end; // this is an approximation + + #include + #include + #include + + } + `, + + fragmentShader: + /* glsl */` + uniform vec3 diffuse; + uniform float opacity; + uniform float linewidth; + + #ifdef USE_DASH + + uniform float dashOffset; + uniform float dashSize; + uniform float gapSize; + + #endif + + varying float vLineDistance; + + #ifdef WORLD_UNITS + + varying vec4 worldPos; + varying vec3 worldStart; + varying vec3 worldEnd; + + #ifdef USE_DASH + + varying vec2 vUv; + + #endif + + #else + + varying vec2 vUv; + + #endif + + #include + #include + #include + #include + #include + + vec2 closestLineToLine(vec3 p1, vec3 p2, vec3 p3, vec3 p4) { + + float mua; + float mub; + + vec3 p13 = p1 - p3; + vec3 p43 = p4 - p3; + + vec3 p21 = p2 - p1; + + 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; + + float numer = d1343 * d4321 - d1321 * d4343; + + mua = numer / denom; + mua = clamp( mua, 0.0, 1.0 ); + mub = ( d1343 + d4321 * ( mua ) ) / d4343; + mub = clamp( mub, 0.0, 1.0 ); + + return vec2( mua, mub ); + + } + + void main() { + + #include + + #ifdef USE_DASH + + if ( vUv.y < - 1.0 || vUv.y > 1.0 ) discard; // discard endcaps + + if ( mod( vLineDistance + dashOffset, dashSize + gapSize ) > dashSize ) discard; // todo - FIX + + #endif + + float alpha = opacity; + + #ifdef WORLD_UNITS + + // 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 ); + + vec3 p1 = worldStart + lineDir * params.x; + vec3 p2 = rayEnd * params.y; + vec3 delta = p1 - p2; + float len = length( delta ); + float norm = len / linewidth; + + #ifndef USE_DASH + + #ifdef USE_ALPHA_TO_COVERAGE + + float dnorm = fwidth( norm ); + alpha = 1.0 - smoothstep( 0.5 - dnorm, 0.5 + dnorm, norm ); + + #else + + if ( norm > 0.5 ) { + + discard; + + } + + #endif + + #endif + + #else + + #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 ); + + if ( abs( vUv.y ) > 1.0 ) { + + alpha = 1.0 - smoothstep( 1.0 - dlen, 1.0 + dlen, len2 ); + + } + + #else + + 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; + + if ( len2 > 1.0 ) discard; + + } + + #endif + + #endif + + vec4 diffuseColor = vec4( diffuse, alpha ); + + #include + #include + + gl_FragColor = vec4( diffuseColor.rgb, alpha ); + + #include + #include + #include + #include + + } + ` +}; + +class LineMaterial extends ShaderMaterial { + + constructor( parameters ) { + + super( { + + type: 'LineMaterial', + + uniforms: UniformsUtils.clone( ShaderLib[ 'line' ].uniforms ), + + vertexShader: ShaderLib[ 'line' ].vertexShader, + fragmentShader: ShaderLib[ 'line' ].fragmentShader, + + clipping: true // required for clipping support + + } ); + + Object.defineProperties( this, { + + color: { + + enumerable: true, + + get: function () { + + return this.uniforms.diffuse.value; + + }, + + set: function ( value ) { + + this.uniforms.diffuse.value = value; + + } + + }, + + worldUnits: { + + enumerable: true, + + get: function () { + + return 'WORLD_UNITS' in this.defines; + + }, + + set: function ( value ) { + + if ( value === true ) { + + this.defines.WORLD_UNITS = ''; + + } else { + + delete this.defines.WORLD_UNITS; + + } + + } + + }, + + linewidth: { + + enumerable: true, + + get: function () { + + return this.uniforms.linewidth.value; + + }, + + set: function ( value ) { + + this.uniforms.linewidth.value = value; + + } + + }, + + dashed: { + + enumerable: true, + + get: function () { + + return Boolean( 'USE_DASH' in this.defines ); + + }, + + set( value ) { + + if ( Boolean( value ) !== Boolean( 'USE_DASH' in this.defines ) ) { + + this.needsUpdate = true; + + } + + if ( value === true ) { + + this.defines.USE_DASH = ''; + + } else { + + delete this.defines.USE_DASH; + + } + + } + + }, + + dashScale: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashScale.value; + + }, + + set: function ( value ) { + + this.uniforms.dashScale.value = value; + + } + + }, + + dashSize: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashSize.value; + + }, + + set: function ( value ) { + + this.uniforms.dashSize.value = value; + + } + + }, + + dashOffset: { + + enumerable: true, + + get: function () { + + return this.uniforms.dashOffset.value; + + }, + + set: function ( value ) { + + this.uniforms.dashOffset.value = value; + + } + + }, + + gapSize: { + + enumerable: true, + + get: function () { + + return this.uniforms.gapSize.value; + + }, + + set: function ( value ) { + + this.uniforms.gapSize.value = value; + + } + + }, + + opacity: { + + enumerable: true, + + get: function () { + + return this.uniforms.opacity.value; + + }, + + set: function ( value ) { + + this.uniforms.opacity.value = value; + + } + + }, + + resolution: { + + enumerable: true, + + get: function () { + + return this.uniforms.resolution.value; + + }, + + set: function ( value ) { + + this.uniforms.resolution.value.copy( value ); + + } + + }, + + alphaToCoverage: { + + enumerable: true, + + get: function () { + + return Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ); + + }, + + set: function ( value ) { + + if ( Boolean( value ) !== Boolean( 'USE_ALPHA_TO_COVERAGE' in this.defines ) ) { + + this.needsUpdate = true; + + } + + if ( value === true ) { + + this.defines.USE_ALPHA_TO_COVERAGE = ''; + this.extensions.derivatives = true; + + } else { + + delete this.defines.USE_ALPHA_TO_COVERAGE; + this.extensions.derivatives = false; + + } + + } + + } + + } ); + + this.setValues( parameters ); + + } + +} + +LineMaterial.prototype.isLineMaterial = true; + +export { LineMaterial }; diff --git a/jsm/lines/LineSegments2.js b/jsm/lines/LineSegments2.js new file mode 100644 index 0000000..137745c --- /dev/null +++ b/jsm/lines/LineSegments2.js @@ -0,0 +1,355 @@ +import { + Box3, + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Line3, + MathUtils, + Matrix4, + Mesh, + Sphere, + Vector3, + Vector4 +} from 'three'; +import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; +import { LineMaterial } from '../lines/LineMaterial.js'; + +const _start = new Vector3(); +const _end = new Vector3(); + +const _start4 = new Vector4(); +const _end4 = new Vector4(); + +const _ssOrigin = new Vector4(); +const _ssOrigin3 = new Vector3(); +const _mvMatrix = new Matrix4(); +const _line = new Line3(); +const _closestPoint = new Vector3(); + +const _box = new Box3(); +const _sphere = new Sphere(); +const _clipToWorldVector = new Vector4(); + +let _ray, _instanceStart, _instanceEnd, _lineWidth; + +// Returns the margin required to expand by in world space given the distance from the camera, +// line width, resolution, and camera projection +function getWorldSpaceHalfWidth( camera, distance, resolution ) { + + // transform into clip space, adjust the x and y values by the pixel width offset, then + // transform back into world space to get world offset. Note clip space is [-1, 1] so full + // width does not need to be halved. + _clipToWorldVector.set( 0, 0, - distance, 1.0 ).applyMatrix4( camera.projectionMatrix ); + _clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w ); + _clipToWorldVector.x = _lineWidth / resolution.width; + _clipToWorldVector.y = _lineWidth / resolution.height; + _clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse ); + _clipToWorldVector.multiplyScalar( 1.0 / _clipToWorldVector.w ); + + return Math.abs( Math.max( _clipToWorldVector.x, _clipToWorldVector.y ) ); + +} + +function raycastWorldUnits( lineSegments, intersects ) { + + for ( let i = 0, l = _instanceStart.count; i < l; i ++ ) { + + _line.start.fromBufferAttribute( _instanceStart, i ); + _line.end.fromBufferAttribute( _instanceEnd, i ); + + const pointOnLine = new Vector3(); + const point = new Vector3(); + + _ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine ); + const isInside = point.distanceTo( pointOnLine ) < _lineWidth * 0.5; + + if ( isInside ) { + + intersects.push( { + point, + pointOnLine, + distance: _ray.origin.distanceTo( point ), + object: lineSegments, + face: null, + faceIndex: i, + uv: null, + uv2: null, + } ); + + } + + } + +} + +function raycastScreenSpace( lineSegments, camera, intersects ) { + + const projectionMatrix = camera.projectionMatrix; + const material = lineSegments.material; + const resolution = material.resolution; + const matrixWorld = lineSegments.matrixWorld; + + const geometry = lineSegments.geometry; + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + + const near = - camera.near; + + // + + // pick a point 1 unit out along the ray to avoid the ray origin + // sitting at the camera origin which will cause "w" to be 0 when + // applying the projection matrix. + _ray.at( 1, _ssOrigin ); + + // ndc space [ - 1.0, 1.0 ] + _ssOrigin.w = 1; + _ssOrigin.applyMatrix4( camera.matrixWorldInverse ); + _ssOrigin.applyMatrix4( projectionMatrix ); + _ssOrigin.multiplyScalar( 1 / _ssOrigin.w ); + + // screen space + _ssOrigin.x *= resolution.x / 2; + _ssOrigin.y *= resolution.y / 2; + _ssOrigin.z = 0; + + _ssOrigin3.copy( _ssOrigin ); + + _mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld ); + + for ( let i = 0, l = instanceStart.count; i < l; i ++ ) { + + _start4.fromBufferAttribute( instanceStart, i ); + _end4.fromBufferAttribute( instanceEnd, i ); + + _start4.w = 1; + _end4.w = 1; + + // camera space + _start4.applyMatrix4( _mvMatrix ); + _end4.applyMatrix4( _mvMatrix ); + + // skip the segment if it's entirely behind the camera + const isBehindCameraNear = _start4.z > near && _end4.z > near; + if ( isBehindCameraNear ) { + + continue; + + } + + // trim the segment if it extends behind camera near + if ( _start4.z > near ) { + + const deltaDist = _start4.z - _end4.z; + const t = ( _start4.z - near ) / deltaDist; + _start4.lerp( _end4, t ); + + } else if ( _end4.z > near ) { + + const deltaDist = _end4.z - _start4.z; + const t = ( _end4.z - near ) / deltaDist; + _end4.lerp( _start4, t ); + + } + + // clip space + _start4.applyMatrix4( projectionMatrix ); + _end4.applyMatrix4( projectionMatrix ); + + // ndc space [ - 1.0, 1.0 ] + _start4.multiplyScalar( 1 / _start4.w ); + _end4.multiplyScalar( 1 / _end4.w ); + + // screen space + _start4.x *= resolution.x / 2; + _start4.y *= resolution.y / 2; + + _end4.x *= resolution.x / 2; + _end4.y *= resolution.y / 2; + + // create 2d segment + _line.start.copy( _start4 ); + _line.start.z = 0; + + _line.end.copy( _end4 ); + _line.end.z = 0; + + // get closest point on ray to segment + const param = _line.closestPointToPointParameter( _ssOrigin3, true ); + _line.at( param, _closestPoint ); + + // check if the intersection point is within clip space + const zPos = MathUtils.lerp( _start4.z, _end4.z, param ); + const isInClipSpace = zPos >= - 1 && zPos <= 1; + + const isInside = _ssOrigin3.distanceTo( _closestPoint ) < _lineWidth * 0.5; + + if ( isInClipSpace && isInside ) { + + _line.start.fromBufferAttribute( instanceStart, i ); + _line.end.fromBufferAttribute( instanceEnd, i ); + + _line.start.applyMatrix4( matrixWorld ); + _line.end.applyMatrix4( matrixWorld ); + + const pointOnLine = new Vector3(); + const point = new Vector3(); + + _ray.distanceSqToSegment( _line.start, _line.end, point, pointOnLine ); + + intersects.push( { + point: point, + pointOnLine: pointOnLine, + distance: _ray.origin.distanceTo( point ), + object: lineSegments, + face: null, + faceIndex: i, + uv: null, + uv2: null, + } ); + + } + + } + +} + +class LineSegments2 extends Mesh { + + constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) { + + super( geometry, material ); + + this.type = 'LineSegments2'; + + } + + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + + computeLineDistances() { + + const geometry = this.geometry; + + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const lineDistances = new Float32Array( 2 * instanceStart.count ); + + for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) { + + _start.fromBufferAttribute( instanceStart, i ); + _end.fromBufferAttribute( instanceEnd, i ); + + lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ]; + lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end ); + + } + + const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1 + + geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0 + geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1 + + return this; + + } + + raycast( raycaster, intersects ) { + + const worldUnits = this.material.worldUnits; + const camera = raycaster.camera; + + if ( camera === null && ! worldUnits ) { + + console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2 while worldUnits is set to false.' ); + + } + + const threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0; + + _ray = raycaster.ray; + + const matrixWorld = this.matrixWorld; + const geometry = this.geometry; + const material = this.material; + + _lineWidth = material.linewidth + threshold; + + _instanceStart = geometry.attributes.instanceStart; + _instanceEnd = geometry.attributes.instanceEnd; + + // check if we intersect the sphere bounds + if ( geometry.boundingSphere === null ) { + + geometry.computeBoundingSphere(); + + } + + _sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld ); + + // increase the sphere bounds by the worst case line screen space width + let sphereMargin; + if ( worldUnits ) { + + sphereMargin = _lineWidth * 0.5; + + } else { + + const distanceToSphere = Math.max( camera.near, _sphere.distanceToPoint( _ray.origin ) ); + sphereMargin = getWorldSpaceHalfWidth( camera, distanceToSphere, material.resolution ); + + } + + _sphere.radius += sphereMargin; + + if ( _ray.intersectsSphere( _sphere ) === false ) { + + return; + + } + + // check if we intersect the box bounds + if ( geometry.boundingBox === null ) { + + geometry.computeBoundingBox(); + + } + + _box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld ); + + // increase the box bounds by the worst case line width + let boxMargin; + if ( worldUnits ) { + + boxMargin = _lineWidth * 0.5; + + } else { + + const distanceToBox = Math.max( camera.near, _box.distanceToPoint( _ray.origin ) ); + boxMargin = getWorldSpaceHalfWidth( camera, distanceToBox, material.resolution ); + + } + + _box.expandByScalar( boxMargin ); + + if ( _ray.intersectsBox( _box ) === false ) { + + return; + + } + + if ( worldUnits ) { + + raycastWorldUnits( this, intersects ); + + } else { + + raycastScreenSpace( this, camera, intersects ); + + } + + } + +} + +LineSegments2.prototype.isLineSegments2 = true; + +export { LineSegments2 }; diff --git a/jsm/lines/LineSegmentsGeometry.js b/jsm/lines/LineSegmentsGeometry.js new file mode 100644 index 0000000..bee5360 --- /dev/null +++ b/jsm/lines/LineSegmentsGeometry.js @@ -0,0 +1,250 @@ +import { + Box3, + Float32BufferAttribute, + InstancedBufferGeometry, + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Sphere, + Vector3, + WireframeGeometry +} from 'three'; + +const _box = new Box3(); +const _vector = new Vector3(); + +class LineSegmentsGeometry extends InstancedBufferGeometry { + + constructor() { + + super(); + + this.type = 'LineSegmentsGeometry'; + + const positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ]; + const uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ]; + const index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ]; + + this.setIndex( index ); + this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + } + + applyMatrix4( matrix ) { + + const start = this.attributes.instanceStart; + const end = this.attributes.instanceEnd; + + if ( start !== undefined ) { + + start.applyMatrix4( matrix ); + + end.applyMatrix4( matrix ); + + start.needsUpdate = true; + + } + + if ( this.boundingBox !== null ) { + + this.computeBoundingBox(); + + } + + if ( this.boundingSphere !== null ) { + + this.computeBoundingSphere(); + + } + + return this; + + } + + setPositions( array ) { + + let lineSegments; + + if ( array instanceof Float32Array ) { + + lineSegments = array; + + } else if ( Array.isArray( array ) ) { + + lineSegments = new Float32Array( array ); + + } + + const instanceBuffer = new InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz + + this.setAttribute( 'instanceStart', new InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz + this.setAttribute( 'instanceEnd', new InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz + + // + + this.computeBoundingBox(); + this.computeBoundingSphere(); + + return this; + + } + + setColors( array ) { + + let colors; + + if ( array instanceof Float32Array ) { + + colors = array; + + } else if ( Array.isArray( array ) ) { + + colors = new Float32Array( array ); + + } + + const instanceColorBuffer = new InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb + + this.setAttribute( 'instanceColorStart', new InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb + this.setAttribute( 'instanceColorEnd', new InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb + + return this; + + } + + fromWireframeGeometry( geometry ) { + + this.setPositions( geometry.attributes.position.array ); + + return this; + + } + + fromEdgesGeometry( geometry ) { + + this.setPositions( geometry.attributes.position.array ); + + return this; + + } + + fromMesh( mesh ) { + + this.fromWireframeGeometry( new WireframeGeometry( mesh.geometry ) ); + + // set colors, maybe + + return this; + + } + + fromLineSegments( lineSegments ) { + + const geometry = lineSegments.geometry; + + if ( geometry.isGeometry ) { + + console.error( 'THREE.LineSegmentsGeometry no longer supports Geometry. Use THREE.BufferGeometry instead.' ); + return; + + } else if ( geometry.isBufferGeometry ) { + + this.setPositions( geometry.attributes.position.array ); // assumes non-indexed + + } + + // set colors, maybe + + return this; + + } + + computeBoundingBox() { + + if ( this.boundingBox === null ) { + + this.boundingBox = new Box3(); + + } + + const start = this.attributes.instanceStart; + const end = this.attributes.instanceEnd; + + if ( start !== undefined && end !== undefined ) { + + this.boundingBox.setFromBufferAttribute( start ); + + _box.setFromBufferAttribute( end ); + + this.boundingBox.union( _box ); + + } + + } + + computeBoundingSphere() { + + if ( this.boundingSphere === null ) { + + this.boundingSphere = new Sphere(); + + } + + if ( this.boundingBox === null ) { + + this.computeBoundingBox(); + + } + + const start = this.attributes.instanceStart; + const end = this.attributes.instanceEnd; + + if ( start !== undefined && end !== undefined ) { + + const center = this.boundingSphere.center; + + this.boundingBox.getCenter( center ); + + let maxRadiusSq = 0; + + for ( let i = 0, il = start.count; i < il; i ++ ) { + + _vector.fromBufferAttribute( start, i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) ); + + _vector.fromBufferAttribute( end, i ); + maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) ); + + } + + this.boundingSphere.radius = Math.sqrt( maxRadiusSq ); + + if ( isNaN( this.boundingSphere.radius ) ) { + + console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this ); + + } + + } + + } + + toJSON() { + + // todo + + } + + applyMatrix( matrix ) { + + console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' ); + + return this.applyMatrix4( matrix ); + + } + +} + +LineSegmentsGeometry.prototype.isLineSegmentsGeometry = true; + +export { LineSegmentsGeometry }; diff --git a/jsm/lines/Wireframe.js b/jsm/lines/Wireframe.js new file mode 100644 index 0000000..91f8218 --- /dev/null +++ b/jsm/lines/Wireframe.js @@ -0,0 +1,56 @@ +import { + InstancedInterleavedBuffer, + InterleavedBufferAttribute, + Mesh, + Vector3 +} from 'three'; +import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; +import { LineMaterial } from '../lines/LineMaterial.js'; + +const _start = new Vector3(); +const _end = new Vector3(); + +class Wireframe extends Mesh { + + constructor( geometry = new LineSegmentsGeometry(), material = new LineMaterial( { color: Math.random() * 0xffffff } ) ) { + + super( geometry, material ); + + this.type = 'Wireframe'; + + } + + // for backwards-compatibility, but could be a method of LineSegmentsGeometry... + + computeLineDistances() { + + const geometry = this.geometry; + + const instanceStart = geometry.attributes.instanceStart; + const instanceEnd = geometry.attributes.instanceEnd; + const lineDistances = new Float32Array( 2 * instanceStart.count ); + + for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) { + + _start.fromBufferAttribute( instanceStart, i ); + _end.fromBufferAttribute( instanceEnd, i ); + + lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ]; + lineDistances[ j + 1 ] = lineDistances[ j ] + _start.distanceTo( _end ); + + } + + const instanceDistanceBuffer = new InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1 + + geometry.setAttribute( 'instanceDistanceStart', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0 + geometry.setAttribute( 'instanceDistanceEnd', new InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1 + + return this; + + } + +} + +Wireframe.prototype.isWireframe = true; + +export { Wireframe }; diff --git a/jsm/lines/WireframeGeometry2.js b/jsm/lines/WireframeGeometry2.js new file mode 100644 index 0000000..57df177 --- /dev/null +++ b/jsm/lines/WireframeGeometry2.js @@ -0,0 +1,24 @@ +import { + WireframeGeometry +} from 'three'; +import { LineSegmentsGeometry } from '../lines/LineSegmentsGeometry.js'; + +class WireframeGeometry2 extends LineSegmentsGeometry { + + constructor( geometry ) { + + super(); + + this.type = 'WireframeGeometry2'; + + this.fromWireframeGeometry( new WireframeGeometry( geometry ) ); + + // set colors, maybe + + } + +} + +WireframeGeometry2.prototype.isWireframeGeometry2 = true; + +export { WireframeGeometry2 }; diff --git a/jsm/loaders/3DMLoader.js b/jsm/loaders/3DMLoader.js new file mode 100644 index 0000000..764cf64 --- /dev/null +++ b/jsm/loaders/3DMLoader.js @@ -0,0 +1,1494 @@ +import { + BufferGeometryLoader, + FileLoader, + Loader, + Object3D, + MeshStandardMaterial, + Mesh, + Color, + Points, + PointsMaterial, + Line, + LineBasicMaterial, + Matrix4, + DirectionalLight, + PointLight, + SpotLight, + RectAreaLight, + Vector3, + Sprite, + SpriteMaterial, + CanvasTexture, + LinearFilter, + ClampToEdgeWrapping, + TextureLoader +} from 'three'; + +const _taskCache = new WeakMap(); + +class Rhino3dmLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.libraryPath = ''; + this.libraryPending = null; + this.libraryBinary = null; + this.libraryConfig = {}; + + this.url = ''; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + this.workerConfig = {}; + + this.materials = []; + this.warnings = []; + + } + + setLibraryPath( path ) { + + this.libraryPath = path; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + + this.url = url; + + loader.load( url, ( buffer ) => { + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + return cachedTask.promise.then( onLoad ).catch( onError ); + + } + + this.decodeObjects( buffer, url ) + .then( result => { + + result.userData.warnings = this.warnings; + onLoad( result ); + + } ) + .catch( e => onError( e ) ); + + }, onProgress, onError ); + + } + + debug() { + + console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); + + } + + decodeObjects( buffer, url ) { + + let worker; + let taskID; + + const taskCost = buffer.byteLength; + + const objectPending = this._getWorker( taskCost ) + .then( ( _worker ) => { + + worker = _worker; + taskID = this.workerNextTaskID ++; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'decode', id: taskID, buffer }, [ buffer ] ); + + // this.debug(); + + } ); + + } ) + .then( ( message ) => this._createGeometry( message.data ) ) + .catch( e => { + + throw e; + + } ); + + // Remove task from the task list. + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + objectPending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + this._releaseTask( worker, taskID ); + + //this.debug(); + + } + + } ); + + // Cache the task result. + _taskCache.set( buffer, { + + url: url, + promise: objectPending + + } ); + + return objectPending; + + } + + parse( data, onLoad, onError ) { + + this.decodeObjects( data, '' ) + .then( result => { + + result.userData.warnings = this.warnings; + onLoad( result ); + + } ) + .catch( e => onError( e ) ); + + } + + _compareMaterials( material ) { + + const mat = {}; + mat.name = material.name; + mat.color = {}; + mat.color.r = material.color.r; + mat.color.g = material.color.g; + mat.color.b = material.color.b; + mat.type = material.type; + + for ( let i = 0; i < this.materials.length; i ++ ) { + + const m = this.materials[ i ]; + const _mat = {}; + _mat.name = m.name; + _mat.color = {}; + _mat.color.r = m.color.r; + _mat.color.g = m.color.g; + _mat.color.b = m.color.b; + _mat.type = m.type; + + if ( JSON.stringify( mat ) === JSON.stringify( _mat ) ) { + + return m; + + } + + } + + this.materials.push( material ); + + return material; + + } + + _createMaterial( material ) { + + if ( material === undefined ) { + + return new MeshStandardMaterial( { + color: new Color( 1, 1, 1 ), + metalness: 0.8, + name: 'default', + side: 2 + } ); + + } + + const _diffuseColor = material.diffuseColor; + + const diffusecolor = new Color( _diffuseColor.r / 255.0, _diffuseColor.g / 255.0, _diffuseColor.b / 255.0 ); + + if ( _diffuseColor.r === 0 && _diffuseColor.g === 0 && _diffuseColor.b === 0 ) { + + diffusecolor.r = 1; + diffusecolor.g = 1; + diffusecolor.b = 1; + + } + + // console.log( material ); + + const mat = new MeshStandardMaterial( { + color: diffusecolor, + name: material.name, + side: 2, + transparent: material.transparency > 0 ? true : false, + opacity: 1.0 - material.transparency + } ); + + const textureLoader = new TextureLoader(); + + for ( let i = 0; i < material.textures.length; i ++ ) { + + const texture = material.textures[ i ]; + + if ( texture.image !== null ) { + + const map = textureLoader.load( texture.image ); + + switch ( texture.type ) { + + case 'Diffuse': + + mat.map = map; + + break; + + case 'Bump': + + mat.bumpMap = map; + + break; + + case 'Transparency': + + mat.alphaMap = map; + mat.transparent = true; + + break; + + case 'Emap': + + mat.envMap = map; + + break; + + } + + map.wrapS = texture.wrapU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + map.wrapT = texture.wrapV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; + map.repeat.set( texture.repeat[ 0 ], texture.repeat[ 1 ] ); + + } + + } + + return mat; + + } + + _createGeometry( data ) { + + // console.log(data); + + const object = new Object3D(); + const instanceDefinitionObjects = []; + const instanceDefinitions = []; + const instanceReferences = []; + + object.userData[ 'layers' ] = data.layers; + object.userData[ 'groups' ] = data.groups; + object.userData[ 'settings' ] = data.settings; + object.userData[ 'objectType' ] = 'File3dm'; + object.userData[ 'materials' ] = null; + object.name = this.url; + + let objects = data.objects; + const materials = data.materials; + + for ( let i = 0; i < objects.length; i ++ ) { + + const obj = objects[ i ]; + const attributes = obj.attributes; + + switch ( obj.objectType ) { + + case 'InstanceDefinition': + + instanceDefinitions.push( obj ); + + break; + + case 'InstanceReference': + + instanceReferences.push( obj ); + + break; + + default: + + let _object; + + if ( attributes.materialIndex >= 0 ) { + + const rMaterial = materials[ attributes.materialIndex ]; + let material = this._createMaterial( rMaterial ); + material = this._compareMaterials( material ); + _object = this._createObject( obj, material ); + + } else { + + const material = this._createMaterial(); + _object = this._createObject( obj, material ); + + } + + if ( _object === undefined ) { + + continue; + + } + + const layer = data.layers[ attributes.layerIndex ]; + + _object.visible = layer ? data.layers[ attributes.layerIndex ].visible : true; + + if ( attributes.isInstanceDefinitionObject ) { + + instanceDefinitionObjects.push( _object ); + + } else { + + object.add( _object ); + + } + + break; + + } + + } + + for ( let i = 0; i < instanceDefinitions.length; i ++ ) { + + const iDef = instanceDefinitions[ i ]; + + objects = []; + + for ( let j = 0; j < iDef.attributes.objectIds.length; j ++ ) { + + const objId = iDef.attributes.objectIds[ j ]; + + for ( let p = 0; p < instanceDefinitionObjects.length; p ++ ) { + + const idoId = instanceDefinitionObjects[ p ].userData.attributes.id; + + if ( objId === idoId ) { + + objects.push( instanceDefinitionObjects[ p ] ); + + } + + } + + } + + // Currently clones geometry and does not take advantage of instancing + + for ( let j = 0; j < instanceReferences.length; j ++ ) { + + const iRef = instanceReferences[ j ]; + + if ( iRef.geometry.parentIdefId === iDef.attributes.id ) { + + const iRefObject = new Object3D(); + const xf = iRef.geometry.xform.array; + + const matrix = new Matrix4(); + matrix.set( xf[ 0 ], xf[ 1 ], xf[ 2 ], xf[ 3 ], xf[ 4 ], xf[ 5 ], xf[ 6 ], xf[ 7 ], xf[ 8 ], xf[ 9 ], xf[ 10 ], xf[ 11 ], xf[ 12 ], xf[ 13 ], xf[ 14 ], xf[ 15 ] ); + + iRefObject.applyMatrix4( matrix ); + + for ( let p = 0; p < objects.length; p ++ ) { + + iRefObject.add( objects[ p ].clone( true ) ); + + } + + object.add( iRefObject ); + + } + + } + + } + + object.userData[ 'materials' ] = this.materials; + return object; + + } + + _createObject( obj, mat ) { + + const loader = new BufferGeometryLoader(); + + const attributes = obj.attributes; + + let geometry, material, _color, color; + + switch ( obj.objectType ) { + + case 'Point': + case 'PointSet': + + geometry = loader.parse( obj.geometry ); + + if ( geometry.attributes.hasOwnProperty( 'color' ) ) { + + material = new PointsMaterial( { vertexColors: true, sizeAttenuation: false, size: 2 } ); + + } else { + + _color = attributes.drawColor; + color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); + material = new PointsMaterial( { color: color, sizeAttenuation: false, size: 2 } ); + + } + + material = this._compareMaterials( material ); + + const points = new Points( geometry, material ); + points.userData[ 'attributes' ] = attributes; + points.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + points.name = attributes.name; + + } + + return points; + + case 'Mesh': + case 'Extrusion': + case 'SubD': + case 'Brep': + + if ( obj.geometry === null ) return; + + geometry = loader.parse( obj.geometry ); + + if ( geometry.attributes.hasOwnProperty( 'color' ) ) { + + mat.vertexColors = true; + + } + + if ( mat === null ) { + + mat = this._createMaterial(); + mat = this._compareMaterials( mat ); + + } + + const mesh = new Mesh( geometry, mat ); + mesh.castShadow = attributes.castsShadows; + mesh.receiveShadow = attributes.receivesShadows; + mesh.userData[ 'attributes' ] = attributes; + mesh.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + mesh.name = attributes.name; + + } + + return mesh; + + case 'Curve': + + geometry = loader.parse( obj.geometry ); + + _color = attributes.drawColor; + color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); + + material = new LineBasicMaterial( { color: color } ); + material = this._compareMaterials( material ); + + const lines = new Line( geometry, material ); + lines.userData[ 'attributes' ] = attributes; + lines.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + lines.name = attributes.name; + + } + + return lines; + + case 'TextDot': + + geometry = obj.geometry; + + const ctx = document.createElement( 'canvas' ).getContext( '2d' ); + const font = `${geometry.fontHeight}px ${geometry.fontFace}`; + ctx.font = font; + const width = ctx.measureText( geometry.text ).width + 10; + const height = geometry.fontHeight + 10; + + const r = window.devicePixelRatio; + + ctx.canvas.width = width * r; + ctx.canvas.height = height * r; + ctx.canvas.style.width = width + 'px'; + ctx.canvas.style.height = height + 'px'; + ctx.setTransform( r, 0, 0, r, 0, 0 ); + + ctx.font = font; + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + color = attributes.drawColor; + ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a})`; + ctx.fillRect( 0, 0, width, height ); + ctx.fillStyle = 'white'; + ctx.fillText( geometry.text, width / 2, height / 2 ); + + const texture = new CanvasTexture( ctx.canvas ); + texture.minFilter = LinearFilter; + texture.wrapS = ClampToEdgeWrapping; + texture.wrapT = ClampToEdgeWrapping; + + material = new SpriteMaterial( { map: texture, depthTest: false } ); + const sprite = new Sprite( material ); + sprite.position.set( geometry.point[ 0 ], geometry.point[ 1 ], geometry.point[ 2 ] ); + sprite.scale.set( width / 10, height / 10, 1.0 ); + + sprite.userData[ 'attributes' ] = attributes; + sprite.userData[ 'objectType' ] = obj.objectType; + + if ( attributes.name ) { + + sprite.name = attributes.name; + + } + + return sprite; + + case 'Light': + + geometry = obj.geometry; + + let light; + + switch ( geometry.lightStyle.name ) { + + case 'LightStyle_WorldPoint': + + light = new PointLight(); + light.castShadow = attributes.castsShadows; + light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); + light.shadow.normalBias = 0.1; + + break; + + case 'LightStyle_WorldSpot': + + light = new SpotLight(); + light.castShadow = attributes.castsShadows; + light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); + light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ); + light.angle = geometry.spotAngleRadians; + light.shadow.normalBias = 0.1; + + break; + + case 'LightStyle_WorldRectangular': + + light = new RectAreaLight(); + const width = Math.abs( geometry.width[ 2 ] ); + const height = Math.abs( geometry.length[ 0 ] ); + light.position.set( geometry.location[ 0 ] - ( height / 2 ), geometry.location[ 1 ], geometry.location[ 2 ] - ( width / 2 ) ); + light.height = height; + light.width = width; + light.lookAt( new Vector3( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ) ); + + break; + + case 'LightStyle_WorldDirectional': + + light = new DirectionalLight(); + light.castShadow = attributes.castsShadows; + light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] ); + light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ); + light.shadow.normalBias = 0.1; + + break; + + case 'LightStyle_WorldLinear': + // not conversion exists, warning has already been printed to the console + break; + + default: + break; + + } + + if ( light ) { + + light.intensity = geometry.intensity; + _color = geometry.diffuse; + color = new Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ); + light.color = color; + light.userData[ 'attributes' ] = attributes; + light.userData[ 'objectType' ] = obj.objectType; + + } + + return light; + + } + + } + + _initLibrary() { + + if ( ! this.libraryPending ) { + + // Load rhino3dm wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.libraryPath ); + const jsContent = new Promise( ( resolve, reject ) => { + + jsLoader.load( 'rhino3dm.js', resolve, undefined, reject ); + + } ); + + // Load rhino3dm WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.libraryPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + const binaryContent = new Promise( ( resolve, reject ) => { + + binaryLoader.load( 'rhino3dm.wasm', resolve, undefined, reject ); + + } ); + + this.libraryPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { + + //this.libraryBinary = binaryContent; + this.libraryConfig.wasmBinary = binaryContent; + + const fn = Rhino3dmWorker.toString(); + + const body = [ + '/* rhino3dm.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + + } ); + + } + + return this.libraryPending; + + } + + _getWorker( taskCost ) { + + return this._initLibrary().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskCosts = {}; + worker._taskLoad = 0; + + worker.postMessage( { + type: 'init', + libraryConfig: this.libraryConfig + } ); + + worker.onmessage = e => { + + const message = e.data; + + switch ( message.type ) { + + case 'warning': + this.warnings.push( message.data ); + console.warn( message.data ); + break; + + case 'decode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.Rhino3dmLoader: Unexpected message, "' + message.type + '"' ); + + } + + }; + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + + worker._taskLoad += taskCost; + + return worker; + + } ); + + } + + _releaseTask( worker, taskID ) { + + worker._taskLoad -= worker._taskCosts[ taskID ]; + delete worker._callbacks[ taskID ]; + delete worker._taskCosts[ taskID ]; + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; ++ i ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + return this; + + } + +} + +/* WEB WORKER */ + +function Rhino3dmWorker() { + + let libraryPending; + let libraryConfig; + let rhino; + let taskID; + + onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + + // console.log(message) + libraryConfig = message.libraryConfig; + const wasmBinary = libraryConfig.wasmBinary; + let RhinoModule; + libraryPending = new Promise( function ( resolve ) { + + /* Like Basis Loader */ + RhinoModule = { wasmBinary, onRuntimeInitialized: resolve }; + + rhino3dm( RhinoModule ); // eslint-disable-line no-undef + + } ).then( () => { + + rhino = RhinoModule; + + } ); + + break; + + case 'decode': + + taskID = message.id; + const buffer = message.buffer; + libraryPending.then( () => { + + try { + + const data = decodeObjects( rhino, buffer ); + self.postMessage( { type: 'decode', id: message.id, data } ); + + } catch ( error ) { + + self.postMessage( { type: 'error', id: message.id, error } ); + + } + + } ); + + break; + + } + + }; + + function decodeObjects( rhino, buffer ) { + + const arr = new Uint8Array( buffer ); + const doc = rhino.File3dm.fromByteArray( arr ); + + const objects = []; + const materials = []; + const layers = []; + const views = []; + const namedViews = []; + const groups = []; + const strings = []; + + //Handle objects + + const objs = doc.objects(); + const cnt = objs.count; + + for ( let i = 0; i < cnt; i ++ ) { + + const _object = objs.get( i ); + + const object = extractObjectData( _object, doc ); + + _object.delete(); + + if ( object ) { + + objects.push( object ); + + } + + } + + // Handle instance definitions + // console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` ); + + for ( let i = 0; i < doc.instanceDefinitions().count(); i ++ ) { + + const idef = doc.instanceDefinitions().get( i ); + const idefAttributes = extractProperties( idef ); + idefAttributes.objectIds = idef.getObjectIds(); + + objects.push( { geometry: null, attributes: idefAttributes, objectType: 'InstanceDefinition' } ); + + } + + // Handle materials + + const textureTypes = [ + // rhino.TextureType.Bitmap, + rhino.TextureType.Diffuse, + rhino.TextureType.Bump, + rhino.TextureType.Transparency, + rhino.TextureType.Opacity, + rhino.TextureType.Emap + ]; + + const pbrTextureTypes = [ + rhino.TextureType.PBR_BaseColor, + rhino.TextureType.PBR_Subsurface, + rhino.TextureType.PBR_SubsurfaceScattering, + rhino.TextureType.PBR_SubsurfaceScatteringRadius, + rhino.TextureType.PBR_Metallic, + rhino.TextureType.PBR_Specular, + rhino.TextureType.PBR_SpecularTint, + rhino.TextureType.PBR_Roughness, + rhino.TextureType.PBR_Anisotropic, + rhino.TextureType.PBR_Anisotropic_Rotation, + rhino.TextureType.PBR_Sheen, + rhino.TextureType.PBR_SheenTint, + rhino.TextureType.PBR_Clearcoat, + rhino.TextureType.PBR_ClearcoatBump, + rhino.TextureType.PBR_ClearcoatRoughness, + rhino.TextureType.PBR_OpacityIor, + rhino.TextureType.PBR_OpacityRoughness, + rhino.TextureType.PBR_Emission, + rhino.TextureType.PBR_AmbientOcclusion, + rhino.TextureType.PBR_Displacement + ]; + + for ( let i = 0; i < doc.materials().count(); i ++ ) { + + const _material = doc.materials().get( i ); + const _pbrMaterial = _material.physicallyBased(); + + let material = extractProperties( _material ); + + const textures = []; + + for ( let j = 0; j < textureTypes.length; j ++ ) { + + const _texture = _material.getTexture( textureTypes[ j ] ); + if ( _texture ) { + + let textureType = textureTypes[ j ].constructor.name; + textureType = textureType.substring( 12, textureType.length ); + const texture = { type: textureType }; + + const image = doc.getEmbeddedFileAsBase64( _texture.fileName ); + + texture.wrapU = _texture.wrapU; + texture.wrapV = _texture.wrapV; + texture.wrapW = _texture.wrapW; + const uvw = _texture.uvwTransform.toFloatArray( true ); + texture.repeat = [ uvw[ 0 ], uvw[ 5 ] ]; + + if ( image ) { + + texture.image = 'data:image/png;base64,' + image; + + } else { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: Image for ${textureType} texture not embedded in file.`, + type: 'missing resource' + } + + } ); + + texture.image = null; + + } + + textures.push( texture ); + + _texture.delete(); + + } + + } + + material.textures = textures; + + if ( _pbrMaterial.supported ) { + + for ( let j = 0; j < pbrTextureTypes.length; j ++ ) { + + const _texture = _material.getTexture( pbrTextureTypes[ j ] ); + if ( _texture ) { + + const image = doc.getEmbeddedFileAsBase64( _texture.fileName ); + let textureType = pbrTextureTypes[ j ].constructor.name; + textureType = textureType.substring( 12, textureType.length ); + const texture = { type: textureType, image: 'data:image/png;base64,' + image }; + textures.push( texture ); + + _texture.delete(); + + } + + } + + const pbMaterialProperties = extractProperties( _material.physicallyBased() ); + + material = Object.assign( pbMaterialProperties, material ); + + } + + materials.push( material ); + + _material.delete(); + _pbrMaterial.delete(); + + } + + // Handle layers + + for ( let i = 0; i < doc.layers().count(); i ++ ) { + + const _layer = doc.layers().get( i ); + const layer = extractProperties( _layer ); + + layers.push( layer ); + + _layer.delete(); + + } + + // Handle views + + for ( let i = 0; i < doc.views().count(); i ++ ) { + + const _view = doc.views().get( i ); + const view = extractProperties( _view ); + + views.push( view ); + + _view.delete(); + + } + + // Handle named views + + for ( let i = 0; i < doc.namedViews().count(); i ++ ) { + + const _namedView = doc.namedViews().get( i ); + const namedView = extractProperties( _namedView ); + + namedViews.push( namedView ); + + _namedView.delete(); + + } + + // Handle groups + + for ( let i = 0; i < doc.groups().count(); i ++ ) { + + const _group = doc.groups().get( i ); + const group = extractProperties( _group ); + + groups.push( group ); + + _group.delete(); + + } + + // Handle settings + + const settings = extractProperties( doc.settings() ); + + //TODO: Handle other document stuff like dimstyles, instance definitions, bitmaps etc. + + // Handle dimstyles + // console.log( `Dimstyle Count: ${doc.dimstyles().count()}` ); + + // Handle bitmaps + // console.log( `Bitmap Count: ${doc.bitmaps().count()}` ); + + // Handle strings + // console.log( `Document Strings Count: ${doc.strings().count()}` ); + // Note: doc.strings().documentUserTextCount() counts any doc.strings defined in a section + //console.log( `Document User Text Count: ${doc.strings().documentUserTextCount()}` ); + + const strings_count = doc.strings().count(); + + for ( let i = 0; i < strings_count; i ++ ) { + + strings.push( doc.strings().get( i ) ); + + } + + doc.delete(); + + return { objects, materials, layers, views, namedViews, groups, strings, settings }; + + } + + function extractObjectData( object, doc ) { + + const _geometry = object.geometry(); + const _attributes = object.attributes(); + let objectType = _geometry.objectType; + let geometry, attributes, position, data, mesh; + + // skip instance definition objects + //if( _attributes.isInstanceDefinitionObject ) { continue; } + + // TODO: handle other geometry types + switch ( objectType ) { + + case rhino.ObjectType.Curve: + + const pts = curveToPoints( _geometry, 100 ); + + position = {}; + attributes = {}; + data = {}; + + position.itemSize = 3; + position.type = 'Float32Array'; + position.array = []; + + for ( let j = 0; j < pts.length; j ++ ) { + + position.array.push( pts[ j ][ 0 ] ); + position.array.push( pts[ j ][ 1 ] ); + position.array.push( pts[ j ][ 2 ] ); + + } + + attributes.position = position; + data.attributes = attributes; + + geometry = { data }; + + break; + + case rhino.ObjectType.Point: + + const pt = _geometry.location; + + position = {}; + const color = {}; + attributes = {}; + data = {}; + + position.itemSize = 3; + position.type = 'Float32Array'; + position.array = [ pt[ 0 ], pt[ 1 ], pt[ 2 ] ]; + + const _color = _attributes.drawColor( doc ); + + color.itemSize = 3; + color.type = 'Float32Array'; + color.array = [ _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ]; + + attributes.position = position; + attributes.color = color; + data.attributes = attributes; + + geometry = { data }; + + break; + + case rhino.ObjectType.PointSet: + case rhino.ObjectType.Mesh: + + geometry = _geometry.toThreejsJSON(); + + break; + + case rhino.ObjectType.Brep: + + const faces = _geometry.faces(); + mesh = new rhino.Mesh(); + + for ( let faceIndex = 0; faceIndex < faces.count; faceIndex ++ ) { + + const face = faces.get( faceIndex ); + const _mesh = face.getMesh( rhino.MeshType.Any ); + + if ( _mesh ) { + + mesh.append( _mesh ); + _mesh.delete(); + + } + + face.delete(); + + } + + if ( mesh.faces().count > 0 ) { + + mesh.compact(); + geometry = mesh.toThreejsJSON(); + faces.delete(); + + } + + mesh.delete(); + + break; + + case rhino.ObjectType.Extrusion: + + mesh = _geometry.getMesh( rhino.MeshType.Any ); + + if ( mesh ) { + + geometry = mesh.toThreejsJSON(); + mesh.delete(); + + } + + break; + + case rhino.ObjectType.TextDot: + + geometry = extractProperties( _geometry ); + + break; + + case rhino.ObjectType.Light: + + geometry = extractProperties( _geometry ); + + if ( geometry.lightStyle.name === 'LightStyle_WorldLinear' ) { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: No conversion exists for ${objectType.constructor.name} ${geometry.lightStyle.name}`, + type: 'no conversion', + guid: _attributes.id + } + + } ); + + } + + break; + + case rhino.ObjectType.InstanceReference: + + geometry = extractProperties( _geometry ); + geometry.xform = extractProperties( _geometry.xform ); + geometry.xform.array = _geometry.xform.toFloatArray( true ); + + break; + + case rhino.ObjectType.SubD: + + // TODO: precalculate resulting vertices and faces and warn on excessive results + _geometry.subdivide( 3 ); + mesh = rhino.Mesh.createFromSubDControlNet( _geometry ); + if ( mesh ) { + + geometry = mesh.toThreejsJSON(); + mesh.delete(); + + } + + break; + + /* + case rhino.ObjectType.Annotation: + case rhino.ObjectType.Hatch: + case rhino.ObjectType.ClipPlane: + */ + + default: + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: Conversion not implemented for ${objectType.constructor.name}`, + type: 'not implemented', + guid: _attributes.id + } + + } ); + + break; + + } + + if ( geometry ) { + + attributes = extractProperties( _attributes ); + attributes.geometry = extractProperties( _geometry ); + + if ( _attributes.groupCount > 0 ) { + + attributes.groupIds = _attributes.getGroupList(); + + } + + if ( _attributes.userStringCount > 0 ) { + + attributes.userStrings = _attributes.getUserStrings(); + + } + + if ( _geometry.userStringCount > 0 ) { + + attributes.geometry.userStrings = _geometry.getUserStrings(); + + } + + attributes.drawColor = _attributes.drawColor( doc ); + + objectType = objectType.constructor.name; + objectType = objectType.substring( 11, objectType.length ); + + return { geometry, attributes, objectType }; + + } else { + + self.postMessage( { type: 'warning', id: taskID, data: { + message: `THREE.3DMLoader: ${objectType.constructor.name} has no associated mesh geometry.`, + type: 'missing mesh', + guid: _attributes.id + } + + } ); + + } + + } + + function extractProperties( object ) { + + const result = {}; + + for ( const property in object ) { + + const value = object[ property ]; + + if ( typeof value !== 'function' ) { + + if ( typeof value === 'object' && value !== null && value.hasOwnProperty( 'constructor' ) ) { + + result[ property ] = { name: value.constructor.name, value: value.value }; + + } else { + + result[ property ] = value; + + } + + } else { + + // these are functions that could be called to extract more data. + //console.log( `${property}: ${object[ property ].constructor.name}` ); + + } + + } + + return result; + + } + + function curveToPoints( curve, pointLimit ) { + + let pointCount = pointLimit; + let rc = []; + const ts = []; + + if ( curve instanceof rhino.LineCurve ) { + + return [ curve.pointAtStart, curve.pointAtEnd ]; + + } + + if ( curve instanceof rhino.PolylineCurve ) { + + pointCount = curve.pointCount; + for ( let i = 0; i < pointCount; i ++ ) { + + rc.push( curve.point( i ) ); + + } + + return rc; + + } + + if ( curve instanceof rhino.PolyCurve ) { + + const segmentCount = curve.segmentCount; + + for ( let i = 0; i < segmentCount; i ++ ) { + + const segment = curve.segmentCurve( i ); + const segmentArray = curveToPoints( segment, pointCount ); + rc = rc.concat( segmentArray ); + segment.delete(); + + } + + return rc; + + } + + if ( curve instanceof rhino.ArcCurve ) { + + pointCount = Math.floor( curve.angleDegrees / 5 ); + pointCount = pointCount < 2 ? 2 : pointCount; + // alternative to this hardcoded version: https://stackoverflow.com/a/18499923/2179399 + + } + + if ( curve instanceof rhino.NurbsCurve && curve.degree === 1 ) { + + const pLine = curve.tryGetPolyline(); + + for ( let i = 0; i < pLine.count; i ++ ) { + + rc.push( pLine.get( i ) ); + + } + + pLine.delete(); + + return rc; + + } + + const domain = curve.domain; + const divisions = pointCount - 1.0; + + for ( let j = 0; j < pointCount; j ++ ) { + + const t = domain[ 0 ] + ( j / divisions ) * ( domain[ 1 ] - domain[ 0 ] ); + + if ( t === domain[ 0 ] || t === domain[ 1 ] ) { + + ts.push( t ); + continue; + + } + + const tan = curve.tangentAt( t ); + const prevTan = curve.tangentAt( ts.slice( - 1 )[ 0 ] ); + + // Duplicated from THREE.Vector3 + // How to pass imports to worker? + + const tS = tan[ 0 ] * tan[ 0 ] + tan[ 1 ] * tan[ 1 ] + tan[ 2 ] * tan[ 2 ]; + const ptS = prevTan[ 0 ] * prevTan[ 0 ] + prevTan[ 1 ] * prevTan[ 1 ] + prevTan[ 2 ] * prevTan[ 2 ]; + + const denominator = Math.sqrt( tS * ptS ); + + let angle; + + if ( denominator === 0 ) { + + angle = Math.PI / 2; + + } else { + + const theta = ( tan.x * prevTan.x + tan.y * prevTan.y + tan.z * prevTan.z ) / denominator; + angle = Math.acos( Math.max( - 1, Math.min( 1, theta ) ) ); + + } + + if ( angle < 0.1 ) continue; + + ts.push( t ); + + } + + rc = ts.map( t => curve.pointAt( t ) ); + return rc; + + } + +} + +export { Rhino3dmLoader }; diff --git a/jsm/loaders/3MFLoader.js b/jsm/loaders/3MFLoader.js new file mode 100644 index 0000000..48da188 --- /dev/null +++ b/jsm/loaders/3MFLoader.js @@ -0,0 +1,1473 @@ +import { + BufferAttribute, + BufferGeometry, + ClampToEdgeWrapping, + Color, + FileLoader, + Float32BufferAttribute, + Group, + LinearFilter, + LinearMipmapLinearFilter, + Loader, + LoaderUtils, + Matrix4, + Mesh, + MeshPhongMaterial, + MeshStandardMaterial, + MirroredRepeatWrapping, + NearestFilter, + RepeatWrapping, + TextureLoader, + sRGBEncoding +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +/** + * + * 3D Manufacturing Format (3MF) specification: https://3mf.io/specification/ + * + * The following features from the core specification are supported: + * + * - 3D Models + * - Object Resources (Meshes and Components) + * - Material Resources (Base Materials) + * + * 3MF Materials and Properties Extension are only partially supported. + * + * - Texture 2D + * - Texture 2D Groups + * - Color Groups (Vertex Colors) + * - Metallic Display Properties (PBR) + */ + +class ThreeMFLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.availableExtensions = []; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( buffer ) { + + try { + + onLoad( scope.parse( buffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + const scope = this; + const textureLoader = new TextureLoader( this.manager ); + + function loadDocument( data ) { + + let zip = null; + let file = null; + + let relsName; + let modelRelsName; + const modelPartNames = []; + const texturesPartNames = []; + + let modelRels; + const modelParts = {}; + const printTicketParts = {}; + const texturesParts = {}; + + try { + + zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef + + } catch ( e ) { + + if ( e instanceof ReferenceError ) { + + console.error( 'THREE.3MFLoader: fflate missing and file is compressed.' ); + return null; + + } + + } + + for ( file in zip ) { + + if ( file.match( /\_rels\/.rels$/ ) ) { + + relsName = file; + + } else if ( file.match( /3D\/_rels\/.*\.model\.rels$/ ) ) { + + modelRelsName = file; + + } else if ( file.match( /^3D\/.*\.model$/ ) ) { + + modelPartNames.push( file ); + + } else if ( file.match( /^3D\/Textures?\/.*/ ) ) { + + texturesPartNames.push( file ); + + } + + } + + // + + const relsView = zip[ relsName ]; + const relsFileText = LoaderUtils.decodeText( relsView ); + const rels = parseRelsXml( relsFileText ); + + // + + if ( modelRelsName ) { + + const relsView = zip[ modelRelsName ]; + const relsFileText = LoaderUtils.decodeText( relsView ); + modelRels = parseRelsXml( relsFileText ); + + } + + // + + for ( let i = 0; i < modelPartNames.length; i ++ ) { + + const modelPart = modelPartNames[ i ]; + const view = zip[ modelPart ]; + + const fileText = LoaderUtils.decodeText( view ); + const xmlData = new DOMParser().parseFromString( fileText, 'application/xml' ); + + if ( xmlData.documentElement.nodeName.toLowerCase() !== 'model' ) { + + console.error( 'THREE.3MFLoader: Error loading 3MF - no 3MF document found: ', modelPart ); + + } + + const modelNode = xmlData.querySelector( 'model' ); + const extensions = {}; + + for ( let i = 0; i < modelNode.attributes.length; i ++ ) { + + const attr = modelNode.attributes[ i ]; + if ( attr.name.match( /^xmlns:(.+)$/ ) ) { + + extensions[ attr.value ] = RegExp.$1; + + } + + } + + const modelData = parseModelNode( modelNode ); + modelData[ 'xml' ] = modelNode; + + if ( 0 < Object.keys( extensions ).length ) { + + modelData[ 'extensions' ] = extensions; + + } + + modelParts[ modelPart ] = modelData; + + } + + // + + for ( let i = 0; i < texturesPartNames.length; i ++ ) { + + const texturesPartName = texturesPartNames[ i ]; + texturesParts[ texturesPartName ] = zip[ texturesPartName ].buffer; + + } + + return { + rels: rels, + modelRels: modelRels, + model: modelParts, + printTicket: printTicketParts, + texture: texturesParts + }; + + } + + function parseRelsXml( relsFileText ) { + + const relationships = []; + + const relsXmlData = new DOMParser().parseFromString( relsFileText, 'application/xml' ); + + const relsNodes = relsXmlData.querySelectorAll( 'Relationship' ); + + for ( let i = 0; i < relsNodes.length; i ++ ) { + + const relsNode = relsNodes[ i ]; + + const relationship = { + target: relsNode.getAttribute( 'Target' ), //required + id: relsNode.getAttribute( 'Id' ), //required + type: relsNode.getAttribute( 'Type' ) //required + }; + + relationships.push( relationship ); + + } + + return relationships; + + } + + function parseMetadataNodes( metadataNodes ) { + + const metadataData = {}; + + for ( let i = 0; i < metadataNodes.length; i ++ ) { + + const metadataNode = metadataNodes[ i ]; + const name = metadataNode.getAttribute( 'name' ); + const validNames = [ + 'Title', + 'Designer', + 'Description', + 'Copyright', + 'LicenseTerms', + 'Rating', + 'CreationDate', + 'ModificationDate' + ]; + + if ( 0 <= validNames.indexOf( name ) ) { + + metadataData[ name ] = metadataNode.textContent; + + } + + } + + return metadataData; + + } + + function parseBasematerialsNode( basematerialsNode ) { + + const basematerialsData = { + id: basematerialsNode.getAttribute( 'id' ), // required + basematerials: [] + }; + + const basematerialNodes = basematerialsNode.querySelectorAll( 'base' ); + + for ( let i = 0; i < basematerialNodes.length; i ++ ) { + + const basematerialNode = basematerialNodes[ i ]; + const basematerialData = parseBasematerialNode( basematerialNode ); + basematerialData.index = i; // the order and count of the material nodes form an implicit 0-based index + basematerialsData.basematerials.push( basematerialData ); + + } + + return basematerialsData; + + } + + function parseTexture2DNode( texture2DNode ) { + + const texture2dData = { + id: texture2DNode.getAttribute( 'id' ), // required + path: texture2DNode.getAttribute( 'path' ), // required + contenttype: texture2DNode.getAttribute( 'contenttype' ), // required + tilestyleu: texture2DNode.getAttribute( 'tilestyleu' ), + tilestylev: texture2DNode.getAttribute( 'tilestylev' ), + filter: texture2DNode.getAttribute( 'filter' ), + }; + + return texture2dData; + + } + + function parseTextures2DGroupNode( texture2DGroupNode ) { + + const texture2DGroupData = { + id: texture2DGroupNode.getAttribute( 'id' ), // required + texid: texture2DGroupNode.getAttribute( 'texid' ), // required + displaypropertiesid: texture2DGroupNode.getAttribute( 'displaypropertiesid' ) + }; + + const tex2coordNodes = texture2DGroupNode.querySelectorAll( 'tex2coord' ); + + const uvs = []; + + for ( let i = 0; i < tex2coordNodes.length; i ++ ) { + + const tex2coordNode = tex2coordNodes[ i ]; + const u = tex2coordNode.getAttribute( 'u' ); + const v = tex2coordNode.getAttribute( 'v' ); + + uvs.push( parseFloat( u ), parseFloat( v ) ); + + } + + texture2DGroupData[ 'uvs' ] = new Float32Array( uvs ); + + return texture2DGroupData; + + } + + function parseColorGroupNode( colorGroupNode ) { + + const colorGroupData = { + id: colorGroupNode.getAttribute( 'id' ), // required + displaypropertiesid: colorGroupNode.getAttribute( 'displaypropertiesid' ) + }; + + const colorNodes = colorGroupNode.querySelectorAll( 'color' ); + + const colors = []; + const colorObject = new Color(); + + for ( let i = 0; i < colorNodes.length; i ++ ) { + + const colorNode = colorNodes[ i ]; + const color = colorNode.getAttribute( 'color' ); + + colorObject.setStyle( color.substring( 0, 7 ) ); + colorObject.convertSRGBToLinear(); // color is in sRGB + + colors.push( colorObject.r, colorObject.g, colorObject.b ); + + } + + colorGroupData[ 'colors' ] = new Float32Array( colors ); + + return colorGroupData; + + } + + function parseMetallicDisplaypropertiesNode( metallicDisplaypropetiesNode ) { + + const metallicDisplaypropertiesData = { + id: metallicDisplaypropetiesNode.getAttribute( 'id' ) // required + }; + + const metallicNodes = metallicDisplaypropetiesNode.querySelectorAll( 'pbmetallic' ); + + const metallicData = []; + + for ( let i = 0; i < metallicNodes.length; i ++ ) { + + const metallicNode = metallicNodes[ i ]; + + metallicData.push( { + name: metallicNode.getAttribute( 'name' ), // required + metallicness: parseFloat( metallicNode.getAttribute( 'metallicness' ) ), // required + roughness: parseFloat( metallicNode.getAttribute( 'roughness' ) ) // required + } ); + + } + + metallicDisplaypropertiesData.data = metallicData; + + return metallicDisplaypropertiesData; + + } + + function parseBasematerialNode( basematerialNode ) { + + const basematerialData = {}; + + basematerialData[ 'name' ] = basematerialNode.getAttribute( 'name' ); // required + basematerialData[ 'displaycolor' ] = basematerialNode.getAttribute( 'displaycolor' ); // required + basematerialData[ 'displaypropertiesid' ] = basematerialNode.getAttribute( 'displaypropertiesid' ); + + return basematerialData; + + } + + function parseMeshNode( meshNode ) { + + const meshData = {}; + + const vertices = []; + const vertexNodes = meshNode.querySelectorAll( 'vertices vertex' ); + + for ( let i = 0; i < vertexNodes.length; i ++ ) { + + const vertexNode = vertexNodes[ i ]; + const x = vertexNode.getAttribute( 'x' ); + const y = vertexNode.getAttribute( 'y' ); + const z = vertexNode.getAttribute( 'z' ); + + vertices.push( parseFloat( x ), parseFloat( y ), parseFloat( z ) ); + + } + + meshData[ 'vertices' ] = new Float32Array( vertices ); + + const triangleProperties = []; + const triangles = []; + const triangleNodes = meshNode.querySelectorAll( 'triangles triangle' ); + + for ( let i = 0; i < triangleNodes.length; i ++ ) { + + const triangleNode = triangleNodes[ i ]; + const v1 = triangleNode.getAttribute( 'v1' ); + const v2 = triangleNode.getAttribute( 'v2' ); + const v3 = triangleNode.getAttribute( 'v3' ); + const p1 = triangleNode.getAttribute( 'p1' ); + const p2 = triangleNode.getAttribute( 'p2' ); + const p3 = triangleNode.getAttribute( 'p3' ); + const pid = triangleNode.getAttribute( 'pid' ); + + const triangleProperty = {}; + + triangleProperty[ 'v1' ] = parseInt( v1, 10 ); + triangleProperty[ 'v2' ] = parseInt( v2, 10 ); + triangleProperty[ 'v3' ] = parseInt( v3, 10 ); + + triangles.push( triangleProperty[ 'v1' ], triangleProperty[ 'v2' ], triangleProperty[ 'v3' ] ); + + // optional + + if ( p1 ) { + + triangleProperty[ 'p1' ] = parseInt( p1, 10 ); + + } + + if ( p2 ) { + + triangleProperty[ 'p2' ] = parseInt( p2, 10 ); + + } + + if ( p3 ) { + + triangleProperty[ 'p3' ] = parseInt( p3, 10 ); + + } + + if ( pid ) { + + triangleProperty[ 'pid' ] = pid; + + } + + if ( 0 < Object.keys( triangleProperty ).length ) { + + triangleProperties.push( triangleProperty ); + + } + + } + + meshData[ 'triangleProperties' ] = triangleProperties; + meshData[ 'triangles' ] = new Uint32Array( triangles ); + + return meshData; + + } + + function parseComponentsNode( componentsNode ) { + + const components = []; + + const componentNodes = componentsNode.querySelectorAll( 'component' ); + + for ( let i = 0; i < componentNodes.length; i ++ ) { + + const componentNode = componentNodes[ i ]; + const componentData = parseComponentNode( componentNode ); + components.push( componentData ); + + } + + return components; + + } + + function parseComponentNode( componentNode ) { + + const componentData = {}; + + componentData[ 'objectId' ] = componentNode.getAttribute( 'objectid' ); // required + + const transform = componentNode.getAttribute( 'transform' ); + + if ( transform ) { + + componentData[ 'transform' ] = parseTransform( transform ); + + } + + return componentData; + + } + + function parseTransform( transform ) { + + const t = []; + transform.split( ' ' ).forEach( function ( s ) { + + t.push( parseFloat( s ) ); + + } ); + + const matrix = new Matrix4(); + matrix.set( + t[ 0 ], t[ 3 ], t[ 6 ], t[ 9 ], + t[ 1 ], t[ 4 ], t[ 7 ], t[ 10 ], + t[ 2 ], t[ 5 ], t[ 8 ], t[ 11 ], + 0.0, 0.0, 0.0, 1.0 + ); + + return matrix; + + } + + function parseObjectNode( objectNode ) { + + const objectData = { + type: objectNode.getAttribute( 'type' ) + }; + + const id = objectNode.getAttribute( 'id' ); + + if ( id ) { + + objectData[ 'id' ] = id; + + } + + const pid = objectNode.getAttribute( 'pid' ); + + if ( pid ) { + + objectData[ 'pid' ] = pid; + + } + + const pindex = objectNode.getAttribute( 'pindex' ); + + if ( pindex ) { + + objectData[ 'pindex' ] = pindex; + + } + + const thumbnail = objectNode.getAttribute( 'thumbnail' ); + + if ( thumbnail ) { + + objectData[ 'thumbnail' ] = thumbnail; + + } + + const partnumber = objectNode.getAttribute( 'partnumber' ); + + if ( partnumber ) { + + objectData[ 'partnumber' ] = partnumber; + + } + + const name = objectNode.getAttribute( 'name' ); + + if ( name ) { + + objectData[ 'name' ] = name; + + } + + const meshNode = objectNode.querySelector( 'mesh' ); + + if ( meshNode ) { + + objectData[ 'mesh' ] = parseMeshNode( meshNode ); + + } + + const componentsNode = objectNode.querySelector( 'components' ); + + if ( componentsNode ) { + + objectData[ 'components' ] = parseComponentsNode( componentsNode ); + + } + + return objectData; + + } + + function parseResourcesNode( resourcesNode ) { + + const resourcesData = {}; + + resourcesData[ 'basematerials' ] = {}; + const basematerialsNodes = resourcesNode.querySelectorAll( 'basematerials' ); + + for ( let i = 0; i < basematerialsNodes.length; i ++ ) { + + const basematerialsNode = basematerialsNodes[ i ]; + const basematerialsData = parseBasematerialsNode( basematerialsNode ); + resourcesData[ 'basematerials' ][ basematerialsData[ 'id' ] ] = basematerialsData; + + } + + // + + resourcesData[ 'texture2d' ] = {}; + const textures2DNodes = resourcesNode.querySelectorAll( 'texture2d' ); + + for ( let i = 0; i < textures2DNodes.length; i ++ ) { + + const textures2DNode = textures2DNodes[ i ]; + const texture2DData = parseTexture2DNode( textures2DNode ); + resourcesData[ 'texture2d' ][ texture2DData[ 'id' ] ] = texture2DData; + + } + + // + + resourcesData[ 'colorgroup' ] = {}; + const colorGroupNodes = resourcesNode.querySelectorAll( 'colorgroup' ); + + for ( let i = 0; i < colorGroupNodes.length; i ++ ) { + + const colorGroupNode = colorGroupNodes[ i ]; + const colorGroupData = parseColorGroupNode( colorGroupNode ); + resourcesData[ 'colorgroup' ][ colorGroupData[ 'id' ] ] = colorGroupData; + + } + + // + + resourcesData[ 'pbmetallicdisplayproperties' ] = {}; + const pbmetallicdisplaypropertiesNodes = resourcesNode.querySelectorAll( 'pbmetallicdisplayproperties' ); + + for ( let i = 0; i < pbmetallicdisplaypropertiesNodes.length; i ++ ) { + + const pbmetallicdisplaypropertiesNode = pbmetallicdisplaypropertiesNodes[ i ]; + const pbmetallicdisplaypropertiesData = parseMetallicDisplaypropertiesNode( pbmetallicdisplaypropertiesNode ); + resourcesData[ 'pbmetallicdisplayproperties' ][ pbmetallicdisplaypropertiesData[ 'id' ] ] = pbmetallicdisplaypropertiesData; + + } + + // + + resourcesData[ 'texture2dgroup' ] = {}; + const textures2DGroupNodes = resourcesNode.querySelectorAll( 'texture2dgroup' ); + + for ( let i = 0; i < textures2DGroupNodes.length; i ++ ) { + + const textures2DGroupNode = textures2DGroupNodes[ i ]; + const textures2DGroupData = parseTextures2DGroupNode( textures2DGroupNode ); + resourcesData[ 'texture2dgroup' ][ textures2DGroupData[ 'id' ] ] = textures2DGroupData; + + } + + // + + resourcesData[ 'object' ] = {}; + const objectNodes = resourcesNode.querySelectorAll( 'object' ); + + for ( let i = 0; i < objectNodes.length; i ++ ) { + + const objectNode = objectNodes[ i ]; + const objectData = parseObjectNode( objectNode ); + resourcesData[ 'object' ][ objectData[ 'id' ] ] = objectData; + + } + + return resourcesData; + + } + + function parseBuildNode( buildNode ) { + + const buildData = []; + const itemNodes = buildNode.querySelectorAll( 'item' ); + + for ( let i = 0; i < itemNodes.length; i ++ ) { + + const itemNode = itemNodes[ i ]; + const buildItem = { + objectId: itemNode.getAttribute( 'objectid' ) + }; + const transform = itemNode.getAttribute( 'transform' ); + + if ( transform ) { + + buildItem[ 'transform' ] = parseTransform( transform ); + + } + + buildData.push( buildItem ); + + } + + return buildData; + + } + + function parseModelNode( modelNode ) { + + const modelData = { unit: modelNode.getAttribute( 'unit' ) || 'millimeter' }; + const metadataNodes = modelNode.querySelectorAll( 'metadata' ); + + if ( metadataNodes ) { + + modelData[ 'metadata' ] = parseMetadataNodes( metadataNodes ); + + } + + const resourcesNode = modelNode.querySelector( 'resources' ); + + if ( resourcesNode ) { + + modelData[ 'resources' ] = parseResourcesNode( resourcesNode ); + + } + + const buildNode = modelNode.querySelector( 'build' ); + + if ( buildNode ) { + + modelData[ 'build' ] = parseBuildNode( buildNode ); + + } + + return modelData; + + } + + function buildTexture( texture2dgroup, objects, modelData, textureData ) { + + const texid = texture2dgroup.texid; + const texture2ds = modelData.resources.texture2d; + const texture2d = texture2ds[ texid ]; + + if ( texture2d ) { + + const data = textureData[ texture2d.path ]; + const type = texture2d.contenttype; + + const blob = new Blob( [ data ], { type: type } ); + const sourceURI = URL.createObjectURL( blob ); + + const texture = textureLoader.load( sourceURI, function () { + + URL.revokeObjectURL( sourceURI ); + + } ); + + texture.encoding = sRGBEncoding; + + // texture parameters + + switch ( texture2d.tilestyleu ) { + + case 'wrap': + texture.wrapS = RepeatWrapping; + break; + + case 'mirror': + texture.wrapS = MirroredRepeatWrapping; + break; + + case 'none': + case 'clamp': + texture.wrapS = ClampToEdgeWrapping; + break; + + default: + texture.wrapS = RepeatWrapping; + + } + + switch ( texture2d.tilestylev ) { + + case 'wrap': + texture.wrapT = RepeatWrapping; + break; + + case 'mirror': + texture.wrapT = MirroredRepeatWrapping; + break; + + case 'none': + case 'clamp': + texture.wrapT = ClampToEdgeWrapping; + break; + + default: + texture.wrapT = RepeatWrapping; + + } + + switch ( texture2d.filter ) { + + case 'auto': + texture.magFilter = LinearFilter; + texture.minFilter = LinearMipmapLinearFilter; + break; + + case 'linear': + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + break; + + case 'nearest': + texture.magFilter = NearestFilter; + texture.minFilter = NearestFilter; + break; + + default: + texture.magFilter = LinearFilter; + texture.minFilter = LinearMipmapLinearFilter; + + } + + return texture; + + } else { + + return null; + + } + + } + + function buildBasematerialsMeshes( basematerials, triangleProperties, meshData, objects, modelData, textureData, objectData ) { + + const objectPindex = objectData.pindex; + + const materialMap = {}; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + const pindex = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectPindex; + + if ( materialMap[ pindex ] === undefined ) materialMap[ pindex ] = []; + + materialMap[ pindex ].push( triangleProperty ); + + } + + // + + const keys = Object.keys( materialMap ); + const meshes = []; + + for ( let i = 0, l = keys.length; i < l; i ++ ) { + + const materialIndex = keys[ i ]; + const trianglePropertiesProps = materialMap[ materialIndex ]; + const basematerialData = basematerials.basematerials[ materialIndex ]; + const material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial ); + + // + + const geometry = new BufferGeometry(); + + const positionData = []; + + const vertices = meshData.vertices; + + for ( let j = 0, jl = trianglePropertiesProps.length; j < jl; j ++ ) { + + const triangleProperty = trianglePropertiesProps[ j ]; + + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] ); + + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) ); + + // + + const mesh = new Mesh( geometry, material ); + meshes.push( mesh ); + + } + + return meshes; + + } + + function buildTexturedMesh( texture2dgroup, triangleProperties, meshData, objects, modelData, textureData, objectData ) { + + // geometry + + const geometry = new BufferGeometry(); + + const positionData = []; + const uvData = []; + + const vertices = meshData.vertices; + const uvs = texture2dgroup.uvs; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] ); + positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] ); + + // + + uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] ); + uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] ); + + uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 0 ] ); + uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 1 ] ); + + uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 0 ] ); + uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 1 ] ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) ); + geometry.setAttribute( 'uv', new Float32BufferAttribute( uvData, 2 ) ); + + // material + + const texture = getBuild( texture2dgroup, objects, modelData, textureData, objectData, buildTexture ); + + const material = new MeshPhongMaterial( { map: texture, flatShading: true } ); + + // mesh + + const mesh = new Mesh( geometry, material ); + + return mesh; + + } + + function buildVertexColorMesh( colorgroup, triangleProperties, meshData, objectData ) { + + // geometry + + const geometry = new BufferGeometry(); + + const positionData = []; + const colorData = []; + + const vertices = meshData.vertices; + const colors = colorgroup.colors; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + + const v1 = triangleProperty.v1; + const v2 = triangleProperty.v2; + const v3 = triangleProperty.v3; + + positionData.push( vertices[ ( v1 * 3 ) + 0 ] ); + positionData.push( vertices[ ( v1 * 3 ) + 1 ] ); + positionData.push( vertices[ ( v1 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( v2 * 3 ) + 0 ] ); + positionData.push( vertices[ ( v2 * 3 ) + 1 ] ); + positionData.push( vertices[ ( v2 * 3 ) + 2 ] ); + + positionData.push( vertices[ ( v3 * 3 ) + 0 ] ); + positionData.push( vertices[ ( v3 * 3 ) + 1 ] ); + positionData.push( vertices[ ( v3 * 3 ) + 2 ] ); + + // + + const p1 = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectData.pindex; + const p2 = ( triangleProperty.p2 !== undefined ) ? triangleProperty.p2 : p1; + const p3 = ( triangleProperty.p3 !== undefined ) ? triangleProperty.p3 : p1; + + colorData.push( colors[ ( p1 * 3 ) + 0 ] ); + colorData.push( colors[ ( p1 * 3 ) + 1 ] ); + colorData.push( colors[ ( p1 * 3 ) + 2 ] ); + + colorData.push( colors[ ( p2 * 3 ) + 0 ] ); + colorData.push( colors[ ( p2 * 3 ) + 1 ] ); + colorData.push( colors[ ( p2 * 3 ) + 2 ] ); + + colorData.push( colors[ ( p3 * 3 ) + 0 ] ); + colorData.push( colors[ ( p3 * 3 ) + 1 ] ); + colorData.push( colors[ ( p3 * 3 ) + 2 ] ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positionData, 3 ) ); + geometry.setAttribute( 'color', new Float32BufferAttribute( colorData, 3 ) ); + + // material + + const material = new MeshPhongMaterial( { vertexColors: true, flatShading: true } ); + + // mesh + + const mesh = new Mesh( geometry, material ); + + return mesh; + + } + + function buildDefaultMesh( meshData ) { + + const geometry = new BufferGeometry(); + geometry.setIndex( new BufferAttribute( meshData[ 'triangles' ], 1 ) ); + geometry.setAttribute( 'position', new BufferAttribute( meshData[ 'vertices' ], 3 ) ); + + const material = new MeshPhongMaterial( { color: 0xffffff, flatShading: true } ); + + const mesh = new Mesh( geometry, material ); + + return mesh; + + } + + function buildMeshes( resourceMap, meshData, objects, modelData, textureData, objectData ) { + + const keys = Object.keys( resourceMap ); + const meshes = []; + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + const resourceId = keys[ i ]; + const triangleProperties = resourceMap[ resourceId ]; + const resourceType = getResourceType( resourceId, modelData ); + + switch ( resourceType ) { + + case 'material': + const basematerials = modelData.resources.basematerials[ resourceId ]; + const newMeshes = buildBasematerialsMeshes( basematerials, triangleProperties, meshData, objects, modelData, textureData, objectData ); + + for ( let j = 0, jl = newMeshes.length; j < jl; j ++ ) { + + meshes.push( newMeshes[ j ] ); + + } + + break; + + case 'texture': + const texture2dgroup = modelData.resources.texture2dgroup[ resourceId ]; + meshes.push( buildTexturedMesh( texture2dgroup, triangleProperties, meshData, objects, modelData, textureData, objectData ) ); + break; + + case 'vertexColors': + const colorgroup = modelData.resources.colorgroup[ resourceId ]; + meshes.push( buildVertexColorMesh( colorgroup, triangleProperties, meshData, objectData ) ); + break; + + case 'default': + meshes.push( buildDefaultMesh( meshData ) ); + break; + + default: + console.error( 'THREE.3MFLoader: Unsupported resource type.' ); + + } + + } + + if ( objectData.name ) { + + for ( let i = 0; i < meshes.length; i ++ ) { + + meshes[ i ].name = objectData.name; + + } + + } + + return meshes; + + } + + function getResourceType( pid, modelData ) { + + if ( modelData.resources.texture2dgroup[ pid ] !== undefined ) { + + return 'texture'; + + } else if ( modelData.resources.basematerials[ pid ] !== undefined ) { + + return 'material'; + + } else if ( modelData.resources.colorgroup[ pid ] !== undefined ) { + + return 'vertexColors'; + + } else if ( pid === 'default' ) { + + return 'default'; + + } else { + + return undefined; + + } + + } + + function analyzeObject( meshData, objectData ) { + + const resourceMap = {}; + + const triangleProperties = meshData[ 'triangleProperties' ]; + + const objectPid = objectData.pid; + + for ( let i = 0, l = triangleProperties.length; i < l; i ++ ) { + + const triangleProperty = triangleProperties[ i ]; + let pid = ( triangleProperty.pid !== undefined ) ? triangleProperty.pid : objectPid; + + if ( pid === undefined ) pid = 'default'; + + if ( resourceMap[ pid ] === undefined ) resourceMap[ pid ] = []; + + resourceMap[ pid ].push( triangleProperty ); + + } + + return resourceMap; + + } + + function buildGroup( meshData, objects, modelData, textureData, objectData ) { + + const group = new Group(); + + const resourceMap = analyzeObject( meshData, objectData ); + const meshes = buildMeshes( resourceMap, meshData, objects, modelData, textureData, objectData ); + + for ( let i = 0, l = meshes.length; i < l; i ++ ) { + + group.add( meshes[ i ] ); + + } + + return group; + + } + + function applyExtensions( extensions, meshData, modelXml ) { + + if ( ! extensions ) { + + return; + + } + + const availableExtensions = []; + const keys = Object.keys( extensions ); + + for ( let i = 0; i < keys.length; i ++ ) { + + const ns = keys[ i ]; + + for ( let j = 0; j < scope.availableExtensions.length; j ++ ) { + + const extension = scope.availableExtensions[ j ]; + + if ( extension.ns === ns ) { + + availableExtensions.push( extension ); + + } + + } + + } + + for ( let i = 0; i < availableExtensions.length; i ++ ) { + + const extension = availableExtensions[ i ]; + extension.apply( modelXml, extensions[ extension[ 'ns' ] ], meshData ); + + } + + } + + function getBuild( data, objects, modelData, textureData, objectData, builder ) { + + if ( data.build !== undefined ) return data.build; + + data.build = builder( data, objects, modelData, textureData, objectData ); + + return data.build; + + } + + function buildBasematerial( materialData, objects, modelData ) { + + let material; + + const displaypropertiesid = materialData.displaypropertiesid; + const pbmetallicdisplayproperties = modelData.resources.pbmetallicdisplayproperties; + + if ( displaypropertiesid !== null && pbmetallicdisplayproperties[ displaypropertiesid ] !== undefined ) { + + // metallic display property, use StandardMaterial + + const pbmetallicdisplayproperty = pbmetallicdisplayproperties[ displaypropertiesid ]; + const metallicData = pbmetallicdisplayproperty.data[ materialData.index ]; + + material = new MeshStandardMaterial( { flatShading: true, roughness: metallicData.roughness, metalness: metallicData.metallicness } ); + + } else { + + // otherwise use PhongMaterial + + material = new MeshPhongMaterial( { flatShading: true } ); + + } + + material.name = materialData.name; + + // displaycolor MUST be specified with a value of a 6 or 8 digit hexadecimal number, e.g. "#RRGGBB" or "#RRGGBBAA" + + const displaycolor = materialData.displaycolor; + + const color = displaycolor.substring( 0, 7 ); + material.color.setStyle( color ); + material.color.convertSRGBToLinear(); // displaycolor is in sRGB + + // process alpha if set + + if ( displaycolor.length === 9 ) { + + material.opacity = parseInt( displaycolor.charAt( 7 ) + displaycolor.charAt( 8 ), 16 ) / 255; + + } + + return material; + + } + + function buildComposite( compositeData, objects, modelData, textureData ) { + + const composite = new Group(); + + for ( let j = 0; j < compositeData.length; j ++ ) { + + const component = compositeData[ j ]; + let build = objects[ component.objectId ]; + + if ( build === undefined ) { + + buildObject( component.objectId, objects, modelData, textureData ); + build = objects[ component.objectId ]; + + } + + const object3D = build.clone(); + + // apply component transform + + const transform = component.transform; + + if ( transform ) { + + object3D.applyMatrix4( transform ); + + } + + composite.add( object3D ); + + } + + return composite; + + } + + function buildObject( objectId, objects, modelData, textureData ) { + + const objectData = modelData[ 'resources' ][ 'object' ][ objectId ]; + + if ( objectData[ 'mesh' ] ) { + + const meshData = objectData[ 'mesh' ]; + + const extensions = modelData[ 'extensions' ]; + const modelXml = modelData[ 'xml' ]; + + applyExtensions( extensions, meshData, modelXml ); + + objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildGroup ); + + } else { + + const compositeData = objectData[ 'components' ]; + + objects[ objectData.id ] = getBuild( compositeData, objects, modelData, textureData, objectData, buildComposite ); + + } + + if ( objectData.name ) { + + objects[ objectData.id ].name = objectData.name; + + } + + } + + function buildObjects( data3mf ) { + + const modelsData = data3mf.model; + const modelRels = data3mf.modelRels; + const objects = {}; + const modelsKeys = Object.keys( modelsData ); + const textureData = {}; + + // evaluate model relationships to textures + + if ( modelRels ) { + + for ( let i = 0, l = modelRels.length; i < l; i ++ ) { + + const modelRel = modelRels[ i ]; + const textureKey = modelRel.target.substring( 1 ); + + if ( data3mf.texture[ textureKey ] ) { + + textureData[ modelRel.target ] = data3mf.texture[ textureKey ]; + + } + + } + + } + + // start build + + for ( let i = 0; i < modelsKeys.length; i ++ ) { + + const modelsKey = modelsKeys[ i ]; + const modelData = modelsData[ modelsKey ]; + + const objectIds = Object.keys( modelData[ 'resources' ][ 'object' ] ); + + for ( let j = 0; j < objectIds.length; j ++ ) { + + const objectId = objectIds[ j ]; + + buildObject( objectId, objects, modelData, textureData ); + + } + + } + + return objects; + + } + + function fetch3DModelPart( rels ) { + + for ( let i = 0; i < rels.length; i ++ ) { + + const rel = rels[ i ]; + const extension = rel.target.split( '.' ).pop(); + + if ( extension.toLowerCase() === 'model' ) return rel; + + } + + } + + function build( objects, data3mf ) { + + const group = new Group(); + + const relationship = fetch3DModelPart( data3mf[ 'rels' ] ); + const buildData = data3mf.model[ relationship[ 'target' ].substring( 1 ) ][ 'build' ]; + + for ( let i = 0; i < buildData.length; i ++ ) { + + const buildItem = buildData[ i ]; + const object3D = objects[ buildItem[ 'objectId' ] ].clone(); + + // apply transform + + const transform = buildItem[ 'transform' ]; + + if ( transform ) { + + object3D.applyMatrix4( transform ); + + } + + group.add( object3D ); + + } + + return group; + + } + + const data3mf = loadDocument( data ); + const objects = buildObjects( data3mf ); + + return build( objects, data3mf ); + + } + + addExtension( extension ) { + + this.availableExtensions.push( extension ); + + } + +} + +export { ThreeMFLoader }; diff --git a/jsm/loaders/AMFLoader.js b/jsm/loaders/AMFLoader.js new file mode 100644 index 0000000..1d870cc --- /dev/null +++ b/jsm/loaders/AMFLoader.js @@ -0,0 +1,518 @@ +import { + BufferGeometry, + Color, + FileLoader, + Float32BufferAttribute, + Group, + Loader, + LoaderUtils, + Mesh, + MeshPhongMaterial +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +/** + * Description: Early release of an AMF Loader following the pattern of the + * example loaders in the three.js project. + * + * Usage: + * const loader = new AMFLoader(); + * loader.load('/path/to/project.amf', function(objecttree) { + * scene.add(objecttree); + * }); + * + * Materials now supported, material colors supported + * Zip support, requires fflate + * No constellation support (yet)! + * + */ + +class AMFLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + function loadDocument( data ) { + + let view = new DataView( data ); + const magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) ); + + if ( magic === 'PK' ) { + + let zip = null; + let file = null; + + console.log( 'THREE.AMFLoader: Loading Zip' ); + + try { + + zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef + + } catch ( e ) { + + if ( e instanceof ReferenceError ) { + + console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' ); + return null; + + } + + } + + for ( file in zip ) { + + if ( file.toLowerCase().slice( - 4 ) === '.amf' ) { + + break; + + } + + } + + console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file ); + view = new DataView( zip[ file ].buffer ); + + } + + const fileText = LoaderUtils.decodeText( view ); + const xmlData = new DOMParser().parseFromString( fileText, 'application/xml' ); + + if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) { + + console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' ); + return null; + + } + + return xmlData; + + } + + function loadDocumentScale( node ) { + + let scale = 1.0; + let unit = 'millimeter'; + + if ( node.documentElement.attributes.unit !== undefined ) { + + unit = node.documentElement.attributes.unit.value.toLowerCase(); + + } + + const scaleUnits = { + millimeter: 1.0, + inch: 25.4, + feet: 304.8, + meter: 1000.0, + micron: 0.001 + }; + + if ( scaleUnits[ unit ] !== undefined ) { + + scale = scaleUnits[ unit ]; + + } + + console.log( 'THREE.AMFLoader: Unit scale: ' + scale ); + return scale; + + } + + function loadMaterials( node ) { + + let matName = 'AMF Material'; + const matId = node.attributes.id.textContent; + let color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; + + let loadedMaterial = null; + + for ( let i = 0; i < node.childNodes.length; i ++ ) { + + const matChildEl = node.childNodes[ i ]; + + if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) { + + if ( matChildEl.attributes.type.value === 'name' ) { + + matName = matChildEl.textContent; + + } + + } else if ( matChildEl.nodeName === 'color' ) { + + color = loadColor( matChildEl ); + + } + + } + + loadedMaterial = new MeshPhongMaterial( { + flatShading: true, + color: new Color( color.r, color.g, color.b ), + name: matName + } ); + + if ( color.a !== 1.0 ) { + + loadedMaterial.transparent = true; + loadedMaterial.opacity = color.a; + + } + + return { id: matId, material: loadedMaterial }; + + } + + function loadColor( node ) { + + const color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; + + for ( let i = 0; i < node.childNodes.length; i ++ ) { + + const matColor = node.childNodes[ i ]; + + if ( matColor.nodeName === 'r' ) { + + color.r = matColor.textContent; + + } else if ( matColor.nodeName === 'g' ) { + + color.g = matColor.textContent; + + } else if ( matColor.nodeName === 'b' ) { + + color.b = matColor.textContent; + + } else if ( matColor.nodeName === 'a' ) { + + color.a = matColor.textContent; + + } + + } + + return color; + + } + + function loadMeshVolume( node ) { + + const volume = { name: '', triangles: [], materialid: null }; + + let currVolumeNode = node.firstElementChild; + + if ( node.attributes.materialid !== undefined ) { + + volume.materialId = node.attributes.materialid.nodeValue; + + } + + while ( currVolumeNode ) { + + if ( currVolumeNode.nodeName === 'metadata' ) { + + if ( currVolumeNode.attributes.type !== undefined ) { + + if ( currVolumeNode.attributes.type.value === 'name' ) { + + volume.name = currVolumeNode.textContent; + + } + + } + + } else if ( currVolumeNode.nodeName === 'triangle' ) { + + const v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent; + const v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent; + const v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent; + + volume.triangles.push( v1, v2, v3 ); + + } + + currVolumeNode = currVolumeNode.nextElementSibling; + + } + + return volume; + + } + + function loadMeshVertices( node ) { + + const vertArray = []; + const normalArray = []; + let currVerticesNode = node.firstElementChild; + + while ( currVerticesNode ) { + + if ( currVerticesNode.nodeName === 'vertex' ) { + + let vNode = currVerticesNode.firstElementChild; + + while ( vNode ) { + + if ( vNode.nodeName === 'coordinates' ) { + + const x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent; + const y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent; + const z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent; + + vertArray.push( x, y, z ); + + } else if ( vNode.nodeName === 'normal' ) { + + const nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent; + const ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent; + const nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent; + + normalArray.push( nx, ny, nz ); + + } + + vNode = vNode.nextElementSibling; + + } + + } + + currVerticesNode = currVerticesNode.nextElementSibling; + + } + + return { 'vertices': vertArray, 'normals': normalArray }; + + } + + function loadObject( node ) { + + const objId = node.attributes.id.textContent; + const loadedObject = { name: 'amfobject', meshes: [] }; + let currColor = null; + let currObjNode = node.firstElementChild; + + while ( currObjNode ) { + + if ( currObjNode.nodeName === 'metadata' ) { + + if ( currObjNode.attributes.type !== undefined ) { + + if ( currObjNode.attributes.type.value === 'name' ) { + + loadedObject.name = currObjNode.textContent; + + } + + } + + } else if ( currObjNode.nodeName === 'color' ) { + + currColor = loadColor( currObjNode ); + + } else if ( currObjNode.nodeName === 'mesh' ) { + + let currMeshNode = currObjNode.firstElementChild; + const mesh = { vertices: [], normals: [], volumes: [], color: currColor }; + + while ( currMeshNode ) { + + if ( currMeshNode.nodeName === 'vertices' ) { + + const loadedVertices = loadMeshVertices( currMeshNode ); + + mesh.normals = mesh.normals.concat( loadedVertices.normals ); + mesh.vertices = mesh.vertices.concat( loadedVertices.vertices ); + + } else if ( currMeshNode.nodeName === 'volume' ) { + + mesh.volumes.push( loadMeshVolume( currMeshNode ) ); + + } + + currMeshNode = currMeshNode.nextElementSibling; + + } + + loadedObject.meshes.push( mesh ); + + } + + currObjNode = currObjNode.nextElementSibling; + + } + + return { 'id': objId, 'obj': loadedObject }; + + } + + const xmlData = loadDocument( data ); + let amfName = ''; + let amfAuthor = ''; + const amfScale = loadDocumentScale( xmlData ); + const amfMaterials = {}; + const amfObjects = {}; + const childNodes = xmlData.documentElement.childNodes; + + let i, j; + + for ( i = 0; i < childNodes.length; i ++ ) { + + const child = childNodes[ i ]; + + if ( child.nodeName === 'metadata' ) { + + if ( child.attributes.type !== undefined ) { + + if ( child.attributes.type.value === 'name' ) { + + amfName = child.textContent; + + } else if ( child.attributes.type.value === 'author' ) { + + amfAuthor = child.textContent; + + } + + } + + } else if ( child.nodeName === 'material' ) { + + const loadedMaterial = loadMaterials( child ); + + amfMaterials[ loadedMaterial.id ] = loadedMaterial.material; + + } else if ( child.nodeName === 'object' ) { + + const loadedObject = loadObject( child ); + + amfObjects[ loadedObject.id ] = loadedObject.obj; + + } + + } + + const sceneObject = new Group(); + const defaultMaterial = new MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } ); + + sceneObject.name = amfName; + sceneObject.userData.author = amfAuthor; + sceneObject.userData.loader = 'AMF'; + + for ( const id in amfObjects ) { + + const part = amfObjects[ id ]; + const meshes = part.meshes; + const newObject = new Group(); + newObject.name = part.name || ''; + + for ( i = 0; i < meshes.length; i ++ ) { + + let objDefaultMaterial = defaultMaterial; + const mesh = meshes[ i ]; + const vertices = new Float32BufferAttribute( mesh.vertices, 3 ); + let normals = null; + + if ( mesh.normals.length ) { + + normals = new Float32BufferAttribute( mesh.normals, 3 ); + + } + + if ( mesh.color ) { + + const color = mesh.color; + + objDefaultMaterial = defaultMaterial.clone(); + objDefaultMaterial.color = new Color( color.r, color.g, color.b ); + + if ( color.a !== 1.0 ) { + + objDefaultMaterial.transparent = true; + objDefaultMaterial.opacity = color.a; + + } + + } + + const volumes = mesh.volumes; + + for ( j = 0; j < volumes.length; j ++ ) { + + const volume = volumes[ j ]; + const newGeometry = new BufferGeometry(); + let material = objDefaultMaterial; + + newGeometry.setIndex( volume.triangles ); + newGeometry.setAttribute( 'position', vertices.clone() ); + + if ( normals ) { + + newGeometry.setAttribute( 'normal', normals.clone() ); + + } + + if ( amfMaterials[ volume.materialId ] !== undefined ) { + + material = amfMaterials[ volume.materialId ]; + + } + + newGeometry.scale( amfScale, amfScale, amfScale ); + newObject.add( new Mesh( newGeometry, material.clone() ) ); + + } + + } + + sceneObject.add( newObject ); + + } + + return sceneObject; + + } + +} + +export { AMFLoader }; diff --git a/jsm/loaders/BVHLoader.js b/jsm/loaders/BVHLoader.js new file mode 100644 index 0000000..d30d615 --- /dev/null +++ b/jsm/loaders/BVHLoader.js @@ -0,0 +1,437 @@ +import { + AnimationClip, + Bone, + FileLoader, + Loader, + Quaternion, + QuaternionKeyframeTrack, + Skeleton, + Vector3, + VectorKeyframeTrack +} from 'three'; + +/** + * Description: reads BVH files and outputs a single Skeleton and an AnimationClip + * + * Currently only supports bvh files containing a single root. + * + */ + +class BVHLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.animateBonePositions = true; + this.animateBoneRotations = true; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( text ) { + + /* + reads a string array (lines) from a BVH file + and outputs a skeleton structure including motion data + + returns thee root node: + { name: '', channels: [], children: [] } + */ + function readBvh( lines ) { + + // read model structure + + if ( nextLine( lines ) !== 'HIERARCHY' ) { + + console.error( 'THREE.BVHLoader: HIERARCHY expected.' ); + + } + + const list = []; // collects flat array of all bones + const root = readNode( lines, nextLine( lines ), list ); + + // read motion data + + if ( nextLine( lines ) !== 'MOTION' ) { + + console.error( 'THREE.BVHLoader: MOTION expected.' ); + + } + + // number of frames + + let tokens = nextLine( lines ).split( /[\s]+/ ); + const numFrames = parseInt( tokens[ 1 ] ); + + if ( isNaN( numFrames ) ) { + + console.error( 'THREE.BVHLoader: Failed to read number of frames.' ); + + } + + // frame time + + tokens = nextLine( lines ).split( /[\s]+/ ); + const frameTime = parseFloat( tokens[ 2 ] ); + + if ( isNaN( frameTime ) ) { + + console.error( 'THREE.BVHLoader: Failed to read frame time.' ); + + } + + // read frame data line by line + + for ( let i = 0; i < numFrames; i ++ ) { + + tokens = nextLine( lines ).split( /[\s]+/ ); + readFrameData( tokens, i * frameTime, root ); + + } + + return list; + + } + + /* + Recursively reads data from a single frame into the bone hierarchy. + The passed bone hierarchy has to be structured in the same order as the BVH file. + keyframe data is stored in bone.frames. + + - data: splitted string array (frame values), values are shift()ed so + this should be empty after parsing the whole hierarchy. + - frameTime: playback time for this keyframe. + - bone: the bone to read frame data from. + */ + function readFrameData( data, frameTime, bone ) { + + // end sites have no motion data + + if ( bone.type === 'ENDSITE' ) return; + + // add keyframe + + const keyframe = { + time: frameTime, + position: new Vector3(), + rotation: new Quaternion() + }; + + bone.frames.push( keyframe ); + + const quat = new Quaternion(); + + const vx = new Vector3( 1, 0, 0 ); + const vy = new Vector3( 0, 1, 0 ); + const vz = new Vector3( 0, 0, 1 ); + + // parse values for each channel in node + + for ( let i = 0; i < bone.channels.length; i ++ ) { + + switch ( bone.channels[ i ] ) { + + case 'Xposition': + keyframe.position.x = parseFloat( data.shift().trim() ); + break; + case 'Yposition': + keyframe.position.y = parseFloat( data.shift().trim() ); + break; + case 'Zposition': + keyframe.position.z = parseFloat( data.shift().trim() ); + break; + case 'Xrotation': + quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 ); + keyframe.rotation.multiply( quat ); + break; + case 'Yrotation': + quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 ); + keyframe.rotation.multiply( quat ); + break; + case 'Zrotation': + quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 ); + keyframe.rotation.multiply( quat ); + break; + default: + console.warn( 'THREE.BVHLoader: Invalid channel type.' ); + + } + + } + + // parse child nodes + + for ( let i = 0; i < bone.children.length; i ++ ) { + + readFrameData( data, frameTime, bone.children[ i ] ); + + } + + } + + /* + Recursively parses the HIERACHY section of the BVH file + + - lines: all lines of the file. lines are consumed as we go along. + - firstline: line containing the node type and name e.g. 'JOINT hip' + - list: collects a flat list of nodes + + returns: a BVH node including children + */ + function readNode( lines, firstline, list ) { + + const node = { name: '', type: '', frames: [] }; + list.push( node ); + + // parse node type and name + + let tokens = firstline.split( /[\s]+/ ); + + if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) { + + node.type = 'ENDSITE'; + node.name = 'ENDSITE'; // bvh end sites have no name + + } else { + + node.name = tokens[ 1 ]; + node.type = tokens[ 0 ].toUpperCase(); + + } + + if ( nextLine( lines ) !== '{' ) { + + console.error( 'THREE.BVHLoader: Expected opening { after type & name' ); + + } + + // parse OFFSET + + tokens = nextLine( lines ).split( /[\s]+/ ); + + if ( tokens[ 0 ] !== 'OFFSET' ) { + + console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] ); + + } + + if ( tokens.length !== 4 ) { + + console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' ); + + } + + const offset = new Vector3( + parseFloat( tokens[ 1 ] ), + parseFloat( tokens[ 2 ] ), + parseFloat( tokens[ 3 ] ) + ); + + if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) { + + console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' ); + + } + + node.offset = offset; + + // parse CHANNELS definitions + + if ( node.type !== 'ENDSITE' ) { + + tokens = nextLine( lines ).split( /[\s]+/ ); + + if ( tokens[ 0 ] !== 'CHANNELS' ) { + + console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' ); + + } + + const numChannels = parseInt( tokens[ 1 ] ); + node.channels = tokens.splice( 2, numChannels ); + node.children = []; + + } + + // read children + + while ( true ) { + + const line = nextLine( lines ); + + if ( line === '}' ) { + + return node; + + } else { + + node.children.push( readNode( lines, line, list ) ); + + } + + } + + } + + /* + recursively converts the internal bvh node structure to a Bone hierarchy + + source: the bvh root node + list: pass an empty array, collects a flat list of all converted THREE.Bones + + returns the root Bone + */ + function toTHREEBone( source, list ) { + + const bone = new Bone(); + list.push( bone ); + + bone.position.add( source.offset ); + bone.name = source.name; + + if ( source.type !== 'ENDSITE' ) { + + for ( let i = 0; i < source.children.length; i ++ ) { + + bone.add( toTHREEBone( source.children[ i ], list ) ); + + } + + } + + return bone; + + } + + /* + builds a AnimationClip from the keyframe data saved in each bone. + + bone: bvh root node + + returns: a AnimationClip containing position and quaternion tracks + */ + function toTHREEAnimation( bones ) { + + const tracks = []; + + // create a position and quaternion animation track for each node + + for ( let i = 0; i < bones.length; i ++ ) { + + const bone = bones[ i ]; + + if ( bone.type === 'ENDSITE' ) + continue; + + // track data + + const times = []; + const positions = []; + const rotations = []; + + for ( let j = 0; j < bone.frames.length; j ++ ) { + + const frame = bone.frames[ j ]; + + times.push( frame.time ); + + // the animation system animates the position property, + // so we have to add the joint offset to all values + + positions.push( frame.position.x + bone.offset.x ); + positions.push( frame.position.y + bone.offset.y ); + positions.push( frame.position.z + bone.offset.z ); + + rotations.push( frame.rotation.x ); + rotations.push( frame.rotation.y ); + rotations.push( frame.rotation.z ); + rotations.push( frame.rotation.w ); + + } + + if ( scope.animateBonePositions ) { + + tracks.push( new VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) ); + + } + + if ( scope.animateBoneRotations ) { + + tracks.push( new QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) ); + + } + + } + + return new AnimationClip( 'animation', - 1, tracks ); + + } + + /* + returns the next non-empty line in lines + */ + function nextLine( lines ) { + + let line; + // skip empty lines + while ( ( line = lines.shift().trim() ).length === 0 ) { } + + return line; + + } + + const scope = this; + + const lines = text.split( /[\r\n]+/g ); + + const bones = readBvh( lines ); + + const threeBones = []; + toTHREEBone( bones[ 0 ], threeBones ); + + const threeClip = toTHREEAnimation( bones ); + + return { + skeleton: new Skeleton( threeBones ), + clip: threeClip + }; + + } + +} + +export { BVHLoader }; diff --git a/jsm/loaders/BasisTextureLoader.js b/jsm/loaders/BasisTextureLoader.js new file mode 100644 index 0000000..b9ca9b7 --- /dev/null +++ b/jsm/loaders/BasisTextureLoader.js @@ -0,0 +1,790 @@ +import { + CompressedTexture, + FileLoader, + LinearFilter, + LinearMipmapLinearFilter, + Loader, + RGBAFormat, + RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format, + RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format, + UnsignedByteType +} from 'three'; + +/** + * Loader for Basis Universal GPU Texture Codec. + * + * Basis Universal is a "supercompressed" GPU texture and texture video + * compression system that outputs a highly compressed intermediate file format + * (.basis) that can be quickly transcoded to a wide variety of GPU texture + * compression formats. + * + * This loader parallelizes the transcoding process across a configurable number + * of web workers, before transferring the transcoded compressed texture back + * to the main thread. + */ + +const _taskCache = new WeakMap(); + +class BasisTextureLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.transcoderPath = ''; + this.transcoderBinary = null; + this.transcoderPending = null; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + this.workerConfig = null; + + console.warn( + + 'THREE.BasisTextureLoader: This loader is deprecated, and will be removed in a future release. ' + + 'Instead, use Basis Universal compression in KTX2 (.ktx2) files with THREE.KTX2Loader.' + + ); + + } + + setTranscoderPath( path ) { + + this.transcoderPath = path; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + detectSupport( renderer ) { + + this.workerConfig = { + astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), + etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), + dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), + bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), + pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) + || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) + }; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setResponseType( 'arraybuffer' ); + loader.setWithCredentials( this.withCredentials ); + + const texture = new CompressedTexture(); + + loader.load( url, ( buffer ) => { + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + return cachedTask.promise.then( onLoad ).catch( onError ); + + } + + this._createTexture( [ buffer ] ) + .then( function ( _texture ) { + + texture.copy( _texture ); + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } ) + .catch( onError ); + + }, onProgress, onError ); + + return texture; + + } + + /** Low-level transcoding API, exposed for use by KTX2Loader. */ + parseInternalAsync( options ) { + + const { levels } = options; + + const buffers = new Set(); + + for ( let i = 0; i < levels.length; i ++ ) { + + buffers.add( levels[ i ].data.buffer ); + + } + + return this._createTexture( Array.from( buffers ), { ...options, lowLevel: true } ); + + } + + /** + * @param {ArrayBuffer[]} buffers + * @param {object?} config + * @return {Promise} + */ + _createTexture( buffers, config = {} ) { + + let worker; + let taskID; + + const taskConfig = config; + let taskCost = 0; + + for ( let i = 0; i < buffers.length; i ++ ) { + + taskCost += buffers[ i ].byteLength; + + } + + const texturePending = this._allocateWorker( taskCost ) + .then( ( _worker ) => { + + worker = _worker; + taskID = this.workerNextTaskID ++; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'transcode', id: taskID, buffers: buffers, taskConfig: taskConfig }, buffers ); + + } ); + + } ) + .then( ( message ) => { + + const { mipmaps, width, height, format } = message; + + const texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + + return texture; + + } ); + + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + texturePending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + worker._taskLoad -= taskCost; + delete worker._callbacks[ taskID ]; + + } + + } ); + + // Cache the task result. + _taskCache.set( buffers[ 0 ], { promise: texturePending } ); + + return texturePending; + + } + + _initTranscoder() { + + if ( ! this.transcoderPending ) { + + // Load transcoder wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.transcoderPath ); + jsLoader.setWithCredentials( this.withCredentials ); + const jsContent = new Promise( ( resolve, reject ) => { + + jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject ); + + } ); + + // Load transcoder WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.transcoderPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + binaryLoader.setWithCredentials( this.withCredentials ); + const binaryContent = new Promise( ( resolve, reject ) => { + + binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject ); + + } ); + + this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { + + const fn = BasisTextureLoader.BasisWorker.toString(); + + const body = [ + '/* constants */', + 'let _EngineFormat = ' + JSON.stringify( BasisTextureLoader.EngineFormat ), + 'let _TranscoderFormat = ' + JSON.stringify( BasisTextureLoader.TranscoderFormat ), + 'let _BasisFormat = ' + JSON.stringify( BasisTextureLoader.BasisFormat ), + '/* basis_transcoder.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + this.transcoderBinary = binaryContent; + + } ); + + } + + return this.transcoderPending; + + } + + _allocateWorker( taskCost ) { + + return this._initTranscoder().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskLoad = 0; + + worker.postMessage( { + type: 'init', + config: this.workerConfig, + transcoderBinary: this.transcoderBinary, + } ); + + worker.onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'transcode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' ); + + } + + }; + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + + worker._taskLoad += taskCost; + + return worker; + + } ); + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; i ++ ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + return this; + + } + +} + +/* 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; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + + 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 ); // eslint-disable-line no-undef + + } ).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/jsm/loaders/ColladaLoader.js b/jsm/loaders/ColladaLoader.js new file mode 100644 index 0000000..1b3ef85 --- /dev/null +++ b/jsm/loaders/ColladaLoader.js @@ -0,0 +1,4091 @@ +import { + AmbientLight, + AnimationClip, + Bone, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DirectionalLight, + DoubleSide, + Euler, + FileLoader, + Float32BufferAttribute, + FrontSide, + Group, + Line, + LineBasicMaterial, + LineSegments, + Loader, + LoaderUtils, + MathUtils, + Matrix4, + Mesh, + MeshBasicMaterial, + MeshLambertMaterial, + MeshPhongMaterial, + OrthographicCamera, + PerspectiveCamera, + PointLight, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Scene, + Skeleton, + SkinnedMesh, + SpotLight, + TextureLoader, + Vector2, + Vector3, + VectorKeyframeTrack, + sRGBEncoding +} from 'three'; +import { TGALoader } from '../loaders/TGALoader.js'; + +class ColladaLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( scope.path === '' ) ? LoaderUtils.extractUrlBase( url ) : scope.path; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( text, path ) { + + function getElementsByTagName( xml, name ) { + + // Non recursive xml.getElementsByTagName() ... + + const array = []; + const childNodes = xml.childNodes; + + for ( let i = 0, l = childNodes.length; i < l; i ++ ) { + + const child = childNodes[ i ]; + + if ( child.nodeName === name ) { + + array.push( child ); + + } + + } + + return array; + + } + + function parseStrings( text ) { + + if ( text.length === 0 ) return []; + + const parts = text.trim().split( /\s+/ ); + const array = new Array( parts.length ); + + for ( let i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parts[ i ]; + + } + + return array; + + } + + function parseFloats( text ) { + + if ( text.length === 0 ) return []; + + const parts = text.trim().split( /\s+/ ); + const array = new Array( parts.length ); + + for ( let i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parseFloat( parts[ i ] ); + + } + + return array; + + } + + function parseInts( text ) { + + if ( text.length === 0 ) return []; + + const parts = text.trim().split( /\s+/ ); + const array = new Array( parts.length ); + + for ( let i = 0, l = parts.length; i < l; i ++ ) { + + array[ i ] = parseInt( parts[ i ] ); + + } + + return array; + + } + + function parseId( text ) { + + return text.substring( 1 ); + + } + + function generateId() { + + return 'three_default_' + ( count ++ ); + + } + + function isEmpty( object ) { + + return Object.keys( object ).length === 0; + + } + + // asset + + function parseAsset( xml ) { + + return { + unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ), + upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] ) + }; + + } + + function parseAssetUnit( xml ) { + + if ( ( xml !== undefined ) && ( xml.hasAttribute( 'meter' ) === true ) ) { + + return parseFloat( xml.getAttribute( 'meter' ) ); + + } else { + + return 1; // default 1 meter + + } + + } + + function parseAssetUpAxis( xml ) { + + return xml !== undefined ? xml.textContent : 'Y_UP'; + + } + + // library + + function parseLibrary( xml, libraryName, nodeName, parser ) { + + const library = getElementsByTagName( xml, libraryName )[ 0 ]; + + if ( library !== undefined ) { + + const elements = getElementsByTagName( library, nodeName ); + + for ( let i = 0; i < elements.length; i ++ ) { + + parser( elements[ i ] ); + + } + + } + + } + + function buildLibrary( data, builder ) { + + for ( const name in data ) { + + const object = data[ name ]; + object.build = builder( data[ name ] ); + + } + + } + + // get + + function getBuild( data, builder ) { + + if ( data.build !== undefined ) return data.build; + + data.build = builder( data ); + + return data.build; + + } + + // animation + + function parseAnimation( xml ) { + + const data = { + sources: {}, + samplers: {}, + channels: {} + }; + + let hasChildren = false; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + let id; + + switch ( child.nodeName ) { + + case 'source': + id = child.getAttribute( 'id' ); + data.sources[ id ] = parseSource( child ); + break; + + case 'sampler': + id = child.getAttribute( 'id' ); + data.samplers[ id ] = parseAnimationSampler( child ); + break; + + case 'channel': + id = child.getAttribute( 'target' ); + data.channels[ id ] = parseAnimationChannel( child ); + break; + + case 'animation': + // hierarchy of related animations + parseAnimation( child ); + hasChildren = true; + break; + + default: + console.log( child ); + + } + + } + + if ( hasChildren === false ) { + + // since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment + + library.animations[ xml.getAttribute( 'id' ) || MathUtils.generateUUID() ] = data; + + } + + } + + function parseAnimationSampler( xml ) { + + const data = { + inputs: {}, + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const id = parseId( child.getAttribute( 'source' ) ); + const semantic = child.getAttribute( 'semantic' ); + data.inputs[ semantic ] = id; + break; + + } + + } + + return data; + + } + + function parseAnimationChannel( xml ) { + + const data = {}; + + const target = xml.getAttribute( 'target' ); + + // parsing SID Addressing Syntax + + let parts = target.split( '/' ); + + const id = parts.shift(); + let sid = parts.shift(); + + // check selection syntax + + const arraySyntax = ( sid.indexOf( '(' ) !== - 1 ); + const memberSyntax = ( sid.indexOf( '.' ) !== - 1 ); + + if ( memberSyntax ) { + + // member selection access + + parts = sid.split( '.' ); + sid = parts.shift(); + data.member = parts.shift(); + + } else if ( arraySyntax ) { + + // array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices. + + const indices = sid.split( '(' ); + sid = indices.shift(); + + for ( let i = 0; i < indices.length; i ++ ) { + + indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) ); + + } + + data.indices = indices; + + } + + data.id = id; + data.sid = sid; + + data.arraySyntax = arraySyntax; + data.memberSyntax = memberSyntax; + + data.sampler = parseId( xml.getAttribute( 'source' ) ); + + return data; + + } + + function buildAnimation( data ) { + + const tracks = []; + + const channels = data.channels; + const samplers = data.samplers; + const sources = data.sources; + + for ( const target in channels ) { + + if ( channels.hasOwnProperty( target ) ) { + + const channel = channels[ target ]; + const sampler = samplers[ channel.sampler ]; + + const inputId = sampler.inputs.INPUT; + const outputId = sampler.inputs.OUTPUT; + + const inputSource = sources[ inputId ]; + const outputSource = sources[ outputId ]; + + const animation = buildAnimationChannel( channel, inputSource, outputSource ); + + createKeyframeTracks( animation, tracks ); + + } + + } + + return tracks; + + } + + function getAnimation( id ) { + + return getBuild( library.animations[ id ], buildAnimation ); + + } + + function buildAnimationChannel( channel, inputSource, outputSource ) { + + const node = library.nodes[ channel.id ]; + const object3D = getNode( node.id ); + + const transform = node.transforms[ channel.sid ]; + const defaultMatrix = node.matrix.clone().transpose(); + + let time, stride; + let i, il, j, jl; + + const data = {}; + + // the collada spec allows the animation of data in various ways. + // depending on the transform type (matrix, translate, rotate, scale), we execute different logic + + switch ( transform ) { + + case 'matrix': + + for ( i = 0, il = inputSource.array.length; i < il; i ++ ) { + + time = inputSource.array[ i ]; + stride = i * outputSource.stride; + + if ( data[ time ] === undefined ) data[ time ] = {}; + + if ( channel.arraySyntax === true ) { + + const value = outputSource.array[ stride ]; + const index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ]; + + data[ time ][ index ] = value; + + } else { + + for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) { + + data[ time ][ j ] = outputSource.array[ stride + j ]; + + } + + } + + } + + break; + + case 'translate': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + case 'rotate': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + case 'scale': + console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform ); + break; + + } + + const keyframes = prepareAnimationData( data, defaultMatrix ); + + const animation = { + name: object3D.uuid, + keyframes: keyframes + }; + + return animation; + + } + + function prepareAnimationData( data, defaultMatrix ) { + + const keyframes = []; + + // transfer data into a sortable array + + for ( const time in data ) { + + keyframes.push( { time: parseFloat( time ), value: data[ time ] } ); + + } + + // ensure keyframes are sorted by time + + keyframes.sort( ascending ); + + // now we clean up all animation data, so we can use them for keyframe tracks + + for ( let i = 0; i < 16; i ++ ) { + + transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] ); + + } + + return keyframes; + + // array sort function + + function ascending( a, b ) { + + return a.time - b.time; + + } + + } + + const position = new Vector3(); + const scale = new Vector3(); + const quaternion = new Quaternion(); + + function createKeyframeTracks( animation, tracks ) { + + const keyframes = animation.keyframes; + const name = animation.name; + + const times = []; + const positionData = []; + const quaternionData = []; + const scaleData = []; + + for ( let i = 0, l = keyframes.length; i < l; i ++ ) { + + const keyframe = keyframes[ i ]; + + const time = keyframe.time; + const value = keyframe.value; + + matrix.fromArray( value ).transpose(); + matrix.decompose( position, quaternion, scale ); + + times.push( time ); + positionData.push( position.x, position.y, position.z ); + quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w ); + scaleData.push( scale.x, scale.y, scale.z ); + + } + + if ( positionData.length > 0 ) tracks.push( new VectorKeyframeTrack( name + '.position', times, positionData ) ); + if ( quaternionData.length > 0 ) tracks.push( new QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) ); + if ( scaleData.length > 0 ) tracks.push( new VectorKeyframeTrack( name + '.scale', times, scaleData ) ); + + return tracks; + + } + + function transformAnimationData( keyframes, property, defaultValue ) { + + let keyframe; + + let empty = true; + let i, l; + + // check, if values of a property are missing in our keyframes + + for ( i = 0, l = keyframes.length; i < l; i ++ ) { + + keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] === undefined ) { + + keyframe.value[ property ] = null; // mark as missing + + } else { + + empty = false; + + } + + } + + if ( empty === true ) { + + // no values at all, so we set a default value + + for ( i = 0, l = keyframes.length; i < l; i ++ ) { + + keyframe = keyframes[ i ]; + + keyframe.value[ property ] = defaultValue; + + } + + } else { + + // filling gaps + + createMissingKeyframes( keyframes, property ); + + } + + } + + function createMissingKeyframes( keyframes, property ) { + + let prev, next; + + for ( let i = 0, l = keyframes.length; i < l; i ++ ) { + + const keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] === null ) { + + prev = getPrev( keyframes, i, property ); + next = getNext( keyframes, i, property ); + + if ( prev === null ) { + + keyframe.value[ property ] = next.value[ property ]; + continue; + + } + + if ( next === null ) { + + keyframe.value[ property ] = prev.value[ property ]; + continue; + + } + + interpolate( keyframe, prev, next, property ); + + } + + } + + } + + function getPrev( keyframes, i, property ) { + + while ( i >= 0 ) { + + const keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] !== null ) return keyframe; + + i --; + + } + + return null; + + } + + function getNext( keyframes, i, property ) { + + while ( i < keyframes.length ) { + + const keyframe = keyframes[ i ]; + + if ( keyframe.value[ property ] !== null ) return keyframe; + + i ++; + + } + + return null; + + } + + function interpolate( key, prev, next, property ) { + + if ( ( next.time - prev.time ) === 0 ) { + + key.value[ property ] = prev.value[ property ]; + return; + + } + + key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ]; + + } + + // animation clips + + function parseAnimationClip( xml ) { + + const data = { + name: xml.getAttribute( 'id' ) || 'default', + start: parseFloat( xml.getAttribute( 'start' ) || 0 ), + end: parseFloat( xml.getAttribute( 'end' ) || 0 ), + animations: [] + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'instance_animation': + data.animations.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + } + + } + + library.clips[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildAnimationClip( data ) { + + const tracks = []; + + const name = data.name; + const duration = ( data.end - data.start ) || - 1; + const animations = data.animations; + + for ( let i = 0, il = animations.length; i < il; i ++ ) { + + const animationTracks = getAnimation( animations[ i ] ); + + for ( let j = 0, jl = animationTracks.length; j < jl; j ++ ) { + + tracks.push( animationTracks[ j ] ); + + } + + } + + return new AnimationClip( name, duration, tracks ); + + } + + function getAnimationClip( id ) { + + return getBuild( library.clips[ id ], buildAnimationClip ); + + } + + // controller + + function parseController( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'skin': + // there is exactly one skin per controller + data.id = parseId( child.getAttribute( 'source' ) ); + data.skin = parseSkin( child ); + break; + + case 'morph': + data.id = parseId( child.getAttribute( 'source' ) ); + console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' ); + break; + + } + + } + + library.controllers[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseSkin( xml ) { + + const data = { + sources: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_shape_matrix': + data.bindShapeMatrix = parseFloats( child.textContent ); + break; + + case 'source': + const id = child.getAttribute( 'id' ); + data.sources[ id ] = parseSource( child ); + break; + + case 'joints': + data.joints = parseJoints( child ); + break; + + case 'vertex_weights': + data.vertexWeights = parseVertexWeights( child ); + break; + + } + + } + + return data; + + } + + function parseJoints( xml ) { + + const data = { + inputs: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const semantic = child.getAttribute( 'semantic' ); + const id = parseId( child.getAttribute( 'source' ) ); + data.inputs[ semantic ] = id; + break; + + } + + } + + return data; + + } + + function parseVertexWeights( xml ) { + + const data = { + inputs: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const semantic = child.getAttribute( 'semantic' ); + const id = parseId( child.getAttribute( 'source' ) ); + const offset = parseInt( child.getAttribute( 'offset' ) ); + data.inputs[ semantic ] = { id: id, offset: offset }; + break; + + case 'vcount': + data.vcount = parseInts( child.textContent ); + break; + + case 'v': + data.v = parseInts( child.textContent ); + break; + + } + + } + + return data; + + } + + function buildController( data ) { + + const build = { + id: data.id + }; + + const geometry = library.geometries[ build.id ]; + + if ( data.skin !== undefined ) { + + build.skin = buildSkin( data.skin ); + + // we enhance the 'sources' property of the corresponding geometry with our skin data + + geometry.sources.skinIndices = build.skin.indices; + geometry.sources.skinWeights = build.skin.weights; + + } + + return build; + + } + + function buildSkin( data ) { + + const BONE_LIMIT = 4; + + const build = { + joints: [], // this must be an array to preserve the joint order + indices: { + array: [], + stride: BONE_LIMIT + }, + weights: { + array: [], + stride: BONE_LIMIT + } + }; + + const sources = data.sources; + const vertexWeights = data.vertexWeights; + + const vcount = vertexWeights.vcount; + const v = vertexWeights.v; + const jointOffset = vertexWeights.inputs.JOINT.offset; + const weightOffset = vertexWeights.inputs.WEIGHT.offset; + + const jointSource = data.sources[ data.joints.inputs.JOINT ]; + const inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ]; + + const weights = sources[ vertexWeights.inputs.WEIGHT.id ].array; + let stride = 0; + + let i, j, l; + + // procces skin data for each vertex + + for ( i = 0, l = vcount.length; i < l; i ++ ) { + + const jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex + const vertexSkinData = []; + + for ( j = 0; j < jointCount; j ++ ) { + + const skinIndex = v[ stride + jointOffset ]; + const weightId = v[ stride + weightOffset ]; + const skinWeight = weights[ weightId ]; + + vertexSkinData.push( { index: skinIndex, weight: skinWeight } ); + + stride += 2; + + } + + // we sort the joints in descending order based on the weights. + // this ensures, we only procced the most important joints of the vertex + + vertexSkinData.sort( descending ); + + // now we provide for each vertex a set of four index and weight values. + // the order of the skin data matches the order of vertices + + for ( j = 0; j < BONE_LIMIT; j ++ ) { + + const d = vertexSkinData[ j ]; + + if ( d !== undefined ) { + + build.indices.array.push( d.index ); + build.weights.array.push( d.weight ); + + } else { + + build.indices.array.push( 0 ); + build.weights.array.push( 0 ); + + } + + } + + } + + // setup bind matrix + + if ( data.bindShapeMatrix ) { + + build.bindMatrix = new Matrix4().fromArray( data.bindShapeMatrix ).transpose(); + + } else { + + build.bindMatrix = new Matrix4().identity(); + + } + + // process bones and inverse bind matrix data + + for ( i = 0, l = jointSource.array.length; i < l; i ++ ) { + + const name = jointSource.array[ i ]; + const boneInverse = new Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose(); + + build.joints.push( { name: name, boneInverse: boneInverse } ); + + } + + return build; + + // array sort function + + function descending( a, b ) { + + return b.weight - a.weight; + + } + + } + + function getController( id ) { + + return getBuild( library.controllers[ id ], buildController ); + + } + + // image + + function parseImage( xml ) { + + const data = { + init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent + }; + + library.images[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildImage( data ) { + + if ( data.build !== undefined ) return data.build; + + return data.init_from; + + } + + function getImage( id ) { + + const data = library.images[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildImage ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find image with ID:', id ); + + return null; + + } + + // effect + + function parseEffect( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'profile_COMMON': + data.profile = parseEffectProfileCOMMON( child ); + break; + + } + + } + + library.effects[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseEffectProfileCOMMON( xml ) { + + const data = { + surfaces: {}, + samplers: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'newparam': + parseEffectNewparam( child, data ); + break; + + case 'technique': + data.technique = parseEffectTechnique( child ); + break; + + case 'extra': + data.extra = parseEffectExtra( child ); + break; + + } + + } + + return data; + + } + + function parseEffectNewparam( xml, data ) { + + const sid = xml.getAttribute( 'sid' ); + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'surface': + data.surfaces[ sid ] = parseEffectSurface( child ); + break; + + case 'sampler2D': + data.samplers[ sid ] = parseEffectSampler( child ); + break; + + } + + } + + } + + function parseEffectSurface( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'init_from': + data.init_from = child.textContent; + break; + + } + + } + + return data; + + } + + function parseEffectSampler( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'source': + data.source = child.textContent; + break; + + } + + } + + return data; + + } + + function parseEffectTechnique( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'constant': + case 'lambert': + case 'blinn': + case 'phong': + data.type = child.nodeName; + data.parameters = parseEffectParameters( child ); + break; + + case 'extra': + data.extra = parseEffectExtra( child ); + break; + + } + + } + + return data; + + } + + function parseEffectParameters( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'emission': + case 'diffuse': + case 'specular': + case 'bump': + case 'ambient': + case 'shininess': + case 'transparency': + data[ child.nodeName ] = parseEffectParameter( child ); + break; + case 'transparent': + data[ child.nodeName ] = { + opaque: child.hasAttribute( 'opaque' ) ? child.getAttribute( 'opaque' ) : 'A_ONE', + data: parseEffectParameter( child ) + }; + break; + + } + + } + + return data; + + } + + function parseEffectParameter( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + data[ child.nodeName ] = parseFloats( child.textContent ); + break; + + case 'float': + data[ child.nodeName ] = parseFloat( child.textContent ); + break; + + case 'texture': + data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) }; + break; + + } + + } + + return data; + + } + + function parseEffectParameterTexture( xml ) { + + const data = { + technique: {} + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'extra': + parseEffectParameterTextureExtra( child, data ); + break; + + } + + } + + return data; + + } + + function parseEffectParameterTextureExtra( xml, data ) { + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + parseEffectParameterTextureExtraTechnique( child, data ); + break; + + } + + } + + } + + function parseEffectParameterTextureExtraTechnique( xml, data ) { + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'repeatU': + case 'repeatV': + case 'offsetU': + case 'offsetV': + data.technique[ child.nodeName ] = parseFloat( child.textContent ); + break; + + case 'wrapU': + case 'wrapV': + + // some files have values for wrapU/wrapV which become NaN via parseInt + + if ( child.textContent.toUpperCase() === 'TRUE' ) { + + data.technique[ child.nodeName ] = 1; + + } else if ( child.textContent.toUpperCase() === 'FALSE' ) { + + data.technique[ child.nodeName ] = 0; + + } else { + + data.technique[ child.nodeName ] = parseInt( child.textContent ); + + } + + break; + + case 'bump': + data[ child.nodeName ] = parseEffectExtraTechniqueBump( child ); + break; + + } + + } + + } + + function parseEffectExtra( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique': + data.technique = parseEffectExtraTechnique( child ); + break; + + } + + } + + return data; + + } + + function parseEffectExtraTechnique( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'double_sided': + data[ child.nodeName ] = parseInt( child.textContent ); + break; + + case 'bump': + data[ child.nodeName ] = parseEffectExtraTechniqueBump( child ); + break; + + } + + } + + return data; + + } + + function parseEffectExtraTechniqueBump( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'texture': + data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), texcoord: child.getAttribute( 'texcoord' ), extra: parseEffectParameterTexture( child ) }; + break; + + } + + } + + return data; + + } + + function buildEffect( data ) { + + return data; + + } + + function getEffect( id ) { + + return getBuild( library.effects[ id ], buildEffect ); + + } + + // material + + function parseMaterial( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'instance_effect': + data.url = parseId( child.getAttribute( 'url' ) ); + break; + + } + + } + + library.materials[ xml.getAttribute( 'id' ) ] = data; + + } + + function getTextureLoader( image ) { + + let loader; + + let extension = image.slice( ( image.lastIndexOf( '.' ) - 1 >>> 0 ) + 2 ); // http://www.jstips.co/en/javascript/get-file-extension/ + extension = extension.toLowerCase(); + + switch ( extension ) { + + case 'tga': + loader = tgaLoader; + break; + + default: + loader = textureLoader; + + } + + return loader; + + } + + function buildMaterial( data ) { + + const effect = getEffect( data.url ); + const technique = effect.profile.technique; + + let material; + + switch ( technique.type ) { + + case 'phong': + case 'blinn': + material = new MeshPhongMaterial(); + break; + + case 'lambert': + material = new MeshLambertMaterial(); + break; + + default: + material = new MeshBasicMaterial(); + break; + + } + + material.name = data.name || ''; + + function getTexture( textureObject, encoding = null ) { + + const sampler = effect.profile.samplers[ textureObject.id ]; + let image = null; + + // get image + + if ( sampler !== undefined ) { + + const surface = effect.profile.surfaces[ sampler.source ]; + image = getImage( surface.init_from ); + + } else { + + console.warn( 'THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).' ); + image = getImage( textureObject.id ); + + } + + // create texture if image is avaiable + + if ( image !== null ) { + + const loader = getTextureLoader( image ); + + if ( loader !== undefined ) { + + const texture = loader.load( image ); + + const extra = textureObject.extra; + + if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) { + + const technique = extra.technique; + + texture.wrapS = technique.wrapU ? RepeatWrapping : ClampToEdgeWrapping; + texture.wrapT = technique.wrapV ? RepeatWrapping : ClampToEdgeWrapping; + + texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 ); + texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 ); + + } else { + + texture.wrapS = RepeatWrapping; + texture.wrapT = RepeatWrapping; + + } + + if ( encoding !== null ) { + + texture.encoding = encoding; + + } + + return texture; + + } else { + + console.warn( 'THREE.ColladaLoader: Loader for texture %s not found.', image ); + + return null; + + } + + } else { + + console.warn( 'THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id ); + + return null; + + } + + } + + const parameters = technique.parameters; + + for ( const key in parameters ) { + + const parameter = parameters[ key ]; + + switch ( key ) { + + case 'diffuse': + if ( parameter.color ) material.color.fromArray( parameter.color ); + if ( parameter.texture ) material.map = getTexture( parameter.texture, sRGBEncoding ); + break; + case 'specular': + if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color ); + if ( parameter.texture ) material.specularMap = getTexture( parameter.texture ); + break; + case 'bump': + if ( parameter.texture ) material.normalMap = getTexture( parameter.texture ); + break; + case 'ambient': + if ( parameter.texture ) material.lightMap = getTexture( parameter.texture, sRGBEncoding ); + break; + case 'shininess': + if ( parameter.float && material.shininess ) material.shininess = parameter.float; + break; + case 'emission': + if ( parameter.color && material.emissive ) material.emissive.fromArray( parameter.color ); + if ( parameter.texture ) material.emissiveMap = getTexture( parameter.texture, sRGBEncoding ); + break; + + } + + } + + material.color.convertSRGBToLinear(); + if ( material.specular ) material.specular.convertSRGBToLinear(); + if ( material.emissive ) material.emissive.convertSRGBToLinear(); + + // + + let transparent = parameters[ 'transparent' ]; + let transparency = parameters[ 'transparency' ]; + + // does not exist but + + if ( transparency === undefined && transparent ) { + + transparency = { + float: 1 + }; + + } + + // does not exist but + + if ( transparent === undefined && transparency ) { + + transparent = { + opaque: 'A_ONE', + data: { + color: [ 1, 1, 1, 1 ] + } }; + + } + + if ( transparent && transparency ) { + + // handle case if a texture exists but no color + + if ( transparent.data.texture ) { + + // we do not set an alpha map (see #13792) + + material.transparent = true; + + } else { + + const color = transparent.data.color; + + switch ( transparent.opaque ) { + + case 'A_ONE': + material.opacity = color[ 3 ] * transparency.float; + break; + case 'RGB_ZERO': + material.opacity = 1 - ( color[ 0 ] * transparency.float ); + break; + case 'A_ZERO': + material.opacity = 1 - ( color[ 3 ] * transparency.float ); + break; + case 'RGB_ONE': + material.opacity = color[ 0 ] * transparency.float; + break; + default: + console.warn( 'THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque ); + + } + + if ( material.opacity < 1 ) material.transparent = true; + + } + + } + + // + + + if ( technique.extra !== undefined && technique.extra.technique !== undefined ) { + + const techniques = technique.extra.technique; + + for ( const k in techniques ) { + + const v = techniques[ k ]; + + switch ( k ) { + + case 'double_sided': + material.side = ( v === 1 ? DoubleSide : FrontSide ); + break; + + case 'bump': + material.normalMap = getTexture( v.texture ); + material.normalScale = new Vector2( 1, 1 ); + break; + + } + + } + + } + + return material; + + } + + function getMaterial( id ) { + + return getBuild( library.materials[ id ], buildMaterial ); + + } + + // camera + + function parseCamera( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'optics': + data.optics = parseCameraOptics( child ); + break; + + } + + } + + library.cameras[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseCameraOptics( xml ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'technique_common': + return parseCameraTechnique( child ); + + } + + } + + return {}; + + } + + function parseCameraTechnique( xml ) { + + const data = {}; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'perspective': + case 'orthographic': + + data.technique = child.nodeName; + data.parameters = parseCameraParameters( child ); + + break; + + } + + } + + return data; + + } + + function parseCameraParameters( xml ) { + + const data = {}; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'xfov': + case 'yfov': + case 'xmag': + case 'ymag': + case 'znear': + case 'zfar': + case 'aspect_ratio': + data[ child.nodeName ] = parseFloat( child.textContent ); + break; + + } + + } + + return data; + + } + + function buildCamera( data ) { + + let camera; + + switch ( data.optics.technique ) { + + case 'perspective': + camera = new PerspectiveCamera( + data.optics.parameters.yfov, + data.optics.parameters.aspect_ratio, + data.optics.parameters.znear, + data.optics.parameters.zfar + ); + break; + + case 'orthographic': + let ymag = data.optics.parameters.ymag; + let xmag = data.optics.parameters.xmag; + const aspectRatio = data.optics.parameters.aspect_ratio; + + xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag; + ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag; + + xmag *= 0.5; + ymag *= 0.5; + + camera = new OrthographicCamera( + - xmag, xmag, ymag, - ymag, // left, right, top, bottom + data.optics.parameters.znear, + data.optics.parameters.zfar + ); + break; + + default: + camera = new PerspectiveCamera(); + break; + + } + + camera.name = data.name || ''; + + return camera; + + } + + function getCamera( id ) { + + const data = library.cameras[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildCamera ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find camera with ID:', id ); + + return null; + + } + + // light + + function parseLight( xml ) { + + let data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + data = parseLightTechnique( child ); + break; + + } + + } + + library.lights[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseLightTechnique( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'directional': + case 'point': + case 'spot': + case 'ambient': + + data.technique = child.nodeName; + data.parameters = parseLightParameters( child ); + + } + + } + + return data; + + } + + function parseLightParameters( xml ) { + + const data = {}; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'color': + const array = parseFloats( child.textContent ); + data.color = new Color().fromArray( array ).convertSRGBToLinear(); + break; + + case 'falloff_angle': + data.falloffAngle = parseFloat( child.textContent ); + break; + + case 'quadratic_attenuation': + const f = parseFloat( child.textContent ); + data.distance = f ? Math.sqrt( 1 / f ) : 0; + break; + + } + + } + + return data; + + } + + function buildLight( data ) { + + let light; + + switch ( data.technique ) { + + case 'directional': + light = new DirectionalLight(); + break; + + case 'point': + light = new PointLight(); + break; + + case 'spot': + light = new SpotLight(); + break; + + case 'ambient': + light = new AmbientLight(); + break; + + } + + if ( data.parameters.color ) light.color.copy( data.parameters.color ); + if ( data.parameters.distance ) light.distance = data.parameters.distance; + + return light; + + } + + function getLight( id ) { + + const data = library.lights[ id ]; + + if ( data !== undefined ) { + + return getBuild( data, buildLight ); + + } + + console.warn( 'THREE.ColladaLoader: Couldn\'t find light with ID:', id ); + + return null; + + } + + // geometry + + function parseGeometry( xml ) { + + const data = { + name: xml.getAttribute( 'name' ), + sources: {}, + vertices: {}, + primitives: [] + }; + + const mesh = getElementsByTagName( xml, 'mesh' )[ 0 ]; + + // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep + if ( mesh === undefined ) return; + + for ( let i = 0; i < mesh.childNodes.length; i ++ ) { + + const child = mesh.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + const id = child.getAttribute( 'id' ); + + switch ( child.nodeName ) { + + case 'source': + data.sources[ id ] = parseSource( child ); + break; + + case 'vertices': + // data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ]; + data.vertices = parseGeometryVertices( child ); + break; + + case 'polygons': + console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName ); + break; + + case 'lines': + case 'linestrips': + case 'polylist': + case 'triangles': + data.primitives.push( parseGeometryPrimitive( child ) ); + break; + + default: + console.log( child ); + + } + + } + + library.geometries[ xml.getAttribute( 'id' ) ] = data; + + } + + function parseSource( xml ) { + + const data = { + array: [], + stride: 3 + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'float_array': + data.array = parseFloats( child.textContent ); + break; + + case 'Name_array': + data.array = parseStrings( child.textContent ); + break; + + case 'technique_common': + const accessor = getElementsByTagName( child, 'accessor' )[ 0 ]; + + if ( accessor !== undefined ) { + + data.stride = parseInt( accessor.getAttribute( 'stride' ) ); + + } + + break; + + } + + } + + return data; + + } + + function parseGeometryVertices( xml ) { + + const data = {}; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) ); + + } + + return data; + + } + + function parseGeometryPrimitive( xml ) { + + const primitive = { + type: xml.nodeName, + material: xml.getAttribute( 'material' ), + count: parseInt( xml.getAttribute( 'count' ) ), + inputs: {}, + stride: 0, + hasUV: false + }; + + for ( let i = 0, l = xml.childNodes.length; i < l; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'input': + const id = parseId( child.getAttribute( 'source' ) ); + const semantic = child.getAttribute( 'semantic' ); + const offset = parseInt( child.getAttribute( 'offset' ) ); + const set = parseInt( child.getAttribute( 'set' ) ); + const inputname = ( set > 0 ? semantic + set : semantic ); + primitive.inputs[ inputname ] = { id: id, offset: offset }; + primitive.stride = Math.max( primitive.stride, offset + 1 ); + if ( semantic === 'TEXCOORD' ) primitive.hasUV = true; + break; + + case 'vcount': + primitive.vcount = parseInts( child.textContent ); + break; + + case 'p': + primitive.p = parseInts( child.textContent ); + break; + + } + + } + + return primitive; + + } + + function groupPrimitives( primitives ) { + + const build = {}; + + for ( let i = 0; i < primitives.length; i ++ ) { + + const primitive = primitives[ i ]; + + if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = []; + + build[ primitive.type ].push( primitive ); + + } + + return build; + + } + + function checkUVCoordinates( primitives ) { + + let count = 0; + + for ( let i = 0, l = primitives.length; i < l; i ++ ) { + + const primitive = primitives[ i ]; + + if ( primitive.hasUV === true ) { + + count ++; + + } + + } + + if ( count > 0 && count < primitives.length ) { + + primitives.uvsNeedsFix = true; + + } + + } + + function buildGeometry( data ) { + + const build = {}; + + const sources = data.sources; + const vertices = data.vertices; + const primitives = data.primitives; + + if ( primitives.length === 0 ) return {}; + + // our goal is to create one buffer geometry for a single type of primitives + // first, we group all primitives by their type + + const groupedPrimitives = groupPrimitives( primitives ); + + for ( const type in groupedPrimitives ) { + + const primitiveType = groupedPrimitives[ type ]; + + // second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines) + + checkUVCoordinates( primitiveType ); + + // third, create a buffer geometry for each type of primitives + + build[ type ] = buildGeometryType( primitiveType, sources, vertices ); + + } + + return build; + + } + + function buildGeometryType( primitives, sources, vertices ) { + + const build = {}; + + const position = { array: [], stride: 0 }; + const normal = { array: [], stride: 0 }; + const uv = { array: [], stride: 0 }; + const uv2 = { array: [], stride: 0 }; + const color = { array: [], stride: 0 }; + + const skinIndex = { array: [], stride: 4 }; + const skinWeight = { array: [], stride: 4 }; + + const geometry = new BufferGeometry(); + + const materialKeys = []; + + let start = 0; + + for ( let p = 0; p < primitives.length; p ++ ) { + + const primitive = primitives[ p ]; + const inputs = primitive.inputs; + + // groups + + let count = 0; + + switch ( primitive.type ) { + + case 'lines': + case 'linestrips': + count = primitive.count * 2; + break; + + case 'triangles': + count = primitive.count * 3; + break; + + case 'polylist': + + for ( let g = 0; g < primitive.count; g ++ ) { + + const vc = primitive.vcount[ g ]; + + switch ( vc ) { + + case 3: + count += 3; // single triangle + break; + + case 4: + count += 6; // quad, subdivided into two triangles + break; + + default: + count += ( vc - 2 ) * 3; // polylist with more than four vertices + break; + + } + + } + + break; + + default: + console.warn( 'THREE.ColladaLoader: Unknow primitive type:', primitive.type ); + + } + + geometry.addGroup( start, count, p ); + start += count; + + // material + + if ( primitive.material ) { + + materialKeys.push( primitive.material ); + + } + + // geometry data + + for ( const name in inputs ) { + + const input = inputs[ name ]; + + switch ( name ) { + + case 'VERTEX': + for ( const key in vertices ) { + + const id = vertices[ key ]; + + switch ( key ) { + + case 'POSITION': + const prevLength = position.array.length; + buildGeometryData( primitive, sources[ id ], input.offset, position.array ); + position.stride = sources[ id ].stride; + + if ( sources.skinWeights && sources.skinIndices ) { + + buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array ); + buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array ); + + } + + // see #3803 + + if ( primitive.hasUV === false && primitives.uvsNeedsFix === true ) { + + const count = ( position.array.length - prevLength ) / position.stride; + + for ( let i = 0; i < count; i ++ ) { + + // fill missing uv coordinates + + uv.array.push( 0, 0 ); + + } + + } + + break; + + case 'NORMAL': + buildGeometryData( primitive, sources[ id ], input.offset, normal.array ); + normal.stride = sources[ id ].stride; + break; + + case 'COLOR': + buildGeometryData( primitive, sources[ id ], input.offset, color.array ); + color.stride = sources[ id ].stride; + break; + + case 'TEXCOORD': + buildGeometryData( primitive, sources[ id ], input.offset, uv.array ); + uv.stride = sources[ id ].stride; + break; + + case 'TEXCOORD1': + buildGeometryData( primitive, sources[ id ], input.offset, uv2.array ); + uv.stride = sources[ id ].stride; + break; + + default: + console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key ); + + } + + } + + break; + + case 'NORMAL': + buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array ); + normal.stride = sources[ input.id ].stride; + break; + + case 'COLOR': + buildGeometryData( primitive, sources[ input.id ], input.offset, color.array, true ); + color.stride = sources[ input.id ].stride; + break; + + case 'TEXCOORD': + buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array ); + uv.stride = sources[ input.id ].stride; + break; + + case 'TEXCOORD1': + buildGeometryData( primitive, sources[ input.id ], input.offset, uv2.array ); + uv2.stride = sources[ input.id ].stride; + break; + + } + + } + + } + + // build geometry + + if ( position.array.length > 0 ) geometry.setAttribute( 'position', new Float32BufferAttribute( position.array, position.stride ) ); + if ( normal.array.length > 0 ) geometry.setAttribute( 'normal', new Float32BufferAttribute( normal.array, normal.stride ) ); + if ( color.array.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( color.array, color.stride ) ); + if ( uv.array.length > 0 ) geometry.setAttribute( 'uv', new Float32BufferAttribute( uv.array, uv.stride ) ); + if ( uv2.array.length > 0 ) geometry.setAttribute( 'uv2', new Float32BufferAttribute( uv2.array, uv2.stride ) ); + + if ( skinIndex.array.length > 0 ) geometry.setAttribute( 'skinIndex', new Float32BufferAttribute( skinIndex.array, skinIndex.stride ) ); + if ( skinWeight.array.length > 0 ) geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeight.array, skinWeight.stride ) ); + + build.data = geometry; + build.type = primitives[ 0 ].type; + build.materialKeys = materialKeys; + + return build; + + } + + function buildGeometryData( primitive, source, offset, array, isColor = false ) { + + const indices = primitive.p; + const stride = primitive.stride; + const vcount = primitive.vcount; + + function pushVector( i ) { + + let index = indices[ i + offset ] * sourceStride; + const length = index + sourceStride; + + for ( ; index < length; index ++ ) { + + array.push( sourceArray[ index ] ); + + } + + if ( isColor ) { + + // convert the vertex colors from srgb to linear if present + const startIndex = array.length - sourceStride - 1; + tempColor.setRGB( + array[ startIndex + 0 ], + array[ startIndex + 1 ], + array[ startIndex + 2 ] + ).convertSRGBToLinear(); + + array[ startIndex + 0 ] = tempColor.r; + array[ startIndex + 1 ] = tempColor.g; + array[ startIndex + 2 ] = tempColor.b; + + } + + } + + const sourceArray = source.array; + const sourceStride = source.stride; + + if ( primitive.vcount !== undefined ) { + + let index = 0; + + for ( let i = 0, l = vcount.length; i < l; i ++ ) { + + const count = vcount[ i ]; + + if ( count === 4 ) { + + const a = index + stride * 0; + const b = index + stride * 1; + const c = index + stride * 2; + const d = index + stride * 3; + + pushVector( a ); pushVector( b ); pushVector( d ); + pushVector( b ); pushVector( c ); pushVector( d ); + + } else if ( count === 3 ) { + + const a = index + stride * 0; + const b = index + stride * 1; + const c = index + stride * 2; + + pushVector( a ); pushVector( b ); pushVector( c ); + + } else if ( count > 4 ) { + + for ( let k = 1, kl = ( count - 2 ); k <= kl; k ++ ) { + + const a = index + stride * 0; + const b = index + stride * k; + const c = index + stride * ( k + 1 ); + + pushVector( a ); pushVector( b ); pushVector( c ); + + } + + } + + index += stride * count; + + } + + } else { + + for ( let i = 0, l = indices.length; i < l; i += stride ) { + + pushVector( i ); + + } + + } + + } + + function getGeometry( id ) { + + return getBuild( library.geometries[ id ], buildGeometry ); + + } + + // kinematics + + function parseKinematicsModel( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) || '', + joints: {}, + links: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + parseKinematicsTechniqueCommon( child, data ); + break; + + } + + } + + library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildKinematicsModel( data ) { + + if ( data.build !== undefined ) return data.build; + + return data; + + } + + function getKinematicsModel( id ) { + + return getBuild( library.kinematicsModels[ id ], buildKinematicsModel ); + + } + + function parseKinematicsTechniqueCommon( xml, data ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'joint': + data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child ); + break; + + case 'link': + data.links.push( parseKinematicsLink( child ) ); + break; + + } + + } + + } + + function parseKinematicsJoint( xml ) { + + let data; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'prismatic': + case 'revolute': + data = parseKinematicsJointParameter( child ); + break; + + } + + } + + return data; + + } + + function parseKinematicsJointParameter( xml ) { + + const data = { + sid: xml.getAttribute( 'sid' ), + name: xml.getAttribute( 'name' ) || '', + axis: new Vector3(), + limits: { + min: 0, + max: 0 + }, + type: xml.nodeName, + static: false, + zeroPosition: 0, + middlePosition: 0 + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'axis': + const array = parseFloats( child.textContent ); + data.axis.fromArray( array ); + break; + case 'limits': + const max = child.getElementsByTagName( 'max' )[ 0 ]; + const min = child.getElementsByTagName( 'min' )[ 0 ]; + + data.limits.max = parseFloat( max.textContent ); + data.limits.min = parseFloat( min.textContent ); + break; + + } + + } + + // if min is equal to or greater than max, consider the joint static + + if ( data.limits.min >= data.limits.max ) { + + data.static = true; + + } + + // calculate middle position + + data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0; + + return data; + + } + + function parseKinematicsLink( xml ) { + + const data = { + sid: xml.getAttribute( 'sid' ), + name: xml.getAttribute( 'name' ) || '', + attachments: [], + transforms: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'attachment_full': + data.attachments.push( parseKinematicsAttachment( child ) ); + break; + + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push( parseKinematicsTransform( child ) ); + break; + + } + + } + + return data; + + } + + function parseKinematicsAttachment( xml ) { + + const data = { + joint: xml.getAttribute( 'joint' ).split( '/' ).pop(), + transforms: [], + links: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'link': + data.links.push( parseKinematicsLink( child ) ); + break; + + case 'matrix': + case 'translate': + case 'rotate': + data.transforms.push( parseKinematicsTransform( child ) ); + break; + + } + + } + + return data; + + } + + function parseKinematicsTransform( xml ) { + + const data = { + type: xml.nodeName + }; + + const array = parseFloats( xml.textContent ); + + switch ( data.type ) { + + case 'matrix': + data.obj = new Matrix4(); + data.obj.fromArray( array ).transpose(); + break; + + case 'translate': + data.obj = new Vector3(); + data.obj.fromArray( array ); + break; + + case 'rotate': + data.obj = new Vector3(); + data.obj.fromArray( array ); + data.angle = MathUtils.degToRad( array[ 3 ] ); + break; + + } + + return data; + + } + + // physics + + function parsePhysicsModel( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) || '', + rigidBodies: {} + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'rigid_body': + data.rigidBodies[ child.getAttribute( 'name' ) ] = {}; + parsePhysicsRigidBody( child, data.rigidBodies[ child.getAttribute( 'name' ) ] ); + break; + + } + + } + + library.physicsModels[ xml.getAttribute( 'id' ) ] = data; + + } + + function parsePhysicsRigidBody( xml, data ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'technique_common': + parsePhysicsTechniqueCommon( child, data ); + break; + + } + + } + + } + + function parsePhysicsTechniqueCommon( xml, data ) { + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'inertia': + data.inertia = parseFloats( child.textContent ); + break; + + case 'mass': + data.mass = parseFloats( child.textContent )[ 0 ]; + break; + + } + + } + + } + + // scene + + function parseKinematicsScene( xml ) { + + const data = { + bindJointAxis: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'bind_joint_axis': + data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) ); + break; + + } + + } + + library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data; + + } + + function parseKinematicsBindJointAxis( xml ) { + + const data = { + target: xml.getAttribute( 'target' ).split( '/' ).pop() + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + switch ( child.nodeName ) { + + case 'axis': + const param = child.getElementsByTagName( 'param' )[ 0 ]; + data.axis = param.textContent; + const tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ]; + data.jointIndex = tmpJointIndex.substring( 0, tmpJointIndex.length - 1 ); + break; + + } + + } + + return data; + + } + + function buildKinematicsScene( data ) { + + if ( data.build !== undefined ) return data.build; + + return data; + + } + + function getKinematicsScene( id ) { + + return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene ); + + } + + function setupKinematics() { + + const kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ]; + const kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ]; + const visualSceneId = Object.keys( library.visualScenes )[ 0 ]; + + if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return; + + const kinematicsModel = getKinematicsModel( kinematicsModelId ); + const kinematicsScene = getKinematicsScene( kinematicsSceneId ); + const visualScene = getVisualScene( visualSceneId ); + + const bindJointAxis = kinematicsScene.bindJointAxis; + const jointMap = {}; + + for ( let i = 0, l = bindJointAxis.length; i < l; i ++ ) { + + const axis = bindJointAxis[ i ]; + + // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix' + + const targetElement = collada.querySelector( '[sid="' + axis.target + '"]' ); + + if ( targetElement ) { + + // get the parent of the transform element + + const parentVisualElement = targetElement.parentElement; + + // connect the joint of the kinematics model with the element in the visual scene + + connect( axis.jointIndex, parentVisualElement ); + + } + + } + + function connect( jointIndex, visualElement ) { + + const visualElementName = visualElement.getAttribute( 'name' ); + const joint = kinematicsModel.joints[ jointIndex ]; + + visualScene.traverse( function ( object ) { + + if ( object.name === visualElementName ) { + + jointMap[ jointIndex ] = { + object: object, + transforms: buildTransformList( visualElement ), + joint: joint, + position: joint.zeroPosition + }; + + } + + } ); + + } + + const m0 = new Matrix4(); + + kinematics = { + + joints: kinematicsModel && kinematicsModel.joints, + + getJointValue: function ( jointIndex ) { + + const jointData = jointMap[ jointIndex ]; + + if ( jointData ) { + + return jointData.position; + + } else { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' ); + + } + + }, + + setJointValue: function ( jointIndex, value ) { + + const jointData = jointMap[ jointIndex ]; + + if ( jointData ) { + + const joint = jointData.joint; + + if ( value > joint.limits.max || value < joint.limits.min ) { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' ); + + } else if ( joint.static ) { + + console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' ); + + } else { + + const object = jointData.object; + const axis = joint.axis; + const transforms = jointData.transforms; + + matrix.identity(); + + // each update, we have to apply all transforms in the correct order + + for ( let i = 0; i < transforms.length; i ++ ) { + + const transform = transforms[ i ]; + + // if there is a connection of the transform node with a joint, apply the joint value + + if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) { + + switch ( joint.type ) { + + case 'revolute': + matrix.multiply( m0.makeRotationAxis( axis, MathUtils.degToRad( value ) ) ); + break; + + case 'prismatic': + matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) ); + break; + + default: + console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type ); + break; + + } + + } else { + + switch ( transform.type ) { + + case 'matrix': + matrix.multiply( transform.obj ); + break; + + case 'translate': + matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) ); + break; + + case 'scale': + matrix.scale( transform.obj ); + break; + + case 'rotate': + matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) ); + break; + + } + + } + + } + + object.matrix.copy( matrix ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + + jointMap[ jointIndex ].position = value; + + } + + } else { + + console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' ); + + } + + } + + }; + + } + + function buildTransformList( node ) { + + const transforms = []; + + const xml = collada.querySelector( '[id="' + node.id + '"]' ); + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + let array, vector; + + switch ( child.nodeName ) { + + case 'matrix': + array = parseFloats( child.textContent ); + const matrix = new Matrix4().fromArray( array ).transpose(); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: matrix + } ); + break; + + case 'translate': + case 'scale': + array = parseFloats( child.textContent ); + vector = new Vector3().fromArray( array ); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: vector + } ); + break; + + case 'rotate': + array = parseFloats( child.textContent ); + vector = new Vector3().fromArray( array ); + const angle = MathUtils.degToRad( array[ 3 ] ); + transforms.push( { + sid: child.getAttribute( 'sid' ), + type: child.nodeName, + obj: vector, + angle: angle + } ); + break; + + } + + } + + return transforms; + + } + + // nodes + + function prepareNodes( xml ) { + + const elements = xml.getElementsByTagName( 'node' ); + + // ensure all node elements have id attributes + + for ( let i = 0; i < elements.length; i ++ ) { + + const element = elements[ i ]; + + if ( element.hasAttribute( 'id' ) === false ) { + + element.setAttribute( 'id', generateId() ); + + } + + } + + } + + const matrix = new Matrix4(); + const vector = new Vector3(); + + function parseNode( xml ) { + + const data = { + name: xml.getAttribute( 'name' ) || '', + type: xml.getAttribute( 'type' ), + id: xml.getAttribute( 'id' ), + sid: xml.getAttribute( 'sid' ), + matrix: new Matrix4(), + nodes: [], + instanceCameras: [], + instanceControllers: [], + instanceLights: [], + instanceGeometries: [], + instanceNodes: [], + transforms: {} + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + if ( child.nodeType !== 1 ) continue; + + let array; + + switch ( child.nodeName ) { + + case 'node': + data.nodes.push( child.getAttribute( 'id' ) ); + parseNode( child ); + break; + + case 'instance_camera': + data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'instance_controller': + data.instanceControllers.push( parseNodeInstance( child ) ); + break; + + case 'instance_light': + data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'instance_geometry': + data.instanceGeometries.push( parseNodeInstance( child ) ); + break; + + case 'instance_node': + data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) ); + break; + + case 'matrix': + array = parseFloats( child.textContent ); + data.matrix.multiply( matrix.fromArray( array ).transpose() ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'translate': + array = parseFloats( child.textContent ); + vector.fromArray( array ); + data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'rotate': + array = parseFloats( child.textContent ); + const angle = MathUtils.degToRad( array[ 3 ] ); + data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'scale': + array = parseFloats( child.textContent ); + data.matrix.scale( vector.fromArray( array ) ); + data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName; + break; + + case 'extra': + break; + + default: + console.log( child ); + + } + + } + + if ( hasNode( data.id ) ) { + + console.warn( 'THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id ); + + } else { + + library.nodes[ data.id ] = data; + + } + + return data; + + } + + function parseNodeInstance( xml ) { + + const data = { + id: parseId( xml.getAttribute( 'url' ) ), + materials: {}, + skeletons: [] + }; + + for ( let i = 0; i < xml.childNodes.length; i ++ ) { + + const child = xml.childNodes[ i ]; + + switch ( child.nodeName ) { + + case 'bind_material': + const instances = child.getElementsByTagName( 'instance_material' ); + + for ( let j = 0; j < instances.length; j ++ ) { + + const instance = instances[ j ]; + const symbol = instance.getAttribute( 'symbol' ); + const target = instance.getAttribute( 'target' ); + + data.materials[ symbol ] = parseId( target ); + + } + + break; + + case 'skeleton': + data.skeletons.push( parseId( child.textContent ) ); + break; + + default: + break; + + } + + } + + return data; + + } + + function buildSkeleton( skeletons, joints ) { + + const boneData = []; + const sortedBoneData = []; + + let i, j, data; + + // a skeleton can have multiple root bones. collada expresses this + // situtation with multiple "skeleton" tags per controller instance + + for ( i = 0; i < skeletons.length; i ++ ) { + + const skeleton = skeletons[ i ]; + + let root; + + if ( hasNode( skeleton ) ) { + + root = getNode( skeleton ); + buildBoneHierarchy( root, joints, boneData ); + + } else if ( hasVisualScene( skeleton ) ) { + + // handle case where the skeleton refers to the visual scene (#13335) + + const visualScene = library.visualScenes[ skeleton ]; + const children = visualScene.children; + + for ( let j = 0; j < children.length; j ++ ) { + + const child = children[ j ]; + + if ( child.type === 'JOINT' ) { + + const root = getNode( child.id ); + buildBoneHierarchy( root, joints, boneData ); + + } + + } + + } else { + + console.error( 'THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton ); + + } + + } + + // sort bone data (the order is defined in the corresponding controller) + + for ( i = 0; i < joints.length; i ++ ) { + + for ( j = 0; j < boneData.length; j ++ ) { + + data = boneData[ j ]; + + if ( data.bone.name === joints[ i ].name ) { + + sortedBoneData[ i ] = data; + data.processed = true; + break; + + } + + } + + } + + // add unprocessed bone data at the end of the list + + for ( i = 0; i < boneData.length; i ++ ) { + + data = boneData[ i ]; + + if ( data.processed === false ) { + + sortedBoneData.push( data ); + data.processed = true; + + } + + } + + // setup arrays for skeleton creation + + const bones = []; + const boneInverses = []; + + for ( i = 0; i < sortedBoneData.length; i ++ ) { + + data = sortedBoneData[ i ]; + + bones.push( data.bone ); + boneInverses.push( data.boneInverse ); + + } + + return new Skeleton( bones, boneInverses ); + + } + + function buildBoneHierarchy( root, joints, boneData ) { + + // setup bone data from visual scene + + root.traverse( function ( object ) { + + if ( object.isBone === true ) { + + let boneInverse; + + // retrieve the boneInverse from the controller data + + for ( let i = 0; i < joints.length; i ++ ) { + + const joint = joints[ i ]; + + if ( joint.name === object.name ) { + + boneInverse = joint.boneInverse; + break; + + } + + } + + if ( boneInverse === undefined ) { + + // Unfortunately, there can be joints in the visual scene that are not part of the + // corresponding controller. In this case, we have to create a dummy boneInverse matrix + // for the respective bone. This bone won't affect any vertices, because there are no skin indices + // and weights defined for it. But we still have to add the bone to the sorted bone list in order to + // ensure a correct animation of the model. + + boneInverse = new Matrix4(); + + } + + boneData.push( { bone: object, boneInverse: boneInverse, processed: false } ); + + } + + } ); + + } + + function buildNode( data ) { + + const objects = []; + + const matrix = data.matrix; + const nodes = data.nodes; + const type = data.type; + const instanceCameras = data.instanceCameras; + const instanceControllers = data.instanceControllers; + const instanceLights = data.instanceLights; + const instanceGeometries = data.instanceGeometries; + const instanceNodes = data.instanceNodes; + + // nodes + + for ( let i = 0, l = nodes.length; i < l; i ++ ) { + + objects.push( getNode( nodes[ i ] ) ); + + } + + // instance cameras + + for ( let i = 0, l = instanceCameras.length; i < l; i ++ ) { + + const instanceCamera = getCamera( instanceCameras[ i ] ); + + if ( instanceCamera !== null ) { + + objects.push( instanceCamera.clone() ); + + } + + } + + // instance controllers + + for ( let i = 0, l = instanceControllers.length; i < l; i ++ ) { + + const instance = instanceControllers[ i ]; + const controller = getController( instance.id ); + const geometries = getGeometry( controller.id ); + const newObjects = buildObjects( geometries, instance.materials ); + + const skeletons = instance.skeletons; + const joints = controller.skin.joints; + + const skeleton = buildSkeleton( skeletons, joints ); + + for ( let j = 0, jl = newObjects.length; j < jl; j ++ ) { + + const object = newObjects[ j ]; + + if ( object.isSkinnedMesh ) { + + object.bind( skeleton, controller.skin.bindMatrix ); + object.normalizeSkinWeights(); + + } + + objects.push( object ); + + } + + } + + // instance lights + + for ( let i = 0, l = instanceLights.length; i < l; i ++ ) { + + const instanceLight = getLight( instanceLights[ i ] ); + + if ( instanceLight !== null ) { + + objects.push( instanceLight.clone() ); + + } + + } + + // instance geometries + + for ( let i = 0, l = instanceGeometries.length; i < l; i ++ ) { + + const instance = instanceGeometries[ i ]; + + // a single geometry instance in collada can lead to multiple object3Ds. + // this is the case when primitives are combined like triangles and lines + + const geometries = getGeometry( instance.id ); + const newObjects = buildObjects( geometries, instance.materials ); + + for ( let j = 0, jl = newObjects.length; j < jl; j ++ ) { + + objects.push( newObjects[ j ] ); + + } + + } + + // instance nodes + + for ( let i = 0, l = instanceNodes.length; i < l; i ++ ) { + + objects.push( getNode( instanceNodes[ i ] ).clone() ); + + } + + let object; + + if ( nodes.length === 0 && objects.length === 1 ) { + + object = objects[ 0 ]; + + } else { + + object = ( type === 'JOINT' ) ? new Bone() : new Group(); + + for ( let i = 0; i < objects.length; i ++ ) { + + object.add( objects[ i ] ); + + } + + } + + object.name = ( type === 'JOINT' ) ? data.sid : data.name; + object.matrix.copy( matrix ); + object.matrix.decompose( object.position, object.quaternion, object.scale ); + + return object; + + } + + const fallbackMaterial = new MeshBasicMaterial( { color: 0xff00ff } ); + + function resolveMaterialBinding( keys, instanceMaterials ) { + + const materials = []; + + for ( let i = 0, l = keys.length; i < l; i ++ ) { + + const id = instanceMaterials[ keys[ i ] ]; + + if ( id === undefined ) { + + console.warn( 'THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[ i ] ); + materials.push( fallbackMaterial ); + + } else { + + materials.push( getMaterial( id ) ); + + } + + } + + return materials; + + } + + function buildObjects( geometries, instanceMaterials ) { + + const objects = []; + + for ( const type in geometries ) { + + const geometry = geometries[ type ]; + + const materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials ); + + // handle case if no materials are defined + + if ( materials.length === 0 ) { + + if ( type === 'lines' || type === 'linestrips' ) { + + materials.push( new LineBasicMaterial() ); + + } else { + + materials.push( new MeshPhongMaterial() ); + + } + + } + + // regard skinning + + const skinning = ( geometry.data.attributes.skinIndex !== undefined ); + + // choose between a single or multi materials (material array) + + const material = ( materials.length === 1 ) ? materials[ 0 ] : materials; + + // now create a specific 3D object + + let object; + + switch ( type ) { + + case 'lines': + object = new LineSegments( geometry.data, material ); + break; + + case 'linestrips': + object = new Line( geometry.data, material ); + break; + + case 'triangles': + case 'polylist': + if ( skinning ) { + + object = new SkinnedMesh( geometry.data, material ); + + } else { + + object = new Mesh( geometry.data, material ); + + } + + break; + + } + + objects.push( object ); + + } + + return objects; + + } + + function hasNode( id ) { + + return library.nodes[ id ] !== undefined; + + } + + function getNode( id ) { + + return getBuild( library.nodes[ id ], buildNode ); + + } + + // visual scenes + + function parseVisualScene( xml ) { + + const data = { + name: xml.getAttribute( 'name' ), + children: [] + }; + + prepareNodes( xml ); + + const elements = getElementsByTagName( xml, 'node' ); + + for ( let i = 0; i < elements.length; i ++ ) { + + data.children.push( parseNode( elements[ i ] ) ); + + } + + library.visualScenes[ xml.getAttribute( 'id' ) ] = data; + + } + + function buildVisualScene( data ) { + + const group = new Group(); + group.name = data.name; + + const children = data.children; + + for ( let i = 0; i < children.length; i ++ ) { + + const child = children[ i ]; + + group.add( getNode( child.id ) ); + + } + + return group; + + } + + function hasVisualScene( id ) { + + return library.visualScenes[ id ] !== undefined; + + } + + function getVisualScene( id ) { + + return getBuild( library.visualScenes[ id ], buildVisualScene ); + + } + + // scenes + + function parseScene( xml ) { + + const instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ]; + return getVisualScene( parseId( instance.getAttribute( 'url' ) ) ); + + } + + function setupAnimations() { + + const clips = library.clips; + + if ( isEmpty( clips ) === true ) { + + if ( isEmpty( library.animations ) === false ) { + + // if there are animations but no clips, we create a default clip for playback + + const tracks = []; + + for ( const id in library.animations ) { + + const animationTracks = getAnimation( id ); + + for ( let i = 0, l = animationTracks.length; i < l; i ++ ) { + + tracks.push( animationTracks[ i ] ); + + } + + } + + animations.push( new AnimationClip( 'default', - 1, tracks ) ); + + } + + } else { + + for ( const id in clips ) { + + animations.push( getAnimationClip( id ) ); + + } + + } + + } + + // convert the parser error element into text with each child elements text + // separated by new lines. + + function parserErrorToText( parserError ) { + + let result = ''; + const stack = [ parserError ]; + + while ( stack.length ) { + + const node = stack.shift(); + + if ( node.nodeType === Node.TEXT_NODE ) { + + result += node.textContent; + + } else { + + result += '\n'; + stack.push.apply( stack, node.childNodes ); + + } + + } + + return result.trim(); + + } + + if ( text.length === 0 ) { + + return { scene: new Scene() }; + + } + + const xml = new DOMParser().parseFromString( text, 'application/xml' ); + + const collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ]; + + const parserError = xml.getElementsByTagName( 'parsererror' )[ 0 ]; + if ( parserError !== undefined ) { + + // Chrome will return parser error with a div in it + + const errorElement = getElementsByTagName( parserError, 'div' )[ 0 ]; + let errorText; + + if ( errorElement ) { + + errorText = errorElement.textContent; + + } else { + + errorText = parserErrorToText( parserError ); + + } + + console.error( 'THREE.ColladaLoader: Failed to parse collada file.\n', errorText ); + + return null; + + } + + // metadata + + const version = collada.getAttribute( 'version' ); + console.log( 'THREE.ColladaLoader: File version', version ); + + const asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] ); + const textureLoader = new TextureLoader( this.manager ); + textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + + let tgaLoader; + + if ( TGALoader ) { + + tgaLoader = new TGALoader( this.manager ); + tgaLoader.setPath( this.resourcePath || path ); + + } + + // + + const tempColor = new Color(); + const animations = []; + let kinematics = {}; + let count = 0; + + // + + const library = { + animations: {}, + clips: {}, + controllers: {}, + images: {}, + effects: {}, + materials: {}, + cameras: {}, + lights: {}, + geometries: {}, + nodes: {}, + visualScenes: {}, + kinematicsModels: {}, + physicsModels: {}, + kinematicsScenes: {} + }; + + parseLibrary( collada, 'library_animations', 'animation', parseAnimation ); + parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip ); + parseLibrary( collada, 'library_controllers', 'controller', parseController ); + parseLibrary( collada, 'library_images', 'image', parseImage ); + parseLibrary( collada, 'library_effects', 'effect', parseEffect ); + parseLibrary( collada, 'library_materials', 'material', parseMaterial ); + parseLibrary( collada, 'library_cameras', 'camera', parseCamera ); + parseLibrary( collada, 'library_lights', 'light', parseLight ); + parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry ); + parseLibrary( collada, 'library_nodes', 'node', parseNode ); + parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene ); + parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel ); + parseLibrary( collada, 'library_physics_models', 'physics_model', parsePhysicsModel ); + parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene ); + + buildLibrary( library.animations, buildAnimation ); + buildLibrary( library.clips, buildAnimationClip ); + buildLibrary( library.controllers, buildController ); + buildLibrary( library.images, buildImage ); + buildLibrary( library.effects, buildEffect ); + buildLibrary( library.materials, buildMaterial ); + buildLibrary( library.cameras, buildCamera ); + buildLibrary( library.lights, buildLight ); + buildLibrary( library.geometries, buildGeometry ); + buildLibrary( library.visualScenes, buildVisualScene ); + + setupAnimations(); + setupKinematics(); + + const scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] ); + scene.animations = animations; + + if ( asset.upAxis === 'Z_UP' ) { + + scene.quaternion.setFromEuler( new Euler( - Math.PI / 2, 0, 0 ) ); + + } + + scene.scale.multiplyScalar( asset.unit ); + + return { + get animations() { + + console.warn( 'THREE.ColladaLoader: Please access animations over scene.animations now.' ); + return animations; + + }, + kinematics: kinematics, + library: library, + scene: scene + }; + + } + +} + +export { ColladaLoader }; diff --git a/jsm/loaders/DDSLoader.js b/jsm/loaders/DDSLoader.js new file mode 100644 index 0000000..a08d71a --- /dev/null +++ b/jsm/loaders/DDSLoader.js @@ -0,0 +1,281 @@ +import { + CompressedTextureLoader, + RGBAFormat, + RGBA_S3TC_DXT3_Format, + RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format, + RGB_S3TC_DXT1_Format +} from 'three'; + +class DDSLoader extends CompressedTextureLoader { + + constructor( manager ) { + + super( manager ); + + } + + parse( buffer, loadMipmaps ) { + + const dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; + + // Adapted from @toji's DDS utils + // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js + + // All values and structures referenced from: + // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ + + const DDS_MAGIC = 0x20534444; + + // let DDSD_CAPS = 0x1; + // let DDSD_HEIGHT = 0x2; + // let DDSD_WIDTH = 0x4; + // let DDSD_PITCH = 0x8; + // let DDSD_PIXELFORMAT = 0x1000; + const DDSD_MIPMAPCOUNT = 0x20000; + // let DDSD_LINEARSIZE = 0x80000; + // let DDSD_DEPTH = 0x800000; + + // let DDSCAPS_COMPLEX = 0x8; + // let DDSCAPS_MIPMAP = 0x400000; + // let DDSCAPS_TEXTURE = 0x1000; + + const DDSCAPS2_CUBEMAP = 0x200; + const DDSCAPS2_CUBEMAP_POSITIVEX = 0x400; + const DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800; + const DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000; + const DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000; + const DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000; + const DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000; + // let DDSCAPS2_VOLUME = 0x200000; + + // let DDPF_ALPHAPIXELS = 0x1; + // let DDPF_ALPHA = 0x2; + const DDPF_FOURCC = 0x4; + // let DDPF_RGB = 0x40; + // let DDPF_YUV = 0x200; + // let DDPF_LUMINANCE = 0x20000; + + function fourCCToInt32( value ) { + + return value.charCodeAt( 0 ) + + ( value.charCodeAt( 1 ) << 8 ) + + ( value.charCodeAt( 2 ) << 16 ) + + ( value.charCodeAt( 3 ) << 24 ); + + } + + function int32ToFourCC( value ) { + + return String.fromCharCode( + value & 0xff, + ( value >> 8 ) & 0xff, + ( value >> 16 ) & 0xff, + ( value >> 24 ) & 0xff + ); + + } + + function loadARGBMip( buffer, dataOffset, width, height ) { + + const dataLength = width * height * 4; + const srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); + const byteArray = new Uint8Array( dataLength ); + let dst = 0; + let src = 0; + for ( let y = 0; y < height; y ++ ) { + + for ( let x = 0; x < width; x ++ ) { + + const b = srcBuffer[ src ]; src ++; + const g = srcBuffer[ src ]; src ++; + const r = srcBuffer[ src ]; src ++; + const a = srcBuffer[ src ]; src ++; + byteArray[ dst ] = r; dst ++; //r + byteArray[ dst ] = g; dst ++; //g + byteArray[ dst ] = b; dst ++; //b + byteArray[ dst ] = a; dst ++; //a + + } + + } + + return byteArray; + + } + + const FOURCC_DXT1 = fourCCToInt32( 'DXT1' ); + const FOURCC_DXT3 = fourCCToInt32( 'DXT3' ); + const FOURCC_DXT5 = fourCCToInt32( 'DXT5' ); + const FOURCC_ETC1 = fourCCToInt32( 'ETC1' ); + + const headerLengthInt = 31; // The header length in 32 bit ints + + // Offsets into the header array + + const off_magic = 0; + + const off_size = 1; + const off_flags = 2; + const off_height = 3; + const off_width = 4; + + const off_mipmapCount = 7; + + const off_pfFlags = 20; + const off_pfFourCC = 21; + const off_RGBBitCount = 22; + const off_RBitMask = 23; + const off_GBitMask = 24; + const off_BBitMask = 25; + const off_ABitMask = 26; + + // let off_caps = 27; + const off_caps2 = 28; + // let off_caps3 = 29; + // let off_caps4 = 30; + + // Parse header + + const header = new Int32Array( buffer, 0, headerLengthInt ); + + if ( header[ off_magic ] !== DDS_MAGIC ) { + + console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' ); + return dds; + + } + + if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) { + + console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' ); + return dds; + + } + + let blockBytes; + + const fourCC = header[ off_pfFourCC ]; + + let isRGBAUncompressed = false; + + switch ( fourCC ) { + + case FOURCC_DXT1: + + blockBytes = 8; + dds.format = RGB_S3TC_DXT1_Format; + break; + + case FOURCC_DXT3: + + blockBytes = 16; + dds.format = RGBA_S3TC_DXT3_Format; + break; + + case FOURCC_DXT5: + + blockBytes = 16; + dds.format = RGBA_S3TC_DXT5_Format; + break; + + case FOURCC_ETC1: + + blockBytes = 8; + dds.format = RGB_ETC1_Format; + break; + + default: + + if ( header[ off_RGBBitCount ] === 32 + && header[ off_RBitMask ] & 0xff0000 + && header[ off_GBitMask ] & 0xff00 + && header[ off_BBitMask ] & 0xff + && header[ off_ABitMask ] & 0xff000000 ) { + + isRGBAUncompressed = true; + blockBytes = 64; + dds.format = RGBAFormat; + + } else { + + console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) ); + return dds; + + } + + } + + dds.mipmapCount = 1; + + if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) { + + dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] ); + + } + + const caps2 = header[ off_caps2 ]; + dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false; + if ( dds.isCubemap && ( + ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) || + ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ ) + ) ) { + + console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' ); + return dds; + + } + + dds.width = header[ off_width ]; + dds.height = header[ off_height ]; + + let dataOffset = header[ off_size ] + 4; + + // Extract mipmaps buffers + + const faces = dds.isCubemap ? 6 : 1; + + for ( let face = 0; face < faces; face ++ ) { + + let width = dds.width; + let height = dds.height; + + for ( let i = 0; i < dds.mipmapCount; i ++ ) { + + let byteArray, dataLength; + + if ( isRGBAUncompressed ) { + + byteArray = loadARGBMip( buffer, dataOffset, width, height ); + dataLength = byteArray.length; + + } else { + + dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; + byteArray = new Uint8Array( buffer, dataOffset, dataLength ); + + } + + const mipmap = { 'data': byteArray, 'width': width, 'height': height }; + dds.mipmaps.push( mipmap ); + + dataOffset += dataLength; + + width = Math.max( width >> 1, 1 ); + height = Math.max( height >> 1, 1 ); + + } + + } + + return dds; + + } + +} + +export { DDSLoader }; diff --git a/jsm/loaders/DRACOLoader.js b/jsm/loaders/DRACOLoader.js new file mode 100644 index 0000000..53afc5b --- /dev/null +++ b/jsm/loaders/DRACOLoader.js @@ -0,0 +1,587 @@ +import { + BufferAttribute, + BufferGeometry, + FileLoader, + Loader +} from 'three'; + +const _taskCache = new WeakMap(); + +class DRACOLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.decoderPath = ''; + this.decoderConfig = {}; + this.decoderBinary = null; + this.decoderPending = null; + + this.workerLimit = 4; + this.workerPool = []; + this.workerNextTaskID = 1; + this.workerSourceURL = ''; + + this.defaultAttributeIDs = { + position: 'POSITION', + normal: 'NORMAL', + color: 'COLOR', + uv: 'TEX_COORD' + }; + this.defaultAttributeTypes = { + position: 'Float32Array', + normal: 'Float32Array', + color: 'Float32Array', + uv: 'Float32Array' + }; + + } + + setDecoderPath( path ) { + + this.decoderPath = path; + + return this; + + } + + setDecoderConfig( config ) { + + this.decoderConfig = config; + + return this; + + } + + setWorkerLimit( workerLimit ) { + + this.workerLimit = workerLimit; + + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, ( buffer ) => { + + const taskConfig = { + attributeIDs: this.defaultAttributeIDs, + attributeTypes: this.defaultAttributeTypes, + useUniqueIDs: false + }; + + this.decodeGeometry( buffer, taskConfig ) + .then( onLoad ) + .catch( onError ); + + }, onProgress, onError ); + + } + + /** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */ + decodeDracoFile( buffer, callback, attributeIDs, attributeTypes ) { + + const taskConfig = { + attributeIDs: attributeIDs || this.defaultAttributeIDs, + attributeTypes: attributeTypes || this.defaultAttributeTypes, + useUniqueIDs: !! attributeIDs + }; + + this.decodeGeometry( buffer, taskConfig ).then( callback ); + + } + + decodeGeometry( buffer, taskConfig ) { + + // TODO: For backward-compatibility, support 'attributeTypes' objects containing + // references (rather than names) to typed array constructors. These must be + // serialized before sending them to the worker. + for ( const attribute in taskConfig.attributeTypes ) { + + const type = taskConfig.attributeTypes[ attribute ]; + + if ( type.BYTES_PER_ELEMENT !== undefined ) { + + taskConfig.attributeTypes[ attribute ] = type.name; + + } + + } + + // + + const taskKey = JSON.stringify( taskConfig ); + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + if ( cachedTask.key === taskKey ) { + + return cachedTask.promise; + + } else if ( buffer.byteLength === 0 ) { + + // Technically, it would be possible to wait for the previous task to complete, + // transfer the buffer back, and decode again with the second configuration. That + // is complex, and I don't know of any reason to decode a Draco buffer twice in + // different ways, so this is left unimplemented. + throw new Error( + + 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + + 'settings. Buffer has already been transferred.' + + ); + + } + + } + + // + + let worker; + const taskID = this.workerNextTaskID ++; + const taskCost = buffer.byteLength; + + // Obtain a worker and assign a task, and construct a geometry instance + // when the task completes. + const geometryPending = this._getWorker( taskID, taskCost ) + .then( ( _worker ) => { + + worker = _worker; + + return new Promise( ( resolve, reject ) => { + + worker._callbacks[ taskID ] = { resolve, reject }; + + worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] ); + + // this.debug(); + + } ); + + } ) + .then( ( message ) => this._createGeometry( message.geometry ) ); + + // Remove task from the task list. + // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416) + geometryPending + .catch( () => true ) + .then( () => { + + if ( worker && taskID ) { + + this._releaseTask( worker, taskID ); + + // this.debug(); + + } + + } ); + + // Cache the task result. + _taskCache.set( buffer, { + + key: taskKey, + promise: geometryPending + + } ); + + return geometryPending; + + } + + _createGeometry( geometryData ) { + + const geometry = new BufferGeometry(); + + if ( geometryData.index ) { + + geometry.setIndex( new BufferAttribute( geometryData.index.array, 1 ) ); + + } + + for ( let i = 0; i < geometryData.attributes.length; i ++ ) { + + const attribute = geometryData.attributes[ i ]; + const name = attribute.name; + const array = attribute.array; + const itemSize = attribute.itemSize; + + geometry.setAttribute( name, new BufferAttribute( array, itemSize ) ); + + } + + return geometry; + + } + + _loadLibrary( url, responseType ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.decoderPath ); + loader.setResponseType( responseType ); + loader.setWithCredentials( this.withCredentials ); + + return new Promise( ( resolve, reject ) => { + + loader.load( url, resolve, undefined, reject ); + + } ); + + } + + preload() { + + this._initDecoder(); + + return this; + + } + + _initDecoder() { + + if ( this.decoderPending ) return this.decoderPending; + + const useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js'; + const librariesPending = []; + + if ( useJS ) { + + librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) ); + + } else { + + librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) ); + librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) ); + + } + + this.decoderPending = Promise.all( librariesPending ) + .then( ( libraries ) => { + + const jsContent = libraries[ 0 ]; + + if ( ! useJS ) { + + this.decoderConfig.wasmBinary = libraries[ 1 ]; + + } + + const fn = DRACOWorker.toString(); + + const body = [ + '/* draco decoder */', + jsContent, + '', + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + + } ); + + return this.decoderPending; + + } + + _getWorker( taskID, taskCost ) { + + return this._initDecoder().then( () => { + + if ( this.workerPool.length < this.workerLimit ) { + + const worker = new Worker( this.workerSourceURL ); + + worker._callbacks = {}; + worker._taskCosts = {}; + worker._taskLoad = 0; + + worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } ); + + worker.onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'decode': + worker._callbacks[ message.id ].resolve( message ); + break; + + case 'error': + worker._callbacks[ message.id ].reject( message ); + break; + + default: + console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' ); + + } + + }; + + this.workerPool.push( worker ); + + } else { + + this.workerPool.sort( function ( a, b ) { + + return a._taskLoad > b._taskLoad ? - 1 : 1; + + } ); + + } + + const worker = this.workerPool[ this.workerPool.length - 1 ]; + worker._taskCosts[ taskID ] = taskCost; + worker._taskLoad += taskCost; + return worker; + + } ); + + } + + _releaseTask( worker, taskID ) { + + worker._taskLoad -= worker._taskCosts[ taskID ]; + delete worker._callbacks[ taskID ]; + delete worker._taskCosts[ taskID ]; + + } + + debug() { + + console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) ); + + } + + dispose() { + + for ( let i = 0; i < this.workerPool.length; ++ i ) { + + this.workerPool[ i ].terminate(); + + } + + this.workerPool.length = 0; + + return this; + + } + +} + +/* WEB WORKER */ + +function DRACOWorker() { + + let decoderConfig; + let decoderPending; + + onmessage = function ( e ) { + + const message = e.data; + + switch ( message.type ) { + + case 'init': + decoderConfig = message.decoderConfig; + decoderPending = new Promise( function ( resolve/*, reject*/ ) { + + decoderConfig.onModuleLoaded = function ( draco ) { + + // Module is Promise-like. Wrap before resolving to avoid loop. + resolve( { draco: draco } ); + + }; + + DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef + + } ); + break; + + case 'decode': + const buffer = message.buffer; + const taskConfig = message.taskConfig; + decoderPending.then( ( module ) => { + + const draco = module.draco; + const decoder = new draco.Decoder(); + const decoderBuffer = new draco.DecoderBuffer(); + decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength ); + + try { + + const geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig ); + + const buffers = geometry.attributes.map( ( attr ) => attr.array.buffer ); + + if ( geometry.index ) buffers.push( geometry.index.array.buffer ); + + self.postMessage( { type: 'decode', id: message.id, geometry }, buffers ); + + } catch ( error ) { + + console.error( error ); + + self.postMessage( { type: 'error', id: message.id, error: error.message } ); + + } finally { + + draco.destroy( decoderBuffer ); + draco.destroy( decoder ); + + } + + } ); + break; + + } + + }; + + function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) { + + const attributeIDs = taskConfig.attributeIDs; + const attributeTypes = taskConfig.attributeTypes; + + let dracoGeometry; + let decodingStatus; + + const geometryType = decoder.GetEncodedGeometryType( decoderBuffer ); + + if ( geometryType === draco.TRIANGULAR_MESH ) { + + dracoGeometry = new draco.Mesh(); + decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry ); + + } else if ( geometryType === draco.POINT_CLOUD ) { + + dracoGeometry = new draco.PointCloud(); + decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry ); + + } else { + + throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' ); + + } + + if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) { + + throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() ); + + } + + const geometry = { index: null, attributes: [] }; + + // Gather all vertex attributes. + for ( const attributeName in attributeIDs ) { + + const attributeType = self[ attributeTypes[ attributeName ] ]; + + let attribute; + let attributeID; + + // A Draco file may be created with default vertex attributes, whose attribute IDs + // are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively, + // a Draco file may contain a custom set of attributes, identified by known unique + // IDs. glTF files always do the latter, and `.drc` files typically do the former. + if ( taskConfig.useUniqueIDs ) { + + attributeID = attributeIDs[ attributeName ]; + attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID ); + + } else { + + attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] ); + + if ( attributeID === - 1 ) continue; + + attribute = decoder.GetAttribute( dracoGeometry, attributeID ); + + } + + geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) ); + + } + + // Add index. + if ( geometryType === draco.TRIANGULAR_MESH ) { + + geometry.index = decodeIndex( draco, decoder, dracoGeometry ); + + } + + draco.destroy( dracoGeometry ); + + return geometry; + + } + + function decodeIndex( draco, decoder, dracoGeometry ) { + + const numFaces = dracoGeometry.num_faces(); + const numIndices = numFaces * 3; + const byteLength = numIndices * 4; + + const ptr = draco._malloc( byteLength ); + decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr ); + const index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice(); + draco._free( ptr ); + + return { array: index, itemSize: 1 }; + + } + + function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) { + + const numComponents = attribute.num_components(); + const numPoints = dracoGeometry.num_points(); + const numValues = numPoints * numComponents; + const byteLength = numValues * attributeType.BYTES_PER_ELEMENT; + const dataType = getDracoDataType( draco, attributeType ); + + const ptr = draco._malloc( byteLength ); + decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr ); + const array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice(); + draco._free( ptr ); + + return { + name: attributeName, + array: array, + itemSize: numComponents + }; + + } + + function getDracoDataType( draco, attributeType ) { + + switch ( attributeType ) { + + case Float32Array: return draco.DT_FLOAT32; + case Int8Array: return draco.DT_INT8; + case Int16Array: return draco.DT_INT16; + case Int32Array: return draco.DT_INT32; + case Uint8Array: return draco.DT_UINT8; + case Uint16Array: return draco.DT_UINT16; + case Uint32Array: return draco.DT_UINT32; + + } + + } + +} + +export { DRACOLoader }; diff --git a/jsm/loaders/EXRLoader.js b/jsm/loaders/EXRLoader.js new file mode 100644 index 0000000..a4747ec --- /dev/null +++ b/jsm/loaders/EXRLoader.js @@ -0,0 +1,2320 @@ +import { + DataTextureLoader, + DataUtils, + FloatType, + HalfFloatType, + LinearEncoding, + LinearFilter, + RedFormat, + RGBAFormat +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; + +/** + * OpenEXR loader currently supports uncompressed, ZIP(S), RLE, PIZ and DWA/B compression. + * Supports reading as UnsignedByte, HalfFloat and Float type data texture. + * + * Referred to the original Industrial Light & Magic OpenEXR implementation and the TinyEXR / Syoyo Fujita + * implementation, so I have preserved their copyright notices. + */ + +// /* +// Copyright (c) 2014 - 2017, Syoyo Fujita +// All rights reserved. + +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of the Syoyo Fujita nor the +// names of its contributors may be used to endorse or promote products +// derived from this software without specific prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY +// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// */ + +// // TinyEXR contains some OpenEXR code, which is licensed under ------------ + +// /////////////////////////////////////////////////////////////////////////// +// // +// // Copyright (c) 2002, Industrial Light & Magic, a division of Lucas +// // Digital Ltd. LLC +// // +// // All rights reserved. +// // +// // Redistribution and use in source and binary forms, with or without +// // modification, are permitted provided that the following conditions are +// // met: +// // * Redistributions of source code must retain the above copyright +// // notice, this list of conditions and the following disclaimer. +// // * Redistributions in binary form must reproduce the above +// // copyright notice, this list of conditions and the following disclaimer +// // in the documentation and/or other materials provided with the +// // distribution. +// // * Neither the name of Industrial Light & Magic nor the names of +// // its contributors may be used to endorse or promote products derived +// // from this software without specific prior written permission. +// // +// // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// // +// /////////////////////////////////////////////////////////////////////////// + +// // End of OpenEXR license ------------------------------------------------- + +class EXRLoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + + } + + parse( buffer ) { + + const USHORT_RANGE = ( 1 << 16 ); + const BITMAP_SIZE = ( USHORT_RANGE >> 3 ); + + const HUF_ENCBITS = 16; // literal (value) bit length + const HUF_DECBITS = 14; // decoding bit size (>= 8) + + const HUF_ENCSIZE = ( 1 << HUF_ENCBITS ) + 1; // encoding table size + const HUF_DECSIZE = 1 << HUF_DECBITS; // decoding table size + const HUF_DECMASK = HUF_DECSIZE - 1; + + const NBITS = 16; + const A_OFFSET = 1 << ( NBITS - 1 ); + const MOD_MASK = ( 1 << NBITS ) - 1; + + const SHORT_ZEROCODE_RUN = 59; + const LONG_ZEROCODE_RUN = 63; + const SHORTEST_LONG_RUN = 2 + LONG_ZEROCODE_RUN - SHORT_ZEROCODE_RUN; + + const ULONG_SIZE = 8; + const FLOAT32_SIZE = 4; + const INT32_SIZE = 4; + const INT16_SIZE = 2; + const INT8_SIZE = 1; + + const STATIC_HUFFMAN = 0; + const DEFLATE = 1; + + const UNKNOWN = 0; + const LOSSY_DCT = 1; + const RLE = 2; + + const logBase = Math.pow( 2.7182818, 2.2 ); + + function reverseLutFromBitmap( bitmap, lut ) { + + let k = 0; + + for ( let i = 0; i < USHORT_RANGE; ++ i ) { + + if ( ( i == 0 ) || ( bitmap[ i >> 3 ] & ( 1 << ( i & 7 ) ) ) ) { + + lut[ k ++ ] = i; + + } + + } + + const n = k - 1; + + while ( k < USHORT_RANGE ) lut[ k ++ ] = 0; + + return n; + + } + + function hufClearDecTable( hdec ) { + + for ( let i = 0; i < HUF_DECSIZE; i ++ ) { + + hdec[ i ] = {}; + hdec[ i ].len = 0; + hdec[ i ].lit = 0; + hdec[ i ].p = null; + + } + + } + + const getBitsReturn = { l: 0, c: 0, lc: 0 }; + + function getBits( nBits, c, lc, uInt8Array, inOffset ) { + + while ( lc < nBits ) { + + c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset ); + lc += 8; + + } + + lc -= nBits; + + getBitsReturn.l = ( c >> lc ) & ( ( 1 << nBits ) - 1 ); + getBitsReturn.c = c; + getBitsReturn.lc = lc; + + } + + const hufTableBuffer = new Array( 59 ); + + function hufCanonicalCodeTable( hcode ) { + + for ( let i = 0; i <= 58; ++ i ) hufTableBuffer[ i ] = 0; + for ( let i = 0; i < HUF_ENCSIZE; ++ i ) hufTableBuffer[ hcode[ i ] ] += 1; + + let c = 0; + + for ( let i = 58; i > 0; -- i ) { + + const nc = ( ( c + hufTableBuffer[ i ] ) >> 1 ); + hufTableBuffer[ i ] = c; + c = nc; + + } + + for ( let i = 0; i < HUF_ENCSIZE; ++ i ) { + + const l = hcode[ i ]; + if ( l > 0 ) hcode[ i ] = l | ( hufTableBuffer[ l ] ++ << 6 ); + + } + + } + + function hufUnpackEncTable( uInt8Array, inOffset, ni, im, iM, hcode ) { + + const p = inOffset; + let c = 0; + let lc = 0; + + for ( ; im <= iM; im ++ ) { + + if ( p.value - inOffset.value > ni ) return false; + + getBits( 6, c, lc, uInt8Array, p ); + + const l = getBitsReturn.l; + c = getBitsReturn.c; + lc = getBitsReturn.lc; + + hcode[ im ] = l; + + if ( l == LONG_ZEROCODE_RUN ) { + + if ( p.value - inOffset.value > ni ) { + + throw new Error( 'Something wrong with hufUnpackEncTable' ); + + } + + getBits( 8, c, lc, uInt8Array, p ); + + let zerun = getBitsReturn.l + SHORTEST_LONG_RUN; + c = getBitsReturn.c; + lc = getBitsReturn.lc; + + if ( im + zerun > iM + 1 ) { + + throw new Error( 'Something wrong with hufUnpackEncTable' ); + + } + + while ( zerun -- ) hcode[ im ++ ] = 0; + + im --; + + } else if ( l >= SHORT_ZEROCODE_RUN ) { + + let zerun = l - SHORT_ZEROCODE_RUN + 2; + + if ( im + zerun > iM + 1 ) { + + throw new Error( 'Something wrong with hufUnpackEncTable' ); + + } + + while ( zerun -- ) hcode[ im ++ ] = 0; + + im --; + + } + + } + + hufCanonicalCodeTable( hcode ); + + } + + function hufLength( code ) { + + return code & 63; + + } + + function hufCode( code ) { + + return code >> 6; + + } + + function hufBuildDecTable( hcode, im, iM, hdecod ) { + + for ( ; im <= iM; im ++ ) { + + const c = hufCode( hcode[ im ] ); + const l = hufLength( hcode[ im ] ); + + if ( c >> l ) { + + throw new Error( 'Invalid table entry' ); + + } + + if ( l > HUF_DECBITS ) { + + const pl = hdecod[ ( c >> ( l - HUF_DECBITS ) ) ]; + + if ( pl.len ) { + + throw new Error( 'Invalid table entry' ); + + } + + pl.lit ++; + + if ( pl.p ) { + + const p = pl.p; + pl.p = new Array( pl.lit ); + + for ( let i = 0; i < pl.lit - 1; ++ i ) { + + pl.p[ i ] = p[ i ]; + + } + + } else { + + pl.p = new Array( 1 ); + + } + + pl.p[ pl.lit - 1 ] = im; + + } else if ( l ) { + + let plOffset = 0; + + for ( let i = 1 << ( HUF_DECBITS - l ); i > 0; i -- ) { + + const pl = hdecod[ ( c << ( HUF_DECBITS - l ) ) + plOffset ]; + + if ( pl.len || pl.p ) { + + throw new Error( 'Invalid table entry' ); + + } + + pl.len = l; + pl.lit = im; + + plOffset ++; + + } + + } + + } + + return true; + + } + + const getCharReturn = { c: 0, lc: 0 }; + + function getChar( c, lc, uInt8Array, inOffset ) { + + c = ( c << 8 ) | parseUint8Array( uInt8Array, inOffset ); + lc += 8; + + getCharReturn.c = c; + getCharReturn.lc = lc; + + } + + const getCodeReturn = { c: 0, lc: 0 }; + + function getCode( po, rlc, c, lc, uInt8Array, inOffset, outBuffer, outBufferOffset, outBufferEndOffset ) { + + if ( po == rlc ) { + + if ( lc < 8 ) { + + getChar( c, lc, uInt8Array, inOffset ); + c = getCharReturn.c; + lc = getCharReturn.lc; + + } + + lc -= 8; + + let cs = ( c >> lc ); + cs = new Uint8Array( [ cs ] )[ 0 ]; + + if ( outBufferOffset.value + cs > outBufferEndOffset ) { + + return false; + + } + + const s = outBuffer[ outBufferOffset.value - 1 ]; + + while ( cs -- > 0 ) { + + outBuffer[ outBufferOffset.value ++ ] = s; + + } + + } else if ( outBufferOffset.value < outBufferEndOffset ) { + + outBuffer[ outBufferOffset.value ++ ] = po; + + } else { + + return false; + + } + + getCodeReturn.c = c; + getCodeReturn.lc = lc; + + } + + function UInt16( value ) { + + return ( value & 0xFFFF ); + + } + + function Int16( value ) { + + const ref = UInt16( value ); + return ( ref > 0x7FFF ) ? ref - 0x10000 : ref; + + } + + const wdec14Return = { a: 0, b: 0 }; + + function wdec14( l, h ) { + + const ls = Int16( l ); + const hs = Int16( h ); + + const hi = hs; + const ai = ls + ( hi & 1 ) + ( hi >> 1 ); + + const as = ai; + const bs = ai - hi; + + wdec14Return.a = as; + wdec14Return.b = bs; + + } + + function wdec16( l, h ) { + + const m = UInt16( l ); + const d = UInt16( h ); + + const bb = ( m - ( d >> 1 ) ) & MOD_MASK; + const aa = ( d + bb - A_OFFSET ) & MOD_MASK; + + wdec14Return.a = aa; + wdec14Return.b = bb; + + } + + function wav2Decode( buffer, j, nx, ox, ny, oy, mx ) { + + const w14 = mx < ( 1 << 14 ); + const n = ( nx > ny ) ? ny : nx; + let p = 1; + let p2; + let py; + + while ( p <= n ) p <<= 1; + + p >>= 1; + p2 = p; + p >>= 1; + + while ( p >= 1 ) { + + py = 0; + const ey = py + oy * ( ny - p2 ); + const oy1 = oy * p; + const oy2 = oy * p2; + const ox1 = ox * p; + const ox2 = ox * p2; + let i00, i01, i10, i11; + + for ( ; py <= ey; py += oy2 ) { + + let px = py; + const ex = py + ox * ( nx - p2 ); + + for ( ; px <= ex; px += ox2 ) { + + const p01 = px + ox1; + const p10 = px + oy1; + const p11 = p10 + ox1; + + if ( w14 ) { + + wdec14( buffer[ px + j ], buffer[ p10 + j ] ); + + i00 = wdec14Return.a; + i10 = wdec14Return.b; + + wdec14( buffer[ p01 + j ], buffer[ p11 + j ] ); + + i01 = wdec14Return.a; + i11 = wdec14Return.b; + + wdec14( i00, i01 ); + + buffer[ px + j ] = wdec14Return.a; + buffer[ p01 + j ] = wdec14Return.b; + + wdec14( i10, i11 ); + + buffer[ p10 + j ] = wdec14Return.a; + buffer[ p11 + j ] = wdec14Return.b; + + } else { + + wdec16( buffer[ px + j ], buffer[ p10 + j ] ); + + i00 = wdec14Return.a; + i10 = wdec14Return.b; + + wdec16( buffer[ p01 + j ], buffer[ p11 + j ] ); + + i01 = wdec14Return.a; + i11 = wdec14Return.b; + + wdec16( i00, i01 ); + + buffer[ px + j ] = wdec14Return.a; + buffer[ p01 + j ] = wdec14Return.b; + + wdec16( i10, i11 ); + + buffer[ p10 + j ] = wdec14Return.a; + buffer[ p11 + j ] = wdec14Return.b; + + + } + + } + + if ( nx & p ) { + + const p10 = px + oy1; + + if ( w14 ) + wdec14( buffer[ px + j ], buffer[ p10 + j ] ); + else + wdec16( buffer[ px + j ], buffer[ p10 + j ] ); + + i00 = wdec14Return.a; + buffer[ p10 + j ] = wdec14Return.b; + + buffer[ px + j ] = i00; + + } + + } + + if ( ny & p ) { + + let px = py; + const ex = py + ox * ( nx - p2 ); + + for ( ; px <= ex; px += ox2 ) { + + const p01 = px + ox1; + + if ( w14 ) + wdec14( buffer[ px + j ], buffer[ p01 + j ] ); + else + wdec16( buffer[ px + j ], buffer[ p01 + j ] ); + + i00 = wdec14Return.a; + buffer[ p01 + j ] = wdec14Return.b; + + buffer[ px + j ] = i00; + + } + + } + + p2 = p; + p >>= 1; + + } + + return py; + + } + + function hufDecode( encodingTable, decodingTable, uInt8Array, inOffset, ni, rlc, no, outBuffer, outOffset ) { + + let c = 0; + let lc = 0; + const outBufferEndOffset = no; + const inOffsetEnd = Math.trunc( inOffset.value + ( ni + 7 ) / 8 ); + + while ( inOffset.value < inOffsetEnd ) { + + getChar( c, lc, uInt8Array, inOffset ); + + c = getCharReturn.c; + lc = getCharReturn.lc; + + while ( lc >= HUF_DECBITS ) { + + const index = ( c >> ( lc - HUF_DECBITS ) ) & HUF_DECMASK; + const pl = decodingTable[ index ]; + + if ( pl.len ) { + + lc -= pl.len; + + getCode( pl.lit, rlc, c, lc, uInt8Array, inOffset, outBuffer, outOffset, outBufferEndOffset ); + + c = getCodeReturn.c; + lc = getCodeReturn.lc; + + } else { + + if ( ! pl.p ) { + + throw new Error( 'hufDecode issues' ); + + } + + let j; + + for ( j = 0; j < pl.lit; j ++ ) { + + const l = hufLength( encodingTable[ pl.p[ j ] ] ); + + while ( lc < l && inOffset.value < inOffsetEnd ) { + + getChar( c, lc, uInt8Array, inOffset ); + + c = getCharReturn.c; + lc = getCharReturn.lc; + + } + + if ( lc >= l ) { + + if ( hufCode( encodingTable[ pl.p[ j ] ] ) == ( ( c >> ( lc - l ) ) & ( ( 1 << l ) - 1 ) ) ) { + + lc -= l; + + getCode( pl.p[ j ], rlc, c, lc, uInt8Array, inOffset, outBuffer, outOffset, outBufferEndOffset ); + + c = getCodeReturn.c; + lc = getCodeReturn.lc; + + break; + + } + + } + + } + + if ( j == pl.lit ) { + + throw new Error( 'hufDecode issues' ); + + } + + } + + } + + } + + const i = ( 8 - ni ) & 7; + + c >>= i; + lc -= i; + + while ( lc > 0 ) { + + const pl = decodingTable[ ( c << ( HUF_DECBITS - lc ) ) & HUF_DECMASK ]; + + if ( pl.len ) { + + lc -= pl.len; + + getCode( pl.lit, rlc, c, lc, uInt8Array, inOffset, outBuffer, outOffset, outBufferEndOffset ); + + c = getCodeReturn.c; + lc = getCodeReturn.lc; + + } else { + + throw new Error( 'hufDecode issues' ); + + } + + } + + return true; + + } + + function hufUncompress( uInt8Array, inDataView, inOffset, nCompressed, outBuffer, nRaw ) { + + const outOffset = { value: 0 }; + const initialInOffset = inOffset.value; + + const im = parseUint32( inDataView, inOffset ); + const iM = parseUint32( inDataView, inOffset ); + + inOffset.value += 4; + + const nBits = parseUint32( inDataView, inOffset ); + + inOffset.value += 4; + + if ( im < 0 || im >= HUF_ENCSIZE || iM < 0 || iM >= HUF_ENCSIZE ) { + + throw new Error( 'Something wrong with HUF_ENCSIZE' ); + + } + + const freq = new Array( HUF_ENCSIZE ); + const hdec = new Array( HUF_DECSIZE ); + + hufClearDecTable( hdec ); + + const ni = nCompressed - ( inOffset.value - initialInOffset ); + + hufUnpackEncTable( uInt8Array, inOffset, ni, im, iM, freq ); + + if ( nBits > 8 * ( nCompressed - ( inOffset.value - initialInOffset ) ) ) { + + throw new Error( 'Something wrong with hufUncompress' ); + + } + + hufBuildDecTable( freq, im, iM, hdec ); + + hufDecode( freq, hdec, uInt8Array, inOffset, nBits, iM, nRaw, outBuffer, outOffset ); + + } + + function applyLut( lut, data, nData ) { + + for ( let i = 0; i < nData; ++ i ) { + + data[ i ] = lut[ data[ i ] ]; + + } + + } + + function predictor( source ) { + + for ( let t = 1; t < source.length; t ++ ) { + + const d = source[ t - 1 ] + source[ t ] - 128; + source[ t ] = d; + + } + + } + + function interleaveScalar( source, out ) { + + let t1 = 0; + let t2 = Math.floor( ( source.length + 1 ) / 2 ); + let s = 0; + const stop = source.length - 1; + + while ( true ) { + + if ( s > stop ) break; + out[ s ++ ] = source[ t1 ++ ]; + + if ( s > stop ) break; + out[ s ++ ] = source[ t2 ++ ]; + + } + + } + + function decodeRunLength( source ) { + + let size = source.byteLength; + const out = new Array(); + let p = 0; + + const reader = new DataView( source ); + + while ( size > 0 ) { + + const l = reader.getInt8( p ++ ); + + if ( l < 0 ) { + + const count = - l; + size -= count + 1; + + for ( let i = 0; i < count; i ++ ) { + + out.push( reader.getUint8( p ++ ) ); + + } + + + } else { + + const count = l; + size -= 2; + + const value = reader.getUint8( p ++ ); + + for ( let i = 0; i < count + 1; i ++ ) { + + out.push( value ); + + } + + } + + } + + return out; + + } + + function lossyDctDecode( cscSet, rowPtrs, channelData, acBuffer, dcBuffer, outBuffer ) { + + let dataView = new DataView( outBuffer.buffer ); + + const width = channelData[ cscSet.idx[ 0 ] ].width; + const height = channelData[ cscSet.idx[ 0 ] ].height; + + const numComp = 3; + + const numFullBlocksX = Math.floor( width / 8.0 ); + const numBlocksX = Math.ceil( width / 8.0 ); + const numBlocksY = Math.ceil( height / 8.0 ); + const leftoverX = width - ( numBlocksX - 1 ) * 8; + const leftoverY = height - ( numBlocksY - 1 ) * 8; + + const currAcComp = { value: 0 }; + const currDcComp = new Array( numComp ); + const dctData = new Array( numComp ); + const halfZigBlock = new Array( numComp ); + const rowBlock = new Array( numComp ); + const rowOffsets = new Array( numComp ); + + for ( let comp = 0; comp < numComp; ++ comp ) { + + rowOffsets[ comp ] = rowPtrs[ cscSet.idx[ comp ] ]; + currDcComp[ comp ] = ( comp < 1 ) ? 0 : currDcComp[ comp - 1 ] + numBlocksX * numBlocksY; + dctData[ comp ] = new Float32Array( 64 ); + halfZigBlock[ comp ] = new Uint16Array( 64 ); + rowBlock[ comp ] = new Uint16Array( numBlocksX * 64 ); + + } + + for ( let blocky = 0; blocky < numBlocksY; ++ blocky ) { + + let maxY = 8; + + if ( blocky == numBlocksY - 1 ) + maxY = leftoverY; + + let maxX = 8; + + for ( let blockx = 0; blockx < numBlocksX; ++ blockx ) { + + if ( blockx == numBlocksX - 1 ) + maxX = leftoverX; + + for ( let comp = 0; comp < numComp; ++ comp ) { + + halfZigBlock[ comp ].fill( 0 ); + + // set block DC component + halfZigBlock[ comp ][ 0 ] = dcBuffer[ currDcComp[ comp ] ++ ]; + // set block AC components + unRleAC( currAcComp, acBuffer, halfZigBlock[ comp ] ); + + // UnZigZag block to float + unZigZag( halfZigBlock[ comp ], dctData[ comp ] ); + // decode float dct + dctInverse( dctData[ comp ] ); + + } + + if ( numComp == 3 ) { + + csc709Inverse( dctData ); + + } + + for ( let comp = 0; comp < numComp; ++ comp ) { + + convertToHalf( dctData[ comp ], rowBlock[ comp ], blockx * 64 ); + + } + + } // blockx + + let offset = 0; + + for ( let comp = 0; comp < numComp; ++ comp ) { + + const type = channelData[ cscSet.idx[ comp ] ].type; + + for ( let y = 8 * blocky; y < 8 * blocky + maxY; ++ y ) { + + offset = rowOffsets[ comp ][ y ]; + + for ( let blockx = 0; blockx < numFullBlocksX; ++ blockx ) { + + const src = blockx * 64 + ( ( y & 0x7 ) * 8 ); + + dataView.setUint16( offset + 0 * INT16_SIZE * type, rowBlock[ comp ][ src + 0 ], true ); + dataView.setUint16( offset + 1 * INT16_SIZE * type, rowBlock[ comp ][ src + 1 ], true ); + dataView.setUint16( offset + 2 * INT16_SIZE * type, rowBlock[ comp ][ src + 2 ], true ); + dataView.setUint16( offset + 3 * INT16_SIZE * type, rowBlock[ comp ][ src + 3 ], true ); + + dataView.setUint16( offset + 4 * INT16_SIZE * type, rowBlock[ comp ][ src + 4 ], true ); + dataView.setUint16( offset + 5 * INT16_SIZE * type, rowBlock[ comp ][ src + 5 ], true ); + dataView.setUint16( offset + 6 * INT16_SIZE * type, rowBlock[ comp ][ src + 6 ], true ); + dataView.setUint16( offset + 7 * INT16_SIZE * type, rowBlock[ comp ][ src + 7 ], true ); + + offset += 8 * INT16_SIZE * type; + + } + + } + + // handle partial X blocks + if ( numFullBlocksX != numBlocksX ) { + + for ( let y = 8 * blocky; y < 8 * blocky + maxY; ++ y ) { + + const offset = rowOffsets[ comp ][ y ] + 8 * numFullBlocksX * INT16_SIZE * type; + const src = numFullBlocksX * 64 + ( ( y & 0x7 ) * 8 ); + + for ( let x = 0; x < maxX; ++ x ) { + + dataView.setUint16( offset + x * INT16_SIZE * type, rowBlock[ comp ][ src + x ], true ); + + } + + } + + } + + } // comp + + } // blocky + + const halfRow = new Uint16Array( width ); + dataView = new DataView( outBuffer.buffer ); + + // convert channels back to float, if needed + for ( let comp = 0; comp < numComp; ++ comp ) { + + channelData[ cscSet.idx[ comp ] ].decoded = true; + const type = channelData[ cscSet.idx[ comp ] ].type; + + if ( channelData[ comp ].type != 2 ) continue; + + for ( let y = 0; y < height; ++ y ) { + + const offset = rowOffsets[ comp ][ y ]; + + for ( let x = 0; x < width; ++ x ) { + + halfRow[ x ] = dataView.getUint16( offset + x * INT16_SIZE * type, true ); + + } + + for ( let x = 0; x < width; ++ x ) { + + dataView.setFloat32( offset + x * INT16_SIZE * type, decodeFloat16( halfRow[ x ] ), true ); + + } + + } + + } + + } + + function unRleAC( currAcComp, acBuffer, halfZigBlock ) { + + let acValue; + let dctComp = 1; + + while ( dctComp < 64 ) { + + acValue = acBuffer[ currAcComp.value ]; + + if ( acValue == 0xff00 ) { + + dctComp = 64; + + } else if ( acValue >> 8 == 0xff ) { + + dctComp += acValue & 0xff; + + } else { + + halfZigBlock[ dctComp ] = acValue; + dctComp ++; + + } + + currAcComp.value ++; + + } + + } + + function unZigZag( src, dst ) { + + dst[ 0 ] = decodeFloat16( src[ 0 ] ); + dst[ 1 ] = decodeFloat16( src[ 1 ] ); + dst[ 2 ] = decodeFloat16( src[ 5 ] ); + dst[ 3 ] = decodeFloat16( src[ 6 ] ); + dst[ 4 ] = decodeFloat16( src[ 14 ] ); + dst[ 5 ] = decodeFloat16( src[ 15 ] ); + dst[ 6 ] = decodeFloat16( src[ 27 ] ); + dst[ 7 ] = decodeFloat16( src[ 28 ] ); + dst[ 8 ] = decodeFloat16( src[ 2 ] ); + dst[ 9 ] = decodeFloat16( src[ 4 ] ); + + dst[ 10 ] = decodeFloat16( src[ 7 ] ); + dst[ 11 ] = decodeFloat16( src[ 13 ] ); + dst[ 12 ] = decodeFloat16( src[ 16 ] ); + dst[ 13 ] = decodeFloat16( src[ 26 ] ); + dst[ 14 ] = decodeFloat16( src[ 29 ] ); + dst[ 15 ] = decodeFloat16( src[ 42 ] ); + dst[ 16 ] = decodeFloat16( src[ 3 ] ); + dst[ 17 ] = decodeFloat16( src[ 8 ] ); + dst[ 18 ] = decodeFloat16( src[ 12 ] ); + dst[ 19 ] = decodeFloat16( src[ 17 ] ); + + dst[ 20 ] = decodeFloat16( src[ 25 ] ); + dst[ 21 ] = decodeFloat16( src[ 30 ] ); + dst[ 22 ] = decodeFloat16( src[ 41 ] ); + dst[ 23 ] = decodeFloat16( src[ 43 ] ); + dst[ 24 ] = decodeFloat16( src[ 9 ] ); + dst[ 25 ] = decodeFloat16( src[ 11 ] ); + dst[ 26 ] = decodeFloat16( src[ 18 ] ); + dst[ 27 ] = decodeFloat16( src[ 24 ] ); + dst[ 28 ] = decodeFloat16( src[ 31 ] ); + dst[ 29 ] = decodeFloat16( src[ 40 ] ); + + dst[ 30 ] = decodeFloat16( src[ 44 ] ); + dst[ 31 ] = decodeFloat16( src[ 53 ] ); + dst[ 32 ] = decodeFloat16( src[ 10 ] ); + dst[ 33 ] = decodeFloat16( src[ 19 ] ); + dst[ 34 ] = decodeFloat16( src[ 23 ] ); + dst[ 35 ] = decodeFloat16( src[ 32 ] ); + dst[ 36 ] = decodeFloat16( src[ 39 ] ); + dst[ 37 ] = decodeFloat16( src[ 45 ] ); + dst[ 38 ] = decodeFloat16( src[ 52 ] ); + dst[ 39 ] = decodeFloat16( src[ 54 ] ); + + dst[ 40 ] = decodeFloat16( src[ 20 ] ); + dst[ 41 ] = decodeFloat16( src[ 22 ] ); + dst[ 42 ] = decodeFloat16( src[ 33 ] ); + dst[ 43 ] = decodeFloat16( src[ 38 ] ); + dst[ 44 ] = decodeFloat16( src[ 46 ] ); + dst[ 45 ] = decodeFloat16( src[ 51 ] ); + dst[ 46 ] = decodeFloat16( src[ 55 ] ); + dst[ 47 ] = decodeFloat16( src[ 60 ] ); + dst[ 48 ] = decodeFloat16( src[ 21 ] ); + dst[ 49 ] = decodeFloat16( src[ 34 ] ); + + dst[ 50 ] = decodeFloat16( src[ 37 ] ); + dst[ 51 ] = decodeFloat16( src[ 47 ] ); + dst[ 52 ] = decodeFloat16( src[ 50 ] ); + dst[ 53 ] = decodeFloat16( src[ 56 ] ); + dst[ 54 ] = decodeFloat16( src[ 59 ] ); + dst[ 55 ] = decodeFloat16( src[ 61 ] ); + dst[ 56 ] = decodeFloat16( src[ 35 ] ); + dst[ 57 ] = decodeFloat16( src[ 36 ] ); + dst[ 58 ] = decodeFloat16( src[ 48 ] ); + dst[ 59 ] = decodeFloat16( src[ 49 ] ); + + dst[ 60 ] = decodeFloat16( src[ 57 ] ); + dst[ 61 ] = decodeFloat16( src[ 58 ] ); + dst[ 62 ] = decodeFloat16( src[ 62 ] ); + dst[ 63 ] = decodeFloat16( src[ 63 ] ); + + } + + function dctInverse( data ) { + + const a = 0.5 * Math.cos( 3.14159 / 4.0 ); + const b = 0.5 * Math.cos( 3.14159 / 16.0 ); + const c = 0.5 * Math.cos( 3.14159 / 8.0 ); + const d = 0.5 * Math.cos( 3.0 * 3.14159 / 16.0 ); + const e = 0.5 * Math.cos( 5.0 * 3.14159 / 16.0 ); + const f = 0.5 * Math.cos( 3.0 * 3.14159 / 8.0 ); + const g = 0.5 * Math.cos( 7.0 * 3.14159 / 16.0 ); + + const alpha = new Array( 4 ); + const beta = new Array( 4 ); + const theta = new Array( 4 ); + const gamma = new Array( 4 ); + + for ( let row = 0; row < 8; ++ row ) { + + const rowPtr = row * 8; + + alpha[ 0 ] = c * data[ rowPtr + 2 ]; + alpha[ 1 ] = f * data[ rowPtr + 2 ]; + alpha[ 2 ] = c * data[ rowPtr + 6 ]; + alpha[ 3 ] = f * data[ rowPtr + 6 ]; + + beta[ 0 ] = b * data[ rowPtr + 1 ] + d * data[ rowPtr + 3 ] + e * data[ rowPtr + 5 ] + g * data[ rowPtr + 7 ]; + beta[ 1 ] = d * data[ rowPtr + 1 ] - g * data[ rowPtr + 3 ] - b * data[ rowPtr + 5 ] - e * data[ rowPtr + 7 ]; + beta[ 2 ] = e * data[ rowPtr + 1 ] - b * data[ rowPtr + 3 ] + g * data[ rowPtr + 5 ] + d * data[ rowPtr + 7 ]; + beta[ 3 ] = g * data[ rowPtr + 1 ] - e * data[ rowPtr + 3 ] + d * data[ rowPtr + 5 ] - b * data[ rowPtr + 7 ]; + + theta[ 0 ] = a * ( data[ rowPtr + 0 ] + data[ rowPtr + 4 ] ); + theta[ 3 ] = a * ( data[ rowPtr + 0 ] - data[ rowPtr + 4 ] ); + theta[ 1 ] = alpha[ 0 ] + alpha[ 3 ]; + theta[ 2 ] = alpha[ 1 ] - alpha[ 2 ]; + + gamma[ 0 ] = theta[ 0 ] + theta[ 1 ]; + gamma[ 1 ] = theta[ 3 ] + theta[ 2 ]; + gamma[ 2 ] = theta[ 3 ] - theta[ 2 ]; + gamma[ 3 ] = theta[ 0 ] - theta[ 1 ]; + + data[ rowPtr + 0 ] = gamma[ 0 ] + beta[ 0 ]; + data[ rowPtr + 1 ] = gamma[ 1 ] + beta[ 1 ]; + data[ rowPtr + 2 ] = gamma[ 2 ] + beta[ 2 ]; + data[ rowPtr + 3 ] = gamma[ 3 ] + beta[ 3 ]; + + data[ rowPtr + 4 ] = gamma[ 3 ] - beta[ 3 ]; + data[ rowPtr + 5 ] = gamma[ 2 ] - beta[ 2 ]; + data[ rowPtr + 6 ] = gamma[ 1 ] - beta[ 1 ]; + data[ rowPtr + 7 ] = gamma[ 0 ] - beta[ 0 ]; + + } + + for ( let column = 0; column < 8; ++ column ) { + + alpha[ 0 ] = c * data[ 16 + column ]; + alpha[ 1 ] = f * data[ 16 + column ]; + alpha[ 2 ] = c * data[ 48 + column ]; + alpha[ 3 ] = f * data[ 48 + column ]; + + beta[ 0 ] = b * data[ 8 + column ] + d * data[ 24 + column ] + e * data[ 40 + column ] + g * data[ 56 + column ]; + beta[ 1 ] = d * data[ 8 + column ] - g * data[ 24 + column ] - b * data[ 40 + column ] - e * data[ 56 + column ]; + beta[ 2 ] = e * data[ 8 + column ] - b * data[ 24 + column ] + g * data[ 40 + column ] + d * data[ 56 + column ]; + beta[ 3 ] = g * data[ 8 + column ] - e * data[ 24 + column ] + d * data[ 40 + column ] - b * data[ 56 + column ]; + + theta[ 0 ] = a * ( data[ column ] + data[ 32 + column ] ); + theta[ 3 ] = a * ( data[ column ] - data[ 32 + column ] ); + + theta[ 1 ] = alpha[ 0 ] + alpha[ 3 ]; + theta[ 2 ] = alpha[ 1 ] - alpha[ 2 ]; + + gamma[ 0 ] = theta[ 0 ] + theta[ 1 ]; + gamma[ 1 ] = theta[ 3 ] + theta[ 2 ]; + gamma[ 2 ] = theta[ 3 ] - theta[ 2 ]; + gamma[ 3 ] = theta[ 0 ] - theta[ 1 ]; + + data[ 0 + column ] = gamma[ 0 ] + beta[ 0 ]; + data[ 8 + column ] = gamma[ 1 ] + beta[ 1 ]; + data[ 16 + column ] = gamma[ 2 ] + beta[ 2 ]; + data[ 24 + column ] = gamma[ 3 ] + beta[ 3 ]; + + data[ 32 + column ] = gamma[ 3 ] - beta[ 3 ]; + data[ 40 + column ] = gamma[ 2 ] - beta[ 2 ]; + data[ 48 + column ] = gamma[ 1 ] - beta[ 1 ]; + data[ 56 + column ] = gamma[ 0 ] - beta[ 0 ]; + + } + + } + + function csc709Inverse( data ) { + + for ( let i = 0; i < 64; ++ i ) { + + const y = data[ 0 ][ i ]; + const cb = data[ 1 ][ i ]; + const cr = data[ 2 ][ i ]; + + data[ 0 ][ i ] = y + 1.5747 * cr; + data[ 1 ][ i ] = y - 0.1873 * cb - 0.4682 * cr; + data[ 2 ][ i ] = y + 1.8556 * cb; + + } + + } + + function convertToHalf( src, dst, idx ) { + + for ( let i = 0; i < 64; ++ i ) { + + dst[ idx + i ] = DataUtils.toHalfFloat( toLinear( src[ i ] ) ); + + } + + } + + function toLinear( float ) { + + if ( float <= 1 ) { + + return Math.sign( float ) * Math.pow( Math.abs( float ), 2.2 ); + + } else { + + return Math.sign( float ) * Math.pow( logBase, Math.abs( float ) - 1.0 ); + + } + + } + + function uncompressRAW( info ) { + + return new DataView( info.array.buffer, info.offset.value, info.size ); + + } + + function uncompressRLE( info ) { + + const compressed = info.viewer.buffer.slice( info.offset.value, info.offset.value + info.size ); + + const rawBuffer = new Uint8Array( decodeRunLength( compressed ) ); + const tmpBuffer = new Uint8Array( rawBuffer.length ); + + predictor( rawBuffer ); // revert predictor + + interleaveScalar( rawBuffer, tmpBuffer ); // interleave pixels + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressZIP( info ) { + + const compressed = info.array.slice( info.offset.value, info.offset.value + info.size ); + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.EXRLoader: External library fflate.min.js required.' ); + + } + + const rawBuffer = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + const tmpBuffer = new Uint8Array( rawBuffer.length ); + + predictor( rawBuffer ); // revert predictor + + interleaveScalar( rawBuffer, tmpBuffer ); // interleave pixels + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressPIZ( info ) { + + const inDataView = info.viewer; + const inOffset = { value: info.offset.value }; + + const outBuffer = new Uint16Array( info.width * info.scanlineBlockSize * ( info.channels * info.type ) ); + const bitmap = new Uint8Array( BITMAP_SIZE ); + + // Setup channel info + let outBufferEnd = 0; + const pizChannelData = new Array( info.channels ); + for ( let i = 0; i < info.channels; i ++ ) { + + pizChannelData[ i ] = {}; + pizChannelData[ i ][ 'start' ] = outBufferEnd; + pizChannelData[ i ][ 'end' ] = pizChannelData[ i ][ 'start' ]; + pizChannelData[ i ][ 'nx' ] = info.width; + pizChannelData[ i ][ 'ny' ] = info.lines; + pizChannelData[ i ][ 'size' ] = info.type; + + outBufferEnd += pizChannelData[ i ].nx * pizChannelData[ i ].ny * pizChannelData[ i ].size; + + } + + // Read range compression data + + const minNonZero = parseUint16( inDataView, inOffset ); + const maxNonZero = parseUint16( inDataView, inOffset ); + + if ( maxNonZero >= BITMAP_SIZE ) { + + throw new Error( 'Something is wrong with PIZ_COMPRESSION BITMAP_SIZE' ); + + } + + if ( minNonZero <= maxNonZero ) { + + for ( let i = 0; i < maxNonZero - minNonZero + 1; i ++ ) { + + bitmap[ i + minNonZero ] = parseUint8( inDataView, inOffset ); + + } + + } + + // Reverse LUT + const lut = new Uint16Array( USHORT_RANGE ); + const maxValue = reverseLutFromBitmap( bitmap, lut ); + + const length = parseUint32( inDataView, inOffset ); + + // Huffman decoding + hufUncompress( info.array, inDataView, inOffset, length, outBuffer, outBufferEnd ); + + // Wavelet decoding + for ( let i = 0; i < info.channels; ++ i ) { + + const cd = pizChannelData[ i ]; + + for ( let j = 0; j < pizChannelData[ i ].size; ++ j ) { + + wav2Decode( + outBuffer, + cd.start + j, + cd.nx, + cd.size, + cd.ny, + cd.nx * cd.size, + maxValue + ); + + } + + } + + // Expand the pixel data to their original range + applyLut( lut, outBuffer, outBufferEnd ); + + // Rearrange the pixel data into the format expected by the caller. + let tmpOffset = 0; + const tmpBuffer = new Uint8Array( outBuffer.buffer.byteLength ); + for ( let y = 0; y < info.lines; y ++ ) { + + for ( let c = 0; c < info.channels; c ++ ) { + + const cd = pizChannelData[ c ]; + + const n = cd.nx * cd.size; + const cp = new Uint8Array( outBuffer.buffer, cd.end * INT16_SIZE, n * INT16_SIZE ); + + tmpBuffer.set( cp, tmpOffset ); + tmpOffset += n * INT16_SIZE; + cd.end += n; + + } + + } + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressPXR( info ) { + + const compressed = info.array.slice( info.offset.value, info.offset.value + info.size ); + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.EXRLoader: External library fflate.min.js required.' ); + + } + + const rawBuffer = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + + const sz = info.lines * info.channels * info.width; + const tmpBuffer = ( info.type == 1 ) ? new Uint16Array( sz ) : new Uint32Array( sz ); + + let tmpBufferEnd = 0; + let writePtr = 0; + const ptr = new Array( 4 ); + + for ( let y = 0; y < info.lines; y ++ ) { + + for ( let c = 0; c < info.channels; c ++ ) { + + let pixel = 0; + + switch ( info.type ) { + + case 1: + + ptr[ 0 ] = tmpBufferEnd; + ptr[ 1 ] = ptr[ 0 ] + info.width; + tmpBufferEnd = ptr[ 1 ] + info.width; + + for ( let j = 0; j < info.width; ++ j ) { + + const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 8 ) | rawBuffer[ ptr[ 1 ] ++ ]; + + pixel += diff; + + tmpBuffer[ writePtr ] = pixel; + writePtr ++; + + } + + break; + + case 2: + + ptr[ 0 ] = tmpBufferEnd; + ptr[ 1 ] = ptr[ 0 ] + info.width; + ptr[ 2 ] = ptr[ 1 ] + info.width; + tmpBufferEnd = ptr[ 2 ] + info.width; + + for ( let j = 0; j < info.width; ++ j ) { + + const diff = ( rawBuffer[ ptr[ 0 ] ++ ] << 24 ) | ( rawBuffer[ ptr[ 1 ] ++ ] << 16 ) | ( rawBuffer[ ptr[ 2 ] ++ ] << 8 ); + + pixel += diff; + + tmpBuffer[ writePtr ] = pixel; + writePtr ++; + + } + + break; + + } + + } + + } + + return new DataView( tmpBuffer.buffer ); + + } + + function uncompressDWA( info ) { + + const inDataView = info.viewer; + const inOffset = { value: info.offset.value }; + const outBuffer = new Uint8Array( info.width * info.lines * ( info.channels * info.type * INT16_SIZE ) ); + + // Read compression header information + const dwaHeader = { + + version: parseInt64( inDataView, inOffset ), + unknownUncompressedSize: parseInt64( inDataView, inOffset ), + unknownCompressedSize: parseInt64( inDataView, inOffset ), + acCompressedSize: parseInt64( inDataView, inOffset ), + dcCompressedSize: parseInt64( inDataView, inOffset ), + rleCompressedSize: parseInt64( inDataView, inOffset ), + rleUncompressedSize: parseInt64( inDataView, inOffset ), + rleRawSize: parseInt64( inDataView, inOffset ), + totalAcUncompressedCount: parseInt64( inDataView, inOffset ), + totalDcUncompressedCount: parseInt64( inDataView, inOffset ), + acCompression: parseInt64( inDataView, inOffset ) + + }; + + if ( dwaHeader.version < 2 ) + throw new Error( 'EXRLoader.parse: ' + EXRHeader.compression + ' version ' + dwaHeader.version + ' is unsupported' ); + + // Read channel ruleset information + const channelRules = new Array(); + let ruleSize = parseUint16( inDataView, inOffset ) - INT16_SIZE; + + while ( ruleSize > 0 ) { + + const name = parseNullTerminatedString( inDataView.buffer, inOffset ); + const value = parseUint8( inDataView, inOffset ); + const compression = ( value >> 2 ) & 3; + const csc = ( value >> 4 ) - 1; + const index = new Int8Array( [ csc ] )[ 0 ]; + const type = parseUint8( inDataView, inOffset ); + + channelRules.push( { + name: name, + index: index, + type: type, + compression: compression, + } ); + + ruleSize -= name.length + 3; + + } + + // Classify channels + const channels = EXRHeader.channels; + const channelData = new Array( info.channels ); + + for ( let i = 0; i < info.channels; ++ i ) { + + const cd = channelData[ i ] = {}; + const channel = channels[ i ]; + + cd.name = channel.name; + cd.compression = UNKNOWN; + cd.decoded = false; + cd.type = channel.pixelType; + cd.pLinear = channel.pLinear; + cd.width = info.width; + cd.height = info.lines; + + } + + const cscSet = { + idx: new Array( 3 ) + }; + + for ( let offset = 0; offset < info.channels; ++ offset ) { + + const cd = channelData[ offset ]; + + for ( let i = 0; i < channelRules.length; ++ i ) { + + const rule = channelRules[ i ]; + + if ( cd.name == rule.name ) { + + cd.compression = rule.compression; + + if ( rule.index >= 0 ) { + + cscSet.idx[ rule.index ] = offset; + + } + + cd.offset = offset; + + } + + } + + } + + let acBuffer, dcBuffer, rleBuffer; + + // Read DCT - AC component data + if ( dwaHeader.acCompressedSize > 0 ) { + + switch ( dwaHeader.acCompression ) { + + case STATIC_HUFFMAN: + + acBuffer = new Uint16Array( dwaHeader.totalAcUncompressedCount ); + hufUncompress( info.array, inDataView, inOffset, dwaHeader.acCompressedSize, acBuffer, dwaHeader.totalAcUncompressedCount ); + break; + + case DEFLATE: + + const compressed = info.array.slice( inOffset.value, inOffset.value + dwaHeader.totalAcUncompressedCount ); + const data = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + acBuffer = new Uint16Array( data.buffer ); + inOffset.value += dwaHeader.totalAcUncompressedCount; + break; + + } + + + } + + // Read DCT - DC component data + if ( dwaHeader.dcCompressedSize > 0 ) { + + const zlibInfo = { + array: info.array, + offset: inOffset, + size: dwaHeader.dcCompressedSize + }; + dcBuffer = new Uint16Array( uncompressZIP( zlibInfo ).buffer ); + inOffset.value += dwaHeader.dcCompressedSize; + + } + + // Read RLE compressed data + if ( dwaHeader.rleRawSize > 0 ) { + + const compressed = info.array.slice( inOffset.value, inOffset.value + dwaHeader.rleCompressedSize ); + const data = fflate.unzlibSync( compressed ); // eslint-disable-line no-undef + rleBuffer = decodeRunLength( data.buffer ); + + inOffset.value += dwaHeader.rleCompressedSize; + + } + + // Prepare outbuffer data offset + let outBufferEnd = 0; + const rowOffsets = new Array( channelData.length ); + for ( let i = 0; i < rowOffsets.length; ++ i ) { + + rowOffsets[ i ] = new Array(); + + } + + for ( let y = 0; y < info.lines; ++ y ) { + + for ( let chan = 0; chan < channelData.length; ++ chan ) { + + rowOffsets[ chan ].push( outBufferEnd ); + outBufferEnd += channelData[ chan ].width * info.type * INT16_SIZE; + + } + + } + + // Lossy DCT decode RGB channels + lossyDctDecode( cscSet, rowOffsets, channelData, acBuffer, dcBuffer, outBuffer ); + + // Decode other channels + for ( let i = 0; i < channelData.length; ++ i ) { + + const cd = channelData[ i ]; + + if ( cd.decoded ) continue; + + switch ( cd.compression ) { + + case RLE: + + let row = 0; + let rleOffset = 0; + + for ( let y = 0; y < info.lines; ++ y ) { + + let rowOffsetBytes = rowOffsets[ i ][ row ]; + + for ( let x = 0; x < cd.width; ++ x ) { + + for ( let byte = 0; byte < INT16_SIZE * cd.type; ++ byte ) { + + outBuffer[ rowOffsetBytes ++ ] = rleBuffer[ rleOffset + byte * cd.width * cd.height ]; + + } + + rleOffset ++; + + } + + row ++; + + } + + break; + + case LOSSY_DCT: // skip + + default: + throw new Error( 'EXRLoader.parse: unsupported channel compression' ); + + } + + } + + return new DataView( outBuffer.buffer ); + + } + + function parseNullTerminatedString( buffer, offset ) { + + const uintBuffer = new Uint8Array( buffer ); + let endOffset = 0; + + while ( uintBuffer[ offset.value + endOffset ] != 0 ) { + + endOffset += 1; + + } + + const stringValue = new TextDecoder().decode( + uintBuffer.slice( offset.value, offset.value + endOffset ) + ); + + offset.value = offset.value + endOffset + 1; + + return stringValue; + + } + + function parseFixedLengthString( buffer, offset, size ) { + + const stringValue = new TextDecoder().decode( + new Uint8Array( buffer ).slice( offset.value, offset.value + size ) + ); + + offset.value = offset.value + size; + + return stringValue; + + } + + function parseRational( dataView, offset ) { + + const x = parseInt32( dataView, offset ); + const y = parseUint32( dataView, offset ); + + return [ x, y ]; + + } + + function parseTimecode( dataView, offset ) { + + const x = parseUint32( dataView, offset ); + const y = parseUint32( dataView, offset ); + + return [ x, y ]; + + } + + function parseInt32( dataView, offset ) { + + const Int32 = dataView.getInt32( offset.value, true ); + + offset.value = offset.value + INT32_SIZE; + + return Int32; + + } + + function parseUint32( dataView, offset ) { + + const Uint32 = dataView.getUint32( offset.value, true ); + + offset.value = offset.value + INT32_SIZE; + + return Uint32; + + } + + function parseUint8Array( uInt8Array, offset ) { + + const Uint8 = uInt8Array[ offset.value ]; + + offset.value = offset.value + INT8_SIZE; + + return Uint8; + + } + + function parseUint8( dataView, offset ) { + + const Uint8 = dataView.getUint8( offset.value ); + + offset.value = offset.value + INT8_SIZE; + + return Uint8; + + } + + const parseInt64 = function ( dataView, offset ) { + + let int; + + if ( 'getBigInt64' in DataView.prototype ) { + + int = Number( dataView.getBigInt64( offset.value, true ) ); + + } else { + + int = dataView.getUint32( offset.value + 4, true ) + Number( dataView.getUint32( offset.value, true ) << 32 ); + + } + + offset.value += ULONG_SIZE; + + return int; + + }; + + function parseFloat32( dataView, offset ) { + + const float = dataView.getFloat32( offset.value, true ); + + offset.value += FLOAT32_SIZE; + + return float; + + } + + function decodeFloat32( dataView, offset ) { + + return DataUtils.toHalfFloat( parseFloat32( dataView, offset ) ); + + } + + // https://stackoverflow.com/questions/5678432/decompressing-half-precision-floats-in-javascript + function decodeFloat16( binary ) { + + const exponent = ( binary & 0x7C00 ) >> 10, + fraction = binary & 0x03FF; + + return ( binary >> 15 ? - 1 : 1 ) * ( + exponent ? + ( + exponent === 0x1F ? + fraction ? NaN : Infinity : + Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 ) + ) : + 6.103515625e-5 * ( fraction / 0x400 ) + ); + + } + + function parseUint16( dataView, offset ) { + + const Uint16 = dataView.getUint16( offset.value, true ); + + offset.value += INT16_SIZE; + + return Uint16; + + } + + function parseFloat16( buffer, offset ) { + + return decodeFloat16( parseUint16( buffer, offset ) ); + + } + + function parseChlist( dataView, buffer, offset, size ) { + + const startOffset = offset.value; + const channels = []; + + while ( offset.value < ( startOffset + size - 1 ) ) { + + const name = parseNullTerminatedString( buffer, offset ); + const pixelType = parseInt32( dataView, offset ); + const pLinear = parseUint8( dataView, offset ); + offset.value += 3; // reserved, three chars + const xSampling = parseInt32( dataView, offset ); + const ySampling = parseInt32( dataView, offset ); + + channels.push( { + name: name, + pixelType: pixelType, + pLinear: pLinear, + xSampling: xSampling, + ySampling: ySampling + } ); + + } + + offset.value += 1; + + return channels; + + } + + function parseChromaticities( dataView, offset ) { + + const redX = parseFloat32( dataView, offset ); + const redY = parseFloat32( dataView, offset ); + const greenX = parseFloat32( dataView, offset ); + const greenY = parseFloat32( dataView, offset ); + const blueX = parseFloat32( dataView, offset ); + const blueY = parseFloat32( dataView, offset ); + const whiteX = parseFloat32( dataView, offset ); + const whiteY = parseFloat32( dataView, offset ); + + return { redX: redX, redY: redY, greenX: greenX, greenY: greenY, blueX: blueX, blueY: blueY, whiteX: whiteX, whiteY: whiteY }; + + } + + function parseCompression( dataView, offset ) { + + const compressionCodes = [ + 'NO_COMPRESSION', + 'RLE_COMPRESSION', + 'ZIPS_COMPRESSION', + 'ZIP_COMPRESSION', + 'PIZ_COMPRESSION', + 'PXR24_COMPRESSION', + 'B44_COMPRESSION', + 'B44A_COMPRESSION', + 'DWAA_COMPRESSION', + 'DWAB_COMPRESSION' + ]; + + const compression = parseUint8( dataView, offset ); + + return compressionCodes[ compression ]; + + } + + function parseBox2i( dataView, offset ) { + + const xMin = parseUint32( dataView, offset ); + const yMin = parseUint32( dataView, offset ); + const xMax = parseUint32( dataView, offset ); + const yMax = parseUint32( dataView, offset ); + + return { xMin: xMin, yMin: yMin, xMax: xMax, yMax: yMax }; + + } + + function parseLineOrder( dataView, offset ) { + + const lineOrders = [ + 'INCREASING_Y' + ]; + + const lineOrder = parseUint8( dataView, offset ); + + return lineOrders[ lineOrder ]; + + } + + function parseV2f( dataView, offset ) { + + const x = parseFloat32( dataView, offset ); + const y = parseFloat32( dataView, offset ); + + return [ x, y ]; + + } + + function parseV3f( dataView, offset ) { + + const x = parseFloat32( dataView, offset ); + const y = parseFloat32( dataView, offset ); + const z = parseFloat32( dataView, offset ); + + return [ x, y, z ]; + + } + + function parseValue( dataView, buffer, offset, type, size ) { + + if ( type === 'string' || type === 'stringvector' || type === 'iccProfile' ) { + + return parseFixedLengthString( buffer, offset, size ); + + } else if ( type === 'chlist' ) { + + return parseChlist( dataView, buffer, offset, size ); + + } else if ( type === 'chromaticities' ) { + + return parseChromaticities( dataView, offset ); + + } else if ( type === 'compression' ) { + + return parseCompression( dataView, offset ); + + } else if ( type === 'box2i' ) { + + return parseBox2i( dataView, offset ); + + } else if ( type === 'lineOrder' ) { + + return parseLineOrder( dataView, offset ); + + } else if ( type === 'float' ) { + + return parseFloat32( dataView, offset ); + + } else if ( type === 'v2f' ) { + + return parseV2f( dataView, offset ); + + } else if ( type === 'v3f' ) { + + return parseV3f( dataView, offset ); + + } else if ( type === 'int' ) { + + return parseInt32( dataView, offset ); + + } else if ( type === 'rational' ) { + + return parseRational( dataView, offset ); + + } else if ( type === 'timecode' ) { + + return parseTimecode( dataView, offset ); + + } else if ( type === 'preview' ) { + + offset.value += size; + return 'skipped'; + + } else { + + offset.value += size; + return undefined; + + } + + } + + function parseHeader( dataView, buffer, offset ) { + + const EXRHeader = {}; + + if ( dataView.getUint32( 0, true ) != 20000630 ) { // magic + + throw new Error( 'THREE.EXRLoader: provided file doesn\'t appear to be in OpenEXR format.' ); + + } + + EXRHeader.version = dataView.getUint8( 4 ); + + const spec = dataView.getUint8( 5 ); // fullMask + + EXRHeader.spec = { + singleTile: !! ( spec & 2 ), + longName: !! ( spec & 4 ), + deepFormat: !! ( spec & 8 ), + multiPart: !! ( spec & 16 ), + }; + + // start of header + + offset.value = 8; // start at 8 - after pre-amble + + let keepReading = true; + + while ( keepReading ) { + + const attributeName = parseNullTerminatedString( buffer, offset ); + + if ( attributeName == 0 ) { + + keepReading = false; + + } else { + + const attributeType = parseNullTerminatedString( buffer, offset ); + const attributeSize = parseUint32( dataView, offset ); + const attributeValue = parseValue( dataView, buffer, offset, attributeType, attributeSize ); + + if ( attributeValue === undefined ) { + + console.warn( `EXRLoader.parse: skipped unknown header attribute type \'${attributeType}\'.` ); + + } else { + + EXRHeader[ attributeName ] = attributeValue; + + } + + } + + } + + if ( spec != 0 ) { + + console.error( 'EXRHeader:', EXRHeader ); + throw new Error( 'THREE.EXRLoader: provided file is currently unsupported.' ); + + } + + return EXRHeader; + + } + + function setupDecoder( EXRHeader, dataView, uInt8Array, offset, outputType ) { + + const EXRDecoder = { + size: 0, + viewer: dataView, + array: uInt8Array, + offset: offset, + width: EXRHeader.dataWindow.xMax - EXRHeader.dataWindow.xMin + 1, + height: EXRHeader.dataWindow.yMax - EXRHeader.dataWindow.yMin + 1, + channels: EXRHeader.channels.length, + bytesPerLine: null, + lines: null, + inputSize: null, + type: EXRHeader.channels[ 0 ].pixelType, + uncompress: null, + getter: null, + format: null, + encoding: null, + }; + + switch ( EXRHeader.compression ) { + + case 'NO_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressRAW; + break; + + case 'RLE_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressRLE; + break; + + case 'ZIPS_COMPRESSION': + EXRDecoder.lines = 1; + EXRDecoder.uncompress = uncompressZIP; + break; + + case 'ZIP_COMPRESSION': + EXRDecoder.lines = 16; + EXRDecoder.uncompress = uncompressZIP; + break; + + case 'PIZ_COMPRESSION': + EXRDecoder.lines = 32; + EXRDecoder.uncompress = uncompressPIZ; + break; + + case 'PXR24_COMPRESSION': + EXRDecoder.lines = 16; + EXRDecoder.uncompress = uncompressPXR; + break; + + case 'DWAA_COMPRESSION': + EXRDecoder.lines = 32; + EXRDecoder.uncompress = uncompressDWA; + break; + + case 'DWAB_COMPRESSION': + EXRDecoder.lines = 256; + EXRDecoder.uncompress = uncompressDWA; + break; + + default: + throw new Error( 'EXRLoader.parse: ' + EXRHeader.compression + ' is unsupported' ); + + } + + EXRDecoder.scanlineBlockSize = EXRDecoder.lines; + + if ( EXRDecoder.type == 1 ) { + + // half + switch ( outputType ) { + + case FloatType: + EXRDecoder.getter = parseFloat16; + EXRDecoder.inputSize = INT16_SIZE; + break; + + case HalfFloatType: + EXRDecoder.getter = parseUint16; + EXRDecoder.inputSize = INT16_SIZE; + break; + + } + + } else if ( EXRDecoder.type == 2 ) { + + // float + switch ( outputType ) { + + case FloatType: + EXRDecoder.getter = parseFloat32; + EXRDecoder.inputSize = FLOAT32_SIZE; + break; + + case HalfFloatType: + EXRDecoder.getter = decodeFloat32; + EXRDecoder.inputSize = FLOAT32_SIZE; + + } + + } else { + + throw new Error( 'EXRLoader.parse: unsupported pixelType ' + EXRDecoder.type + ' for ' + EXRHeader.compression + '.' ); + + } + + EXRDecoder.blockCount = ( EXRHeader.dataWindow.yMax + 1 ) / EXRDecoder.scanlineBlockSize; + + for ( let i = 0; i < EXRDecoder.blockCount; i ++ ) + parseInt64( dataView, offset ); // scanlineOffset + + // we should be passed the scanline offset table, ready to start reading pixel data. + + // RGB images will be converted to RGBA format, preventing software emulation in select devices. + EXRDecoder.outputChannels = ( ( EXRDecoder.channels == 3 ) ? 4 : EXRDecoder.channels ); + const size = EXRDecoder.width * EXRDecoder.height * EXRDecoder.outputChannels; + + switch ( outputType ) { + + case FloatType: + EXRDecoder.byteArray = new Float32Array( size ); + + // Fill initially with 1s for the alpha value if the texture is not RGBA, RGB values will be overwritten + if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + EXRDecoder.byteArray.fill( 1, 0, size ); + + break; + + case HalfFloatType: + EXRDecoder.byteArray = new Uint16Array( size ); + + if ( EXRDecoder.channels < EXRDecoder.outputChannels ) + EXRDecoder.byteArray.fill( 0x3C00, 0, size ); // Uint16Array holds half float data, 0x3C00 is 1 + + break; + + default: + console.error( 'THREE.EXRLoader: unsupported type: ', outputType ); + break; + + } + + EXRDecoder.bytesPerLine = EXRDecoder.width * EXRDecoder.inputSize * EXRDecoder.channels; + + if ( EXRDecoder.outputChannels == 4 ) { + + EXRDecoder.format = RGBAFormat; + EXRDecoder.encoding = LinearEncoding; + + } else { + + EXRDecoder.format = RedFormat; + EXRDecoder.encoding = LinearEncoding; + + } + + return EXRDecoder; + + } + + // start parsing file [START] + + const bufferDataView = new DataView( buffer ); + const uInt8Array = new Uint8Array( buffer ); + const offset = { value: 0 }; + + // get header information and validate format. + const EXRHeader = parseHeader( bufferDataView, buffer, offset ); + + // get input compression information and prepare decoding. + const EXRDecoder = setupDecoder( EXRHeader, bufferDataView, uInt8Array, offset, this.type ); + + const tmpOffset = { value: 0 }; + const channelOffsets = { R: 0, G: 1, B: 2, A: 3, Y: 0 }; + + for ( let scanlineBlockIdx = 0; scanlineBlockIdx < EXRDecoder.height / EXRDecoder.scanlineBlockSize; scanlineBlockIdx ++ ) { + + const line = parseUint32( bufferDataView, offset ); // line_no + EXRDecoder.size = parseUint32( bufferDataView, offset ); // data_len + EXRDecoder.lines = ( ( line + EXRDecoder.scanlineBlockSize > EXRDecoder.height ) ? ( EXRDecoder.height - line ) : EXRDecoder.scanlineBlockSize ); + + const isCompressed = EXRDecoder.size < EXRDecoder.lines * EXRDecoder.bytesPerLine; + const viewer = isCompressed ? EXRDecoder.uncompress( EXRDecoder ) : uncompressRAW( EXRDecoder ); + + offset.value += EXRDecoder.size; + + for ( let line_y = 0; line_y < EXRDecoder.scanlineBlockSize; line_y ++ ) { + + const true_y = line_y + scanlineBlockIdx * EXRDecoder.scanlineBlockSize; + if ( true_y >= EXRDecoder.height ) break; + + for ( let channelID = 0; channelID < EXRDecoder.channels; channelID ++ ) { + + const cOff = channelOffsets[ EXRHeader.channels[ channelID ].name ]; + + for ( let x = 0; x < EXRDecoder.width; x ++ ) { + + tmpOffset.value = ( line_y * ( EXRDecoder.channels * EXRDecoder.width ) + channelID * EXRDecoder.width + x ) * EXRDecoder.inputSize; + const outIndex = ( EXRDecoder.height - 1 - true_y ) * ( EXRDecoder.width * EXRDecoder.outputChannels ) + x * EXRDecoder.outputChannels + cOff; + EXRDecoder.byteArray[ outIndex ] = EXRDecoder.getter( viewer, tmpOffset ); + + } + + } + + } + + } + + return { + header: EXRHeader, + width: EXRDecoder.width, + height: EXRDecoder.height, + data: EXRDecoder.byteArray, + format: EXRDecoder.format, + encoding: EXRDecoder.encoding, + type: this.type, + }; + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + function onLoadCallback( texture, texData ) { + + texture.encoding = texData.encoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.flipY = false; + + if ( onLoad ) onLoad( texture, texData ); + + } + + return super.load( url, onLoadCallback, onProgress, onError ); + + } + +} + +export { EXRLoader }; diff --git a/jsm/loaders/FBXLoader.js b/jsm/loaders/FBXLoader.js new file mode 100644 index 0000000..d6a3d5e --- /dev/null +++ b/jsm/loaders/FBXLoader.js @@ -0,0 +1,4128 @@ +import { + AmbientLight, + AnimationClip, + Bone, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DirectionalLight, + EquirectangularReflectionMapping, + Euler, + FileLoader, + Float32BufferAttribute, + Group, + Line, + LineBasicMaterial, + Loader, + LoaderUtils, + MathUtils, + Matrix3, + Matrix4, + Mesh, + MeshLambertMaterial, + MeshPhongMaterial, + NumberKeyframeTrack, + Object3D, + OrthographicCamera, + PerspectiveCamera, + PointLight, + PropertyBinding, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Skeleton, + SkinnedMesh, + SpotLight, + Texture, + TextureLoader, + Uint16BufferAttribute, + Vector3, + Vector4, + VectorKeyframeTrack, + sRGBEncoding +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; +import { NURBSCurve } from '../curves/NURBSCurve.js'; + +/** + * Loader loads FBX file and generates Group representing FBX scene. + * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format + * Versions lower than this may load but will probably have errors + * + * Needs Support: + * Morph normals / blend shape normals + * + * FBX format references: + * https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_index_html (C++ SDK reference) + * + * Binary format specification: + * https://code.blender.org/2013/08/fbx-binary-file-format-specification/ + */ + + +let fbxTree; +let connections; +let sceneGraph; + +class FBXLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( scope.path === '' ) ? LoaderUtils.extractUrlBase( url ) : scope.path; + + const loader = new FileLoader( this.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + + loader.load( url, function ( buffer ) { + + try { + + onLoad( scope.parse( buffer, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( FBXBuffer, path ) { + + if ( isFbxFormatBinary( FBXBuffer ) ) { + + fbxTree = new BinaryParser().parse( FBXBuffer ); + + } else { + + const FBXText = convertArrayBufferToString( FBXBuffer ); + + if ( ! isFbxFormatASCII( FBXText ) ) { + + throw new Error( 'THREE.FBXLoader: Unknown format.' ); + + } + + if ( getFbxVersion( FBXText ) < 7000 ) { + + throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) ); + + } + + fbxTree = new TextParser().parse( FBXText ); + + } + + // console.log( fbxTree ); + + const textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + + return new FBXTreeParser( textureLoader, this.manager ).parse( fbxTree ); + + } + +} + +// Parse the FBXTree object returned by the BinaryParser or TextParser and return a Group +class FBXTreeParser { + + constructor( textureLoader, manager ) { + + this.textureLoader = textureLoader; + this.manager = manager; + + } + + parse() { + + connections = this.parseConnections(); + + const images = this.parseImages(); + const textures = this.parseTextures( images ); + const materials = this.parseMaterials( textures ); + const deformers = this.parseDeformers(); + const geometryMap = new GeometryParser().parse( deformers ); + + this.parseScene( deformers, geometryMap, materials ); + + return sceneGraph; + + } + + // Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry ) + // and details the connection type + parseConnections() { + + const connectionMap = new Map(); + + if ( 'Connections' in fbxTree ) { + + const rawConnections = fbxTree.Connections.connections; + + rawConnections.forEach( function ( rawConnection ) { + + const fromID = rawConnection[ 0 ]; + const toID = rawConnection[ 1 ]; + const relationship = rawConnection[ 2 ]; + + if ( ! connectionMap.has( fromID ) ) { + + connectionMap.set( fromID, { + parents: [], + children: [] + } ); + + } + + const parentRelationship = { ID: toID, relationship: relationship }; + connectionMap.get( fromID ).parents.push( parentRelationship ); + + if ( ! connectionMap.has( toID ) ) { + + connectionMap.set( toID, { + parents: [], + children: [] + } ); + + } + + const childRelationship = { ID: fromID, relationship: relationship }; + connectionMap.get( toID ).children.push( childRelationship ); + + } ); + + } + + return connectionMap; + + } + + // Parse FBXTree.Objects.Video for embedded image data + // These images are connected to textures in FBXTree.Objects.Textures + // via FBXTree.Connections. + parseImages() { + + const images = {}; + const blobs = {}; + + if ( 'Video' in fbxTree.Objects ) { + + const videoNodes = fbxTree.Objects.Video; + + for ( const nodeID in videoNodes ) { + + const videoNode = videoNodes[ nodeID ]; + + const id = parseInt( nodeID ); + + images[ id ] = videoNode.RelativeFilename || videoNode.Filename; + + // raw image data is in videoNode.Content + if ( 'Content' in videoNode ) { + + const arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 ); + const base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' ); + + if ( arrayBufferContent || base64Content ) { + + const image = this.parseImage( videoNodes[ nodeID ] ); + + blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image; + + } + + } + + } + + } + + for ( const id in images ) { + + const filename = images[ id ]; + + if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ]; + else images[ id ] = images[ id ].split( '\\' ).pop(); + + } + + return images; + + } + + // Parse embedded image data in FBXTree.Video.Content + parseImage( videoNode ) { + + const content = videoNode.Content; + const fileName = videoNode.RelativeFilename || videoNode.Filename; + const extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase(); + + let type; + + switch ( extension ) { + + case 'bmp': + + type = 'image/bmp'; + break; + + case 'jpg': + case 'jpeg': + + type = 'image/jpeg'; + break; + + case 'png': + + type = 'image/png'; + break; + + case 'tif': + + type = 'image/tiff'; + break; + + case 'tga': + + if ( this.manager.getHandler( '.tga' ) === null ) { + + console.warn( 'FBXLoader: TGA loader not found, skipping ', fileName ); + + } + + type = 'image/tga'; + break; + + default: + + console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' ); + return; + + } + + if ( typeof content === 'string' ) { // ASCII format + + return 'data:' + type + ';base64,' + content; + + } else { // Binary Format + + const array = new Uint8Array( content ); + return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) ); + + } + + } + + // Parse nodes in FBXTree.Objects.Texture + // These contain details such as UV scaling, cropping, rotation etc and are connected + // to images in FBXTree.Objects.Video + parseTextures( images ) { + + const textureMap = new Map(); + + if ( 'Texture' in fbxTree.Objects ) { + + const textureNodes = fbxTree.Objects.Texture; + for ( const nodeID in textureNodes ) { + + const texture = this.parseTexture( textureNodes[ nodeID ], images ); + textureMap.set( parseInt( nodeID ), texture ); + + } + + } + + return textureMap; + + } + + // Parse individual node in FBXTree.Objects.Texture + parseTexture( textureNode, images ) { + + const texture = this.loadTexture( textureNode, images ); + + texture.ID = textureNode.id; + + texture.name = textureNode.attrName; + + const wrapModeU = textureNode.WrapModeU; + const wrapModeV = textureNode.WrapModeV; + + const valueU = wrapModeU !== undefined ? wrapModeU.value : 0; + const valueV = wrapModeV !== undefined ? wrapModeV.value : 0; + + // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a + // 0: repeat(default), 1: clamp + + texture.wrapS = valueU === 0 ? RepeatWrapping : ClampToEdgeWrapping; + texture.wrapT = valueV === 0 ? RepeatWrapping : ClampToEdgeWrapping; + + if ( 'Scaling' in textureNode ) { + + const values = textureNode.Scaling.value; + + texture.repeat.x = values[ 0 ]; + texture.repeat.y = values[ 1 ]; + + } + + if ( 'Translation' in textureNode ) { + + const values = textureNode.Translation.value; + + texture.offset.x = values[ 0 ]; + texture.offset.y = values[ 1 ]; + + } + + return texture; + + } + + // load a texture specified as a blob or data URI, or via an external URL using TextureLoader + loadTexture( textureNode, images ) { + + let fileName; + + const currentPath = this.textureLoader.path; + + const children = connections.get( textureNode.id ).children; + + if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) { + + fileName = images[ children[ 0 ].ID ]; + + if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) { + + this.textureLoader.setPath( undefined ); + + } + + } + + let texture; + + const extension = textureNode.FileName.slice( - 3 ).toLowerCase(); + + if ( extension === 'tga' ) { + + const loader = this.manager.getHandler( '.tga' ); + + if ( loader === null ) { + + console.warn( 'FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename ); + texture = new Texture(); + + } else { + + loader.setPath( this.textureLoader.path ); + texture = loader.load( fileName ); + + } + + } else if ( extension === 'psd' ) { + + console.warn( 'FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename ); + texture = new Texture(); + + } else { + + texture = this.textureLoader.load( fileName ); + + } + + this.textureLoader.setPath( currentPath ); + + return texture; + + } + + // Parse nodes in FBXTree.Objects.Material + parseMaterials( textureMap ) { + + const materialMap = new Map(); + + if ( 'Material' in fbxTree.Objects ) { + + const materialNodes = fbxTree.Objects.Material; + + for ( const nodeID in materialNodes ) { + + const material = this.parseMaterial( materialNodes[ nodeID ], textureMap ); + + if ( material !== null ) materialMap.set( parseInt( nodeID ), material ); + + } + + } + + return materialMap; + + } + + // Parse single node in FBXTree.Objects.Material + // Materials are connected to texture maps in FBXTree.Objects.Textures + // FBX format currently only supports Lambert and Phong shading models + parseMaterial( materialNode, textureMap ) { + + const ID = materialNode.id; + const name = materialNode.attrName; + let type = materialNode.ShadingModel; + + // Case where FBX wraps shading model in property object. + if ( typeof type === 'object' ) { + + type = type.value; + + } + + // Ignore unused materials which don't have any connections. + if ( ! connections.has( ID ) ) return null; + + const parameters = this.parseParameters( materialNode, textureMap, ID ); + + let material; + + switch ( type.toLowerCase() ) { + + case 'phong': + material = new MeshPhongMaterial(); + break; + case 'lambert': + material = new MeshLambertMaterial(); + break; + default: + console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type ); + material = new MeshPhongMaterial(); + break; + + } + + material.setValues( parameters ); + material.name = name; + + return material; + + } + + // Parse FBX material and return parameters suitable for a three.js material + // Also parse the texture map and return any textures associated with the material + parseParameters( materialNode, textureMap, ID ) { + + const parameters = {}; + + if ( materialNode.BumpFactor ) { + + parameters.bumpScale = materialNode.BumpFactor.value; + + } + + if ( materialNode.Diffuse ) { + + parameters.color = new Color().fromArray( materialNode.Diffuse.value ); + + } else if ( materialNode.DiffuseColor && ( materialNode.DiffuseColor.type === 'Color' || materialNode.DiffuseColor.type === 'ColorRGB' ) ) { + + // The blender exporter exports diffuse here instead of in materialNode.Diffuse + parameters.color = new Color().fromArray( materialNode.DiffuseColor.value ); + + } + + if ( materialNode.DisplacementFactor ) { + + parameters.displacementScale = materialNode.DisplacementFactor.value; + + } + + if ( materialNode.Emissive ) { + + parameters.emissive = new Color().fromArray( materialNode.Emissive.value ); + + } else if ( materialNode.EmissiveColor && ( materialNode.EmissiveColor.type === 'Color' || materialNode.EmissiveColor.type === 'ColorRGB' ) ) { + + // The blender exporter exports emissive color here instead of in materialNode.Emissive + parameters.emissive = new Color().fromArray( materialNode.EmissiveColor.value ); + + } + + if ( materialNode.EmissiveFactor ) { + + parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value ); + + } + + if ( materialNode.Opacity ) { + + parameters.opacity = parseFloat( materialNode.Opacity.value ); + + } + + if ( parameters.opacity < 1.0 ) { + + parameters.transparent = true; + + } + + if ( materialNode.ReflectionFactor ) { + + parameters.reflectivity = materialNode.ReflectionFactor.value; + + } + + if ( materialNode.Shininess ) { + + parameters.shininess = materialNode.Shininess.value; + + } + + if ( materialNode.Specular ) { + + parameters.specular = new Color().fromArray( materialNode.Specular.value ); + + } else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) { + + // The blender exporter exports specular color here instead of in materialNode.Specular + parameters.specular = new Color().fromArray( materialNode.SpecularColor.value ); + + } + + const scope = this; + connections.get( ID ).children.forEach( function ( child ) { + + const type = child.relationship; + + switch ( type ) { + + case 'Bump': + parameters.bumpMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'Maya|TEX_ao_map': + parameters.aoMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'DiffuseColor': + case 'Maya|TEX_color_map': + parameters.map = scope.getTexture( textureMap, child.ID ); + if ( parameters.map !== undefined ) { + + parameters.map.encoding = sRGBEncoding; + + } + + break; + + case 'DisplacementColor': + parameters.displacementMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'EmissiveColor': + parameters.emissiveMap = scope.getTexture( textureMap, child.ID ); + if ( parameters.emissiveMap !== undefined ) { + + parameters.emissiveMap.encoding = sRGBEncoding; + + } + + break; + + case 'NormalMap': + case 'Maya|TEX_normal_map': + parameters.normalMap = scope.getTexture( textureMap, child.ID ); + break; + + case 'ReflectionColor': + parameters.envMap = scope.getTexture( textureMap, child.ID ); + if ( parameters.envMap !== undefined ) { + + parameters.envMap.mapping = EquirectangularReflectionMapping; + parameters.envMap.encoding = sRGBEncoding; + + } + + break; + + case 'SpecularColor': + parameters.specularMap = scope.getTexture( textureMap, child.ID ); + if ( parameters.specularMap !== undefined ) { + + parameters.specularMap.encoding = sRGBEncoding; + + } + + break; + + case 'TransparentColor': + case 'TransparencyFactor': + parameters.alphaMap = scope.getTexture( textureMap, child.ID ); + parameters.transparent = true; + break; + + case 'AmbientColor': + case 'ShininessExponent': // AKA glossiness map + case 'SpecularFactor': // AKA specularLevel + case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor + default: + console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type ); + break; + + } + + } ); + + return parameters; + + } + + // get a texture from the textureMap for use by a material. + getTexture( textureMap, id ) { + + // if the texture is a layered texture, just use the first layer and issue a warning + if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) { + + console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' ); + id = connections.get( id ).children[ 0 ].ID; + + } + + return textureMap.get( id ); + + } + + // Parse nodes in FBXTree.Objects.Deformer + // Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here + // Generates map of Skeleton-like objects for use later when generating and binding skeletons. + parseDeformers() { + + const skeletons = {}; + const morphTargets = {}; + + if ( 'Deformer' in fbxTree.Objects ) { + + const DeformerNodes = fbxTree.Objects.Deformer; + + for ( const nodeID in DeformerNodes ) { + + const deformerNode = DeformerNodes[ nodeID ]; + + const relationships = connections.get( parseInt( nodeID ) ); + + if ( deformerNode.attrType === 'Skin' ) { + + const skeleton = this.parseSkeleton( relationships, DeformerNodes ); + skeleton.ID = nodeID; + + if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' ); + skeleton.geometryID = relationships.parents[ 0 ].ID; + + skeletons[ nodeID ] = skeleton; + + } else if ( deformerNode.attrType === 'BlendShape' ) { + + const morphTarget = { + id: nodeID, + }; + + morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes ); + morphTarget.id = nodeID; + + if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' ); + + morphTargets[ nodeID ] = morphTarget; + + } + + } + + } + + return { + + skeletons: skeletons, + morphTargets: morphTargets, + + }; + + } + + // Parse single nodes in FBXTree.Objects.Deformer + // The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster' + // Each skin node represents a skeleton and each cluster node represents a bone + parseSkeleton( relationships, deformerNodes ) { + + const rawBones = []; + + relationships.children.forEach( function ( child ) { + + const boneNode = deformerNodes[ child.ID ]; + + if ( boneNode.attrType !== 'Cluster' ) return; + + const rawBone = { + + ID: child.ID, + indices: [], + weights: [], + transformLink: new Matrix4().fromArray( boneNode.TransformLink.a ), + // transform: new Matrix4().fromArray( boneNode.Transform.a ), + // linkMode: boneNode.Mode, + + }; + + if ( 'Indexes' in boneNode ) { + + rawBone.indices = boneNode.Indexes.a; + rawBone.weights = boneNode.Weights.a; + + } + + rawBones.push( rawBone ); + + } ); + + return { + + rawBones: rawBones, + bones: [] + + }; + + } + + // The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel" + parseMorphTargets( relationships, deformerNodes ) { + + const rawMorphTargets = []; + + for ( let i = 0; i < relationships.children.length; i ++ ) { + + const child = relationships.children[ i ]; + + const morphTargetNode = deformerNodes[ child.ID ]; + + const rawMorphTarget = { + + name: morphTargetNode.attrName, + initialWeight: morphTargetNode.DeformPercent, + id: morphTargetNode.id, + fullWeights: morphTargetNode.FullWeights.a + + }; + + if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return; + + rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) { + + return child.relationship === undefined; + + } )[ 0 ].ID; + + rawMorphTargets.push( rawMorphTarget ); + + } + + return rawMorphTargets; + + } + + // create the main Group() to be returned by the loader + parseScene( deformers, geometryMap, materialMap ) { + + sceneGraph = new Group(); + + const modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap ); + + const modelNodes = fbxTree.Objects.Model; + + const scope = this; + modelMap.forEach( function ( model ) { + + const modelNode = modelNodes[ model.ID ]; + scope.setLookAtProperties( model, modelNode ); + + const parentConnections = connections.get( model.ID ).parents; + + parentConnections.forEach( function ( connection ) { + + const parent = modelMap.get( connection.ID ); + if ( parent !== undefined ) parent.add( model ); + + } ); + + if ( model.parent === null ) { + + sceneGraph.add( model ); + + } + + + } ); + + this.bindSkeleton( deformers.skeletons, geometryMap, modelMap ); + + this.createAmbientLight(); + + sceneGraph.traverse( function ( node ) { + + if ( node.userData.transformData ) { + + if ( node.parent ) { + + node.userData.transformData.parentMatrix = node.parent.matrix; + node.userData.transformData.parentMatrixWorld = node.parent.matrixWorld; + + } + + const transform = generateTransform( node.userData.transformData ); + + node.applyMatrix4( transform ); + node.updateWorldMatrix(); + + } + + } ); + + const animations = new AnimationParser().parse(); + + // if all the models where already combined in a single group, just return that + if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) { + + sceneGraph.children[ 0 ].animations = animations; + sceneGraph = sceneGraph.children[ 0 ]; + + } + + sceneGraph.animations = animations; + + } + + // parse nodes in FBXTree.Objects.Model + parseModels( skeletons, geometryMap, materialMap ) { + + const modelMap = new Map(); + const modelNodes = fbxTree.Objects.Model; + + for ( const nodeID in modelNodes ) { + + const id = parseInt( nodeID ); + const node = modelNodes[ nodeID ]; + const relationships = connections.get( id ); + + let model = this.buildSkeleton( relationships, skeletons, id, node.attrName ); + + if ( ! model ) { + + switch ( node.attrType ) { + + case 'Camera': + model = this.createCamera( relationships ); + break; + case 'Light': + model = this.createLight( relationships ); + break; + case 'Mesh': + model = this.createMesh( relationships, geometryMap, materialMap ); + break; + case 'NurbsCurve': + model = this.createCurve( relationships, geometryMap ); + break; + case 'LimbNode': + case 'Root': + model = new Bone(); + break; + case 'Null': + default: + model = new Group(); + break; + + } + + model.name = node.attrName ? PropertyBinding.sanitizeNodeName( node.attrName ) : ''; + + model.ID = id; + + } + + this.getTransformData( model, node ); + modelMap.set( id, model ); + + } + + return modelMap; + + } + + buildSkeleton( relationships, skeletons, id, name ) { + + let bone = null; + + relationships.parents.forEach( function ( parent ) { + + for ( const ID in skeletons ) { + + const skeleton = skeletons[ ID ]; + + skeleton.rawBones.forEach( function ( rawBone, i ) { + + if ( rawBone.ID === parent.ID ) { + + const subBone = bone; + bone = new Bone(); + + bone.matrixWorld.copy( rawBone.transformLink ); + + // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id + + bone.name = name ? PropertyBinding.sanitizeNodeName( name ) : ''; + bone.ID = id; + + skeleton.bones[ i ] = bone; + + // In cases where a bone is shared between multiple meshes + // duplicate the bone here and and it as a child of the first bone + if ( subBone !== null ) { + + bone.add( subBone ); + + } + + } + + } ); + + } + + } ); + + return bone; + + } + + // create a PerspectiveCamera or OrthographicCamera + createCamera( relationships ) { + + let model; + let cameraAttribute; + + relationships.children.forEach( function ( child ) { + + const attr = fbxTree.Objects.NodeAttribute[ child.ID ]; + + if ( attr !== undefined ) { + + cameraAttribute = attr; + + } + + } ); + + if ( cameraAttribute === undefined ) { + + model = new Object3D(); + + } else { + + let type = 0; + if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) { + + type = 1; + + } + + let nearClippingPlane = 1; + if ( cameraAttribute.NearPlane !== undefined ) { + + nearClippingPlane = cameraAttribute.NearPlane.value / 1000; + + } + + let farClippingPlane = 1000; + if ( cameraAttribute.FarPlane !== undefined ) { + + farClippingPlane = cameraAttribute.FarPlane.value / 1000; + + } + + + let width = window.innerWidth; + let height = window.innerHeight; + + if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) { + + width = cameraAttribute.AspectWidth.value; + height = cameraAttribute.AspectHeight.value; + + } + + const aspect = width / height; + + let fov = 45; + if ( cameraAttribute.FieldOfView !== undefined ) { + + fov = cameraAttribute.FieldOfView.value; + + } + + const focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null; + + switch ( type ) { + + case 0: // Perspective + model = new PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane ); + if ( focalLength !== null ) model.setFocalLength( focalLength ); + break; + + case 1: // Orthographic + model = new OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane ); + break; + + default: + console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' ); + model = new Object3D(); + break; + + } + + } + + return model; + + } + + // Create a DirectionalLight, PointLight or SpotLight + createLight( relationships ) { + + let model; + let lightAttribute; + + relationships.children.forEach( function ( child ) { + + const attr = fbxTree.Objects.NodeAttribute[ child.ID ]; + + if ( attr !== undefined ) { + + lightAttribute = attr; + + } + + } ); + + if ( lightAttribute === undefined ) { + + model = new Object3D(); + + } else { + + let type; + + // LightType can be undefined for Point lights + if ( lightAttribute.LightType === undefined ) { + + type = 0; + + } else { + + type = lightAttribute.LightType.value; + + } + + let color = 0xffffff; + + if ( lightAttribute.Color !== undefined ) { + + color = new Color().fromArray( lightAttribute.Color.value ); + + } + + let intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100; + + // light disabled + if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) { + + intensity = 0; + + } + + let distance = 0; + if ( lightAttribute.FarAttenuationEnd !== undefined ) { + + if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) { + + distance = 0; + + } else { + + distance = lightAttribute.FarAttenuationEnd.value; + + } + + } + + // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd? + const decay = 1; + + switch ( type ) { + + case 0: // Point + model = new PointLight( color, intensity, distance, decay ); + break; + + case 1: // Directional + model = new DirectionalLight( color, intensity ); + break; + + case 2: // Spot + let angle = Math.PI / 3; + + if ( lightAttribute.InnerAngle !== undefined ) { + + angle = MathUtils.degToRad( lightAttribute.InnerAngle.value ); + + } + + let penumbra = 0; + if ( lightAttribute.OuterAngle !== undefined ) { + + // TODO: this is not correct - FBX calculates outer and inner angle in degrees + // with OuterAngle > InnerAngle && OuterAngle <= Math.PI + // while three.js uses a penumbra between (0, 1) to attenuate the inner angle + penumbra = MathUtils.degToRad( lightAttribute.OuterAngle.value ); + penumbra = Math.max( penumbra, 1 ); + + } + + model = new SpotLight( color, intensity, distance, angle, penumbra, decay ); + break; + + default: + console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a PointLight.' ); + model = new PointLight( color, intensity ); + break; + + } + + if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) { + + model.castShadow = true; + + } + + } + + return model; + + } + + createMesh( relationships, geometryMap, materialMap ) { + + let model; + let geometry = null; + let material = null; + const materials = []; + + // get geometry and materials(s) from connections + relationships.children.forEach( function ( child ) { + + if ( geometryMap.has( child.ID ) ) { + + geometry = geometryMap.get( child.ID ); + + } + + if ( materialMap.has( child.ID ) ) { + + materials.push( materialMap.get( child.ID ) ); + + } + + } ); + + if ( materials.length > 1 ) { + + material = materials; + + } else if ( materials.length > 0 ) { + + material = materials[ 0 ]; + + } else { + + material = new MeshPhongMaterial( { color: 0xcccccc } ); + materials.push( material ); + + } + + if ( 'color' in geometry.attributes ) { + + materials.forEach( function ( material ) { + + material.vertexColors = true; + + } ); + + } + + if ( geometry.FBX_Deformer ) { + + model = new SkinnedMesh( geometry, material ); + model.normalizeSkinWeights(); + + } else { + + model = new Mesh( geometry, material ); + + } + + return model; + + } + + createCurve( relationships, geometryMap ) { + + const geometry = relationships.children.reduce( function ( geo, child ) { + + if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID ); + + return geo; + + }, null ); + + // FBX does not list materials for Nurbs lines, so we'll just put our own in here. + const material = new LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } ); + return new Line( geometry, material ); + + } + + // parse the model node for transform data + getTransformData( model, modelNode ) { + + const transformData = {}; + + if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value ); + + if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); + else transformData.eulerOrder = 'ZYX'; + + if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value; + + if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value; + if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value; + if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value; + + if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value; + + if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value; + if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value; + + if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value; + if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value; + + model.userData.transformData = transformData; + + } + + setLookAtProperties( model, modelNode ) { + + if ( 'LookAtProperty' in modelNode ) { + + const children = connections.get( model.ID ).children; + + children.forEach( function ( child ) { + + if ( child.relationship === 'LookAtProperty' ) { + + const lookAtTarget = fbxTree.Objects.Model[ child.ID ]; + + if ( 'Lcl_Translation' in lookAtTarget ) { + + const pos = lookAtTarget.Lcl_Translation.value; + + // DirectionalLight, SpotLight + if ( model.target !== undefined ) { + + model.target.position.fromArray( pos ); + sceneGraph.add( model.target ); + + } else { // Cameras and other Object3Ds + + model.lookAt( new Vector3().fromArray( pos ) ); + + } + + } + + } + + } ); + + } + + } + + bindSkeleton( skeletons, geometryMap, modelMap ) { + + const bindMatrices = this.parsePoseNodes(); + + for ( const ID in skeletons ) { + + const skeleton = skeletons[ ID ]; + + const parents = connections.get( parseInt( skeleton.ID ) ).parents; + + parents.forEach( function ( parent ) { + + if ( geometryMap.has( parent.ID ) ) { + + const geoID = parent.ID; + const geoRelationships = connections.get( geoID ); + + geoRelationships.parents.forEach( function ( geoConnParent ) { + + if ( modelMap.has( geoConnParent.ID ) ) { + + const model = modelMap.get( geoConnParent.ID ); + + model.bind( new Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] ); + + } + + } ); + + } + + } ); + + } + + } + + parsePoseNodes() { + + const bindMatrices = {}; + + if ( 'Pose' in fbxTree.Objects ) { + + const BindPoseNode = fbxTree.Objects.Pose; + + for ( const nodeID in BindPoseNode ) { + + if ( BindPoseNode[ nodeID ].attrType === 'BindPose' && BindPoseNode[ nodeID ].NbPoseNodes > 0 ) { + + const poseNodes = BindPoseNode[ nodeID ].PoseNode; + + if ( Array.isArray( poseNodes ) ) { + + poseNodes.forEach( function ( poseNode ) { + + bindMatrices[ poseNode.Node ] = new Matrix4().fromArray( poseNode.Matrix.a ); + + } ); + + } else { + + bindMatrices[ poseNodes.Node ] = new Matrix4().fromArray( poseNodes.Matrix.a ); + + } + + } + + } + + } + + return bindMatrices; + + } + + // Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light + createAmbientLight() { + + if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) { + + const ambientColor = fbxTree.GlobalSettings.AmbientColor.value; + const r = ambientColor[ 0 ]; + const g = ambientColor[ 1 ]; + const b = ambientColor[ 2 ]; + + if ( r !== 0 || g !== 0 || b !== 0 ) { + + const color = new Color( r, g, b ); + sceneGraph.add( new AmbientLight( color, 1 ) ); + + } + + } + + } + +} + +// parse Geometry data from FBXTree and return map of BufferGeometries +class GeometryParser { + + // Parse nodes in FBXTree.Objects.Geometry + parse( deformers ) { + + const geometryMap = new Map(); + + if ( 'Geometry' in fbxTree.Objects ) { + + const geoNodes = fbxTree.Objects.Geometry; + + for ( const nodeID in geoNodes ) { + + const relationships = connections.get( parseInt( nodeID ) ); + const geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers ); + + geometryMap.set( parseInt( nodeID ), geo ); + + } + + } + + return geometryMap; + + } + + // Parse single node in FBXTree.Objects.Geometry + parseGeometry( relationships, geoNode, deformers ) { + + switch ( geoNode.attrType ) { + + case 'Mesh': + return this.parseMeshGeometry( relationships, geoNode, deformers ); + break; + + case 'NurbsCurve': + return this.parseNurbsGeometry( geoNode ); + break; + + } + + } + + // Parse single node mesh geometry in FBXTree.Objects.Geometry + parseMeshGeometry( relationships, geoNode, deformers ) { + + const skeletons = deformers.skeletons; + const morphTargets = []; + + const modelNodes = relationships.parents.map( function ( parent ) { + + return fbxTree.Objects.Model[ parent.ID ]; + + } ); + + // don't create geometry if it is not associated with any models + if ( modelNodes.length === 0 ) return; + + const skeleton = relationships.children.reduce( function ( skeleton, child ) { + + if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ]; + + return skeleton; + + }, null ); + + relationships.children.forEach( function ( child ) { + + if ( deformers.morphTargets[ child.ID ] !== undefined ) { + + morphTargets.push( deformers.morphTargets[ child.ID ] ); + + } + + } ); + + // Assume one model and get the preRotation from that + // if there is more than one model associated with the geometry this may cause problems + const modelNode = modelNodes[ 0 ]; + + const transformData = {}; + + if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); + if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value ); + + if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value; + if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value; + if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value; + + const transform = generateTransform( transformData ); + + return this.genGeometry( geoNode, skeleton, morphTargets, transform ); + + } + + // Generate a BufferGeometry from a node in FBXTree.Objects.Geometry + genGeometry( geoNode, skeleton, morphTargets, preTransform ) { + + const geo = new BufferGeometry(); + if ( geoNode.attrName ) geo.name = geoNode.attrName; + + const geoInfo = this.parseGeoNode( geoNode, skeleton ); + const buffers = this.genBuffers( geoInfo ); + + const positionAttribute = new Float32BufferAttribute( buffers.vertex, 3 ); + + positionAttribute.applyMatrix4( preTransform ); + + geo.setAttribute( 'position', positionAttribute ); + + if ( buffers.colors.length > 0 ) { + + geo.setAttribute( 'color', new Float32BufferAttribute( buffers.colors, 3 ) ); + + } + + if ( skeleton ) { + + geo.setAttribute( 'skinIndex', new Uint16BufferAttribute( buffers.weightsIndices, 4 ) ); + + geo.setAttribute( 'skinWeight', new Float32BufferAttribute( buffers.vertexWeights, 4 ) ); + + // used later to bind the skeleton to the model + geo.FBX_Deformer = skeleton; + + } + + if ( buffers.normal.length > 0 ) { + + const normalMatrix = new Matrix3().getNormalMatrix( preTransform ); + + const normalAttribute = new Float32BufferAttribute( buffers.normal, 3 ); + normalAttribute.applyNormalMatrix( normalMatrix ); + + geo.setAttribute( 'normal', normalAttribute ); + + } + + buffers.uvs.forEach( function ( uvBuffer, i ) { + + // subsequent uv buffers are called 'uv1', 'uv2', ... + let name = 'uv' + ( i + 1 ).toString(); + + // the first uv buffer is just called 'uv' + if ( i === 0 ) { + + name = 'uv'; + + } + + geo.setAttribute( name, new Float32BufferAttribute( buffers.uvs[ i ], 2 ) ); + + } ); + + if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { + + // Convert the material indices of each vertex into rendering groups on the geometry. + let prevMaterialIndex = buffers.materialIndex[ 0 ]; + let startIndex = 0; + + buffers.materialIndex.forEach( function ( currentIndex, i ) { + + if ( currentIndex !== prevMaterialIndex ) { + + geo.addGroup( startIndex, i - startIndex, prevMaterialIndex ); + + prevMaterialIndex = currentIndex; + startIndex = i; + + } + + } ); + + // the loop above doesn't add the last group, do that here. + if ( geo.groups.length > 0 ) { + + const lastGroup = geo.groups[ geo.groups.length - 1 ]; + const lastIndex = lastGroup.start + lastGroup.count; + + if ( lastIndex !== buffers.materialIndex.length ) { + + geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex ); + + } + + } + + // case where there are multiple materials but the whole geometry is only + // using one of them + if ( geo.groups.length === 0 ) { + + geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] ); + + } + + } + + this.addMorphTargets( geo, geoNode, morphTargets, preTransform ); + + return geo; + + } + + parseGeoNode( geoNode, skeleton ) { + + const geoInfo = {}; + + geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : []; + geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : []; + + if ( geoNode.LayerElementColor ) { + + geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] ); + + } + + if ( geoNode.LayerElementMaterial ) { + + geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] ); + + } + + if ( geoNode.LayerElementNormal ) { + + geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] ); + + } + + if ( geoNode.LayerElementUV ) { + + geoInfo.uv = []; + + let i = 0; + while ( geoNode.LayerElementUV[ i ] ) { + + if ( geoNode.LayerElementUV[ i ].UV ) { + + geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) ); + + } + + i ++; + + } + + } + + geoInfo.weightTable = {}; + + if ( skeleton !== null ) { + + geoInfo.skeleton = skeleton; + + skeleton.rawBones.forEach( function ( rawBone, i ) { + + // loop over the bone's vertex indices and weights + rawBone.indices.forEach( function ( index, j ) { + + if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = []; + + geoInfo.weightTable[ index ].push( { + + id: i, + weight: rawBone.weights[ j ], + + } ); + + } ); + + } ); + + } + + return geoInfo; + + } + + genBuffers( geoInfo ) { + + const buffers = { + vertex: [], + normal: [], + colors: [], + uvs: [], + materialIndex: [], + vertexWeights: [], + weightsIndices: [], + }; + + let polygonIndex = 0; + let faceLength = 0; + let displayedWeightsWarning = false; + + // these will hold data for a single face + let facePositionIndexes = []; + let faceNormals = []; + let faceColors = []; + let faceUVs = []; + let faceWeights = []; + let faceWeightIndices = []; + + const scope = this; + geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) { + + let materialIndex; + let endOfFace = false; + + // Face index and vertex index arrays are combined in a single array + // A cube with quad faces looks like this: + // PolygonVertexIndex: *24 { + // a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5 + // } + // Negative numbers mark the end of a face - first face here is 0, 1, 3, -3 + // to find index of last vertex bit shift the index: ^ - 1 + if ( vertexIndex < 0 ) { + + vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1 + endOfFace = true; + + } + + let weightIndices = []; + let weights = []; + + facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 ); + + if ( geoInfo.color ) { + + const data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color ); + + faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] ); + + } + + if ( geoInfo.skeleton ) { + + if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) { + + geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) { + + weights.push( wt.weight ); + weightIndices.push( wt.id ); + + } ); + + + } + + if ( weights.length > 4 ) { + + if ( ! displayedWeightsWarning ) { + + console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' ); + displayedWeightsWarning = true; + + } + + const wIndex = [ 0, 0, 0, 0 ]; + const Weight = [ 0, 0, 0, 0 ]; + + weights.forEach( function ( weight, weightIndex ) { + + let currentWeight = weight; + let currentIndex = weightIndices[ weightIndex ]; + + Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) { + + if ( currentWeight > comparedWeight ) { + + comparedWeightArray[ comparedWeightIndex ] = currentWeight; + currentWeight = comparedWeight; + + const tmp = wIndex[ comparedWeightIndex ]; + wIndex[ comparedWeightIndex ] = currentIndex; + currentIndex = tmp; + + } + + } ); + + } ); + + weightIndices = wIndex; + weights = Weight; + + } + + // if the weight array is shorter than 4 pad with 0s + while ( weights.length < 4 ) { + + weights.push( 0 ); + weightIndices.push( 0 ); + + } + + for ( let i = 0; i < 4; ++ i ) { + + faceWeights.push( weights[ i ] ); + faceWeightIndices.push( weightIndices[ i ] ); + + } + + } + + if ( geoInfo.normal ) { + + const data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal ); + + faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] ); + + } + + if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { + + materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ]; + + } + + if ( geoInfo.uv ) { + + geoInfo.uv.forEach( function ( uv, i ) { + + const data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv ); + + if ( faceUVs[ i ] === undefined ) { + + faceUVs[ i ] = []; + + } + + faceUVs[ i ].push( data[ 0 ] ); + faceUVs[ i ].push( data[ 1 ] ); + + } ); + + } + + faceLength ++; + + if ( endOfFace ) { + + scope.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ); + + polygonIndex ++; + faceLength = 0; + + // reset arrays for the next face + facePositionIndexes = []; + faceNormals = []; + faceColors = []; + faceUVs = []; + faceWeights = []; + faceWeightIndices = []; + + } + + } ); + + return buffers; + + } + + // Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris + genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) { + + for ( let i = 2; i < faceLength; i ++ ) { + + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] ); + + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] ); + + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] ); + buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] ); + + if ( geoInfo.skeleton ) { + + buffers.vertexWeights.push( faceWeights[ 0 ] ); + buffers.vertexWeights.push( faceWeights[ 1 ] ); + buffers.vertexWeights.push( faceWeights[ 2 ] ); + buffers.vertexWeights.push( faceWeights[ 3 ] ); + + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] ); + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] ); + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] ); + buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] ); + + buffers.vertexWeights.push( faceWeights[ i * 4 ] ); + buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] ); + buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] ); + buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] ); + + buffers.weightsIndices.push( faceWeightIndices[ 0 ] ); + buffers.weightsIndices.push( faceWeightIndices[ 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ 3 ] ); + + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] ); + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] ); + + buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] ); + buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] ); + + } + + if ( geoInfo.color ) { + + buffers.colors.push( faceColors[ 0 ] ); + buffers.colors.push( faceColors[ 1 ] ); + buffers.colors.push( faceColors[ 2 ] ); + + buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] ); + buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] ); + buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] ); + + buffers.colors.push( faceColors[ i * 3 ] ); + buffers.colors.push( faceColors[ i * 3 + 1 ] ); + buffers.colors.push( faceColors[ i * 3 + 2 ] ); + + } + + if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) { + + buffers.materialIndex.push( materialIndex ); + buffers.materialIndex.push( materialIndex ); + buffers.materialIndex.push( materialIndex ); + + } + + if ( geoInfo.normal ) { + + buffers.normal.push( faceNormals[ 0 ] ); + buffers.normal.push( faceNormals[ 1 ] ); + buffers.normal.push( faceNormals[ 2 ] ); + + buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] ); + buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] ); + buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] ); + + buffers.normal.push( faceNormals[ i * 3 ] ); + buffers.normal.push( faceNormals[ i * 3 + 1 ] ); + buffers.normal.push( faceNormals[ i * 3 + 2 ] ); + + } + + if ( geoInfo.uv ) { + + geoInfo.uv.forEach( function ( uv, j ) { + + if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = []; + + buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] ); + + buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] ); + + buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] ); + buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] ); + + } ); + + } + + } + + } + + addMorphTargets( parentGeo, parentGeoNode, morphTargets, preTransform ) { + + if ( morphTargets.length === 0 ) return; + + parentGeo.morphTargetsRelative = true; + + parentGeo.morphAttributes.position = []; + // parentGeo.morphAttributes.normal = []; // not implemented + + const scope = this; + morphTargets.forEach( function ( morphTarget ) { + + morphTarget.rawTargets.forEach( function ( rawTarget ) { + + const morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ]; + + if ( morphGeoNode !== undefined ) { + + scope.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name ); + + } + + } ); + + } ); + + } + + // a morph geometry node is similar to a standard node, and the node is also contained + // in FBXTree.Objects.Geometry, however it can only have attributes for position, normal + // and a special attribute Index defining which vertices of the original geometry are affected + // Normal and position attributes only have data for the vertices that are affected by the morph + genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) { + + const vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : []; + + const morphPositionsSparse = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : []; + const indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : []; + + const length = parentGeo.attributes.position.count * 3; + const morphPositions = new Float32Array( length ); + + for ( let i = 0; i < indices.length; i ++ ) { + + const morphIndex = indices[ i ] * 3; + + morphPositions[ morphIndex ] = morphPositionsSparse[ i * 3 ]; + morphPositions[ morphIndex + 1 ] = morphPositionsSparse[ i * 3 + 1 ]; + morphPositions[ morphIndex + 2 ] = morphPositionsSparse[ i * 3 + 2 ]; + + } + + // TODO: add morph normal support + const morphGeoInfo = { + vertexIndices: vertexIndices, + vertexPositions: morphPositions, + + }; + + const morphBuffers = this.genBuffers( morphGeoInfo ); + + const positionAttribute = new Float32BufferAttribute( morphBuffers.vertex, 3 ); + positionAttribute.name = name || morphGeoNode.attrName; + + positionAttribute.applyMatrix4( preTransform ); + + parentGeo.morphAttributes.position.push( positionAttribute ); + + } + + // Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists + parseNormals( NormalNode ) { + + const mappingType = NormalNode.MappingInformationType; + const referenceType = NormalNode.ReferenceInformationType; + const buffer = NormalNode.Normals.a; + let indexBuffer = []; + if ( referenceType === 'IndexToDirect' ) { + + if ( 'NormalIndex' in NormalNode ) { + + indexBuffer = NormalNode.NormalIndex.a; + + } else if ( 'NormalsIndex' in NormalNode ) { + + indexBuffer = NormalNode.NormalsIndex.a; + + } + + } + + return { + dataSize: 3, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists + parseUVs( UVNode ) { + + const mappingType = UVNode.MappingInformationType; + const referenceType = UVNode.ReferenceInformationType; + const buffer = UVNode.UV.a; + let indexBuffer = []; + if ( referenceType === 'IndexToDirect' ) { + + indexBuffer = UVNode.UVIndex.a; + + } + + return { + dataSize: 2, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists + parseVertexColors( ColorNode ) { + + const mappingType = ColorNode.MappingInformationType; + const referenceType = ColorNode.ReferenceInformationType; + const buffer = ColorNode.Colors.a; + let indexBuffer = []; + if ( referenceType === 'IndexToDirect' ) { + + indexBuffer = ColorNode.ColorIndex.a; + + } + + return { + dataSize: 4, + buffer: buffer, + indices: indexBuffer, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists + parseMaterialIndices( MaterialNode ) { + + const mappingType = MaterialNode.MappingInformationType; + const referenceType = MaterialNode.ReferenceInformationType; + + if ( mappingType === 'NoMappingInformation' ) { + + return { + dataSize: 1, + buffer: [ 0 ], + indices: [ 0 ], + mappingType: 'AllSame', + referenceType: referenceType + }; + + } + + const materialIndexBuffer = MaterialNode.Materials.a; + + // Since materials are stored as indices, there's a bit of a mismatch between FBX and what + // we expect.So we create an intermediate buffer that points to the index in the buffer, + // for conforming with the other functions we've written for other data. + const materialIndices = []; + + for ( let i = 0; i < materialIndexBuffer.length; ++ i ) { + + materialIndices.push( i ); + + } + + return { + dataSize: 1, + buffer: materialIndexBuffer, + indices: materialIndices, + mappingType: mappingType, + referenceType: referenceType + }; + + } + + // Generate a NurbGeometry from a node in FBXTree.Objects.Geometry + parseNurbsGeometry( geoNode ) { + + if ( NURBSCurve === undefined ) { + + console.error( 'THREE.FBXLoader: The loader relies on NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' ); + return new BufferGeometry(); + + } + + const order = parseInt( geoNode.Order ); + + if ( isNaN( order ) ) { + + console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id ); + return new BufferGeometry(); + + } + + const degree = order - 1; + + const knots = geoNode.KnotVector.a; + const controlPoints = []; + const pointsValues = geoNode.Points.a; + + for ( let i = 0, l = pointsValues.length; i < l; i += 4 ) { + + controlPoints.push( new Vector4().fromArray( pointsValues, i ) ); + + } + + let startKnot, endKnot; + + if ( geoNode.Form === 'Closed' ) { + + controlPoints.push( controlPoints[ 0 ] ); + + } else if ( geoNode.Form === 'Periodic' ) { + + startKnot = degree; + endKnot = knots.length - 1 - startKnot; + + for ( let i = 0; i < degree; ++ i ) { + + controlPoints.push( controlPoints[ i ] ); + + } + + } + + const curve = new NURBSCurve( degree, knots, controlPoints, startKnot, endKnot ); + const points = curve.getPoints( controlPoints.length * 12 ); + + return new BufferGeometry().setFromPoints( points ); + + } + +} + +// parse animation data from FBXTree +class AnimationParser { + + // take raw animation clips and turn them into three.js animation clips + parse() { + + const animationClips = []; + + const rawClips = this.parseClips(); + + if ( rawClips !== undefined ) { + + for ( const key in rawClips ) { + + const rawClip = rawClips[ key ]; + + const clip = this.addClip( rawClip ); + + animationClips.push( clip ); + + } + + } + + return animationClips; + + } + + parseClips() { + + // since the actual transformation data is stored in FBXTree.Objects.AnimationCurve, + // if this is undefined we can safely assume there are no animations + if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined; + + const curveNodesMap = this.parseAnimationCurveNodes(); + + this.parseAnimationCurves( curveNodesMap ); + + const layersMap = this.parseAnimationLayers( curveNodesMap ); + const rawClips = this.parseAnimStacks( layersMap ); + + return rawClips; + + } + + // parse nodes in FBXTree.Objects.AnimationCurveNode + // each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation ) + // and is referenced by an AnimationLayer + parseAnimationCurveNodes() { + + const rawCurveNodes = fbxTree.Objects.AnimationCurveNode; + + const curveNodesMap = new Map(); + + for ( const nodeID in rawCurveNodes ) { + + const rawCurveNode = rawCurveNodes[ nodeID ]; + + if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) { + + const curveNode = { + + id: rawCurveNode.id, + attr: rawCurveNode.attrName, + curves: {}, + + }; + + curveNodesMap.set( curveNode.id, curveNode ); + + } + + } + + return curveNodesMap; + + } + + // parse nodes in FBXTree.Objects.AnimationCurve and connect them up to + // previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated + // axis ( e.g. times and values of x rotation) + parseAnimationCurves( curveNodesMap ) { + + const rawCurves = fbxTree.Objects.AnimationCurve; + + // TODO: Many values are identical up to roundoff error, but won't be optimised + // e.g. position times: [0, 0.4, 0. 8] + // position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809] + // clearly, this should be optimised to + // times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809] + // this shows up in nearly every FBX file, and generally time array is length > 100 + + for ( const nodeID in rawCurves ) { + + const animationCurve = { + + id: rawCurves[ nodeID ].id, + times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ), + values: rawCurves[ nodeID ].KeyValueFloat.a, + + }; + + const relationships = connections.get( animationCurve.id ); + + if ( relationships !== undefined ) { + + const animationCurveID = relationships.parents[ 0 ].ID; + const animationCurveRelationship = relationships.parents[ 0 ].relationship; + + if ( animationCurveRelationship.match( /X/ ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve; + + } else if ( animationCurveRelationship.match( /Y/ ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve; + + } else if ( animationCurveRelationship.match( /Z/ ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve; + + } else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) { + + curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve; + + } + + } + + } + + } + + // parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references + // to various AnimationCurveNodes and is referenced by an AnimationStack node + // note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack + parseAnimationLayers( curveNodesMap ) { + + const rawLayers = fbxTree.Objects.AnimationLayer; + + const layersMap = new Map(); + + for ( const nodeID in rawLayers ) { + + const layerCurveNodes = []; + + const connection = connections.get( parseInt( nodeID ) ); + + if ( connection !== undefined ) { + + // all the animationCurveNodes used in the layer + const children = connection.children; + + children.forEach( function ( child, i ) { + + if ( curveNodesMap.has( child.ID ) ) { + + const curveNode = curveNodesMap.get( child.ID ); + + // check that the curves are defined for at least one axis, otherwise ignore the curveNode + if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) { + + if ( layerCurveNodes[ i ] === undefined ) { + + const modelID = connections.get( child.ID ).parents.filter( function ( parent ) { + + return parent.relationship !== undefined; + + } )[ 0 ].ID; + + if ( modelID !== undefined ) { + + const rawModel = fbxTree.Objects.Model[ modelID.toString() ]; + + if ( rawModel === undefined ) { + + console.warn( 'THREE.FBXLoader: Encountered a unused curve.', child ); + return; + + } + + const node = { + + modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '', + ID: rawModel.id, + initialPosition: [ 0, 0, 0 ], + initialRotation: [ 0, 0, 0 ], + initialScale: [ 1, 1, 1 ], + + }; + + sceneGraph.traverse( function ( child ) { + + if ( child.ID === rawModel.id ) { + + node.transform = child.matrix; + + if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder; + + } + + } ); + + if ( ! node.transform ) node.transform = new Matrix4(); + + // if the animated model is pre rotated, we'll have to apply the pre rotations to every + // animation value as well + if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value; + if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value; + + layerCurveNodes[ i ] = node; + + } + + } + + if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode; + + } else if ( curveNode.curves.morph !== undefined ) { + + if ( layerCurveNodes[ i ] === undefined ) { + + const deformerID = connections.get( child.ID ).parents.filter( function ( parent ) { + + return parent.relationship !== undefined; + + } )[ 0 ].ID; + + const morpherID = connections.get( deformerID ).parents[ 0 ].ID; + const geoID = connections.get( morpherID ).parents[ 0 ].ID; + + // assuming geometry is not used in more than one model + const modelID = connections.get( geoID ).parents[ 0 ].ID; + + const rawModel = fbxTree.Objects.Model[ modelID ]; + + const node = { + + modelName: rawModel.attrName ? PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '', + morphName: fbxTree.Objects.Deformer[ deformerID ].attrName, + + }; + + layerCurveNodes[ i ] = node; + + } + + layerCurveNodes[ i ][ curveNode.attr ] = curveNode; + + } + + } + + } ); + + layersMap.set( parseInt( nodeID ), layerCurveNodes ); + + } + + } + + return layersMap; + + } + + // parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation + // hierarchy. Each Stack node will be used to create a AnimationClip + parseAnimStacks( layersMap ) { + + const rawStacks = fbxTree.Objects.AnimationStack; + + // connect the stacks (clips) up to the layers + const rawClips = {}; + + for ( const nodeID in rawStacks ) { + + const children = connections.get( parseInt( nodeID ) ).children; + + if ( children.length > 1 ) { + + // it seems like stacks will always be associated with a single layer. But just in case there are files + // where there are multiple layers per stack, we'll display a warning + console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' ); + + } + + const layer = layersMap.get( children[ 0 ].ID ); + + rawClips[ nodeID ] = { + + name: rawStacks[ nodeID ].attrName, + layer: layer, + + }; + + } + + return rawClips; + + } + + addClip( rawClip ) { + + let tracks = []; + + const scope = this; + rawClip.layer.forEach( function ( rawTracks ) { + + tracks = tracks.concat( scope.generateTracks( rawTracks ) ); + + } ); + + return new AnimationClip( rawClip.name, - 1, tracks ); + + } + + generateTracks( rawTracks ) { + + const tracks = []; + + let initialPosition = new Vector3(); + let initialRotation = new Quaternion(); + let initialScale = new Vector3(); + + if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale ); + + initialPosition = initialPosition.toArray(); + initialRotation = new Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray(); + initialScale = initialScale.toArray(); + + if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) { + + const positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' ); + if ( positionTrack !== undefined ) tracks.push( positionTrack ); + + } + + if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) { + + const rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder ); + if ( rotationTrack !== undefined ) tracks.push( rotationTrack ); + + } + + if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) { + + const scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' ); + if ( scaleTrack !== undefined ) tracks.push( scaleTrack ); + + } + + if ( rawTracks.DeformPercent !== undefined ) { + + const morphTrack = this.generateMorphTrack( rawTracks ); + if ( morphTrack !== undefined ) tracks.push( morphTrack ); + + } + + return tracks; + + } + + generateVectorTrack( modelName, curves, initialValue, type ) { + + const times = this.getTimesForAllAxes( curves ); + const values = this.getKeyframeTrackValues( times, curves, initialValue ); + + return new VectorKeyframeTrack( modelName + '.' + type, times, values ); + + } + + generateRotationTrack( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) { + + if ( curves.x !== undefined ) { + + this.interpolateRotations( curves.x ); + curves.x.values = curves.x.values.map( MathUtils.degToRad ); + + } + + if ( curves.y !== undefined ) { + + this.interpolateRotations( curves.y ); + curves.y.values = curves.y.values.map( MathUtils.degToRad ); + + } + + if ( curves.z !== undefined ) { + + this.interpolateRotations( curves.z ); + curves.z.values = curves.z.values.map( MathUtils.degToRad ); + + } + + const times = this.getTimesForAllAxes( curves ); + const values = this.getKeyframeTrackValues( times, curves, initialValue ); + + if ( preRotation !== undefined ) { + + preRotation = preRotation.map( MathUtils.degToRad ); + preRotation.push( eulerOrder ); + + preRotation = new Euler().fromArray( preRotation ); + preRotation = new Quaternion().setFromEuler( preRotation ); + + } + + if ( postRotation !== undefined ) { + + postRotation = postRotation.map( MathUtils.degToRad ); + postRotation.push( eulerOrder ); + + postRotation = new Euler().fromArray( postRotation ); + postRotation = new Quaternion().setFromEuler( postRotation ).invert(); + + } + + const quaternion = new Quaternion(); + const euler = new Euler(); + + const quaternionValues = []; + + for ( let i = 0; i < values.length; i += 3 ) { + + euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder ); + + quaternion.setFromEuler( euler ); + + if ( preRotation !== undefined ) quaternion.premultiply( preRotation ); + if ( postRotation !== undefined ) quaternion.multiply( postRotation ); + + quaternion.toArray( quaternionValues, ( i / 3 ) * 4 ); + + } + + return new QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues ); + + } + + generateMorphTrack( rawTracks ) { + + const curves = rawTracks.DeformPercent.curves.morph; + const values = curves.values.map( function ( val ) { + + return val / 100; + + } ); + + const morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ]; + + return new NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values ); + + } + + // For all animated objects, times are defined separately for each axis + // Here we'll combine the times into one sorted array without duplicates + getTimesForAllAxes( curves ) { + + let times = []; + + // first join together the times for each axis, if defined + if ( curves.x !== undefined ) times = times.concat( curves.x.times ); + if ( curves.y !== undefined ) times = times.concat( curves.y.times ); + if ( curves.z !== undefined ) times = times.concat( curves.z.times ); + + // then sort them + times = times.sort( function ( a, b ) { + + return a - b; + + } ); + + // and remove duplicates + if ( times.length > 1 ) { + + let targetIndex = 1; + let lastValue = times[ 0 ]; + for ( let i = 1; i < times.length; i ++ ) { + + const currentValue = times[ i ]; + if ( currentValue !== lastValue ) { + + times[ targetIndex ] = currentValue; + lastValue = currentValue; + targetIndex ++; + + } + + } + + times = times.slice( 0, targetIndex ); + + } + + return times; + + } + + getKeyframeTrackValues( times, curves, initialValue ) { + + const prevValue = initialValue; + + const values = []; + + let xIndex = - 1; + let yIndex = - 1; + let zIndex = - 1; + + times.forEach( function ( time ) { + + if ( curves.x ) xIndex = curves.x.times.indexOf( time ); + if ( curves.y ) yIndex = curves.y.times.indexOf( time ); + if ( curves.z ) zIndex = curves.z.times.indexOf( time ); + + // if there is an x value defined for this frame, use that + if ( xIndex !== - 1 ) { + + const xValue = curves.x.values[ xIndex ]; + values.push( xValue ); + prevValue[ 0 ] = xValue; + + } else { + + // otherwise use the x value from the previous frame + values.push( prevValue[ 0 ] ); + + } + + if ( yIndex !== - 1 ) { + + const yValue = curves.y.values[ yIndex ]; + values.push( yValue ); + prevValue[ 1 ] = yValue; + + } else { + + values.push( prevValue[ 1 ] ); + + } + + if ( zIndex !== - 1 ) { + + const zValue = curves.z.values[ zIndex ]; + values.push( zValue ); + prevValue[ 2 ] = zValue; + + } else { + + values.push( prevValue[ 2 ] ); + + } + + } ); + + return values; + + } + + // Rotations are defined as Euler angles which can have values of any size + // These will be converted to quaternions which don't support values greater than + // PI, so we'll interpolate large rotations + interpolateRotations( curve ) { + + for ( let i = 1; i < curve.values.length; i ++ ) { + + const initialValue = curve.values[ i - 1 ]; + const valuesSpan = curve.values[ i ] - initialValue; + + const absoluteSpan = Math.abs( valuesSpan ); + + if ( absoluteSpan >= 180 ) { + + const numSubIntervals = absoluteSpan / 180; + + const step = valuesSpan / numSubIntervals; + let nextValue = initialValue + step; + + const initialTime = curve.times[ i - 1 ]; + const timeSpan = curve.times[ i ] - initialTime; + const interval = timeSpan / numSubIntervals; + let nextTime = initialTime + interval; + + const interpolatedTimes = []; + const interpolatedValues = []; + + while ( nextTime < curve.times[ i ] ) { + + interpolatedTimes.push( nextTime ); + nextTime += interval; + + interpolatedValues.push( nextValue ); + nextValue += step; + + } + + curve.times = inject( curve.times, i, interpolatedTimes ); + curve.values = inject( curve.values, i, interpolatedValues ); + + } + + } + + } + +} + +// parse an FBX file in ASCII format +class TextParser { + + getPrevNode() { + + return this.nodeStack[ this.currentIndent - 2 ]; + + } + + getCurrentNode() { + + return this.nodeStack[ this.currentIndent - 1 ]; + + } + + getCurrentProp() { + + return this.currentProp; + + } + + pushStack( node ) { + + this.nodeStack.push( node ); + this.currentIndent += 1; + + } + + popStack() { + + this.nodeStack.pop(); + this.currentIndent -= 1; + + } + + setCurrentProp( val, name ) { + + this.currentProp = val; + this.currentPropName = name; + + } + + parse( text ) { + + this.currentIndent = 0; + + this.allNodes = new FBXTree(); + this.nodeStack = []; + this.currentProp = []; + this.currentPropName = ''; + + const scope = this; + + const split = text.split( /[\r\n]+/ ); + + split.forEach( function ( line, i ) { + + const matchComment = line.match( /^[\s\t]*;/ ); + const matchEmpty = line.match( /^[\s\t]*$/ ); + + if ( matchComment || matchEmpty ) return; + + const matchBeginning = line.match( '^\\t{' + scope.currentIndent + '}(\\w+):(.*){', '' ); + const matchProperty = line.match( '^\\t{' + ( scope.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' ); + const matchEnd = line.match( '^\\t{' + ( scope.currentIndent - 1 ) + '}}' ); + + if ( matchBeginning ) { + + scope.parseNodeBegin( line, matchBeginning ); + + } else if ( matchProperty ) { + + scope.parseNodeProperty( line, matchProperty, split[ ++ i ] ); + + } else if ( matchEnd ) { + + scope.popStack(); + + } else if ( line.match( /^[^\s\t}]/ ) ) { + + // large arrays are split over multiple lines terminated with a ',' character + // if this is encountered the line needs to be joined to the previous line + scope.parseNodePropertyContinued( line ); + + } + + } ); + + return this.allNodes; + + } + + parseNodeBegin( line, property ) { + + const nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' ); + + const nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) { + + return attr.trim().replace( /^"/, '' ).replace( /"$/, '' ); + + } ); + + const node = { name: nodeName }; + const attrs = this.parseNodeAttr( nodeAttrs ); + + const currentNode = this.getCurrentNode(); + + // a top node + if ( this.currentIndent === 0 ) { + + this.allNodes.add( nodeName, node ); + + } else { // a subnode + + // if the subnode already exists, append it + if ( nodeName in currentNode ) { + + // special case Pose needs PoseNodes as an array + if ( nodeName === 'PoseNode' ) { + + currentNode.PoseNode.push( node ); + + } else if ( currentNode[ nodeName ].id !== undefined ) { + + currentNode[ nodeName ] = {}; + currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ]; + + } + + if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node; + + } else if ( typeof attrs.id === 'number' ) { + + currentNode[ nodeName ] = {}; + currentNode[ nodeName ][ attrs.id ] = node; + + } else if ( nodeName !== 'Properties70' ) { + + if ( nodeName === 'PoseNode' ) currentNode[ nodeName ] = [ node ]; + else currentNode[ nodeName ] = node; + + } + + } + + if ( typeof attrs.id === 'number' ) node.id = attrs.id; + if ( attrs.name !== '' ) node.attrName = attrs.name; + if ( attrs.type !== '' ) node.attrType = attrs.type; + + this.pushStack( node ); + + } + + parseNodeAttr( attrs ) { + + let id = attrs[ 0 ]; + + if ( attrs[ 0 ] !== '' ) { + + id = parseInt( attrs[ 0 ] ); + + if ( isNaN( id ) ) { + + id = attrs[ 0 ]; + + } + + } + + let name = '', type = ''; + + if ( attrs.length > 1 ) { + + name = attrs[ 1 ].replace( /^(\w+)::/, '' ); + type = attrs[ 2 ]; + + } + + return { id: id, name: name, type: type }; + + } + + parseNodeProperty( line, property, contentLine ) { + + let propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim(); + let propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim(); + + // for special case: base64 image data follows "Content: ," line + // Content: , + // "/9j/4RDaRXhpZgAATU0A..." + if ( propName === 'Content' && propValue === ',' ) { + + propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim(); + + } + + const currentNode = this.getCurrentNode(); + const parentName = currentNode.name; + + if ( parentName === 'Properties70' ) { + + this.parseNodeSpecialProperty( line, propName, propValue ); + return; + + } + + // Connections + if ( propName === 'C' ) { + + const connProps = propValue.split( ',' ).slice( 1 ); + const from = parseInt( connProps[ 0 ] ); + const to = parseInt( connProps[ 1 ] ); + + let rest = propValue.split( ',' ).slice( 3 ); + + rest = rest.map( function ( elem ) { + + return elem.trim().replace( /^"/, '' ); + + } ); + + propName = 'connections'; + propValue = [ from, to ]; + append( propValue, rest ); + + if ( currentNode[ propName ] === undefined ) { + + currentNode[ propName ] = []; + + } + + } + + // Node + if ( propName === 'Node' ) currentNode.id = propValue; + + // connections + if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) { + + currentNode[ propName ].push( propValue ); + + } else { + + if ( propName !== 'a' ) currentNode[ propName ] = propValue; + else currentNode.a = propValue; + + } + + this.setCurrentProp( currentNode, propName ); + + // convert string to array, unless it ends in ',' in which case more will be added to it + if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) { + + currentNode.a = parseNumberArray( propValue ); + + } + + } + + parseNodePropertyContinued( line ) { + + const currentNode = this.getCurrentNode(); + + currentNode.a += line; + + // if the line doesn't end in ',' we have reached the end of the property value + // so convert the string to an array + if ( line.slice( - 1 ) !== ',' ) { + + currentNode.a = parseNumberArray( currentNode.a ); + + } + + } + + // parse "Property70" + parseNodeSpecialProperty( line, propName, propValue ) { + + // split this + // P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1 + // into array like below + // ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ] + const props = propValue.split( '",' ).map( function ( prop ) { + + return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' ); + + } ); + + const innerPropName = props[ 0 ]; + const innerPropType1 = props[ 1 ]; + const innerPropType2 = props[ 2 ]; + const innerPropFlag = props[ 3 ]; + let innerPropValue = props[ 4 ]; + + // cast values where needed, otherwise leave as strings + switch ( innerPropType1 ) { + + case 'int': + case 'enum': + case 'bool': + case 'ULongLong': + case 'double': + case 'Number': + case 'FieldOfView': + innerPropValue = parseFloat( innerPropValue ); + break; + + case 'Color': + case 'ColorRGB': + case 'Vector3D': + case 'Lcl_Translation': + case 'Lcl_Rotation': + case 'Lcl_Scaling': + innerPropValue = parseNumberArray( innerPropValue ); + break; + + } + + // CAUTION: these props must append to parent's parent + this.getPrevNode()[ innerPropName ] = { + + 'type': innerPropType1, + 'type2': innerPropType2, + 'flag': innerPropFlag, + 'value': innerPropValue + + }; + + this.setCurrentProp( this.getPrevNode(), innerPropName ); + + } + +} + +// Parse an FBX file in Binary format +class BinaryParser { + + parse( buffer ) { + + const reader = new BinaryReader( buffer ); + reader.skip( 23 ); // skip magic 23 bytes + + const version = reader.getUint32(); + + if ( version < 6400 ) { + + throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + version ); + + } + + const allNodes = new FBXTree(); + + while ( ! this.endOfContent( reader ) ) { + + const node = this.parseNode( reader, version ); + if ( node !== null ) allNodes.add( node.name, node ); + + } + + return allNodes; + + } + + // Check if reader has reached the end of content. + endOfContent( reader ) { + + // footer size: 160bytes + 16-byte alignment padding + // - 16bytes: magic + // - padding til 16-byte alignment (at least 1byte?) + // (seems like some exporters embed fixed 15 or 16bytes?) + // - 4bytes: magic + // - 4bytes: version + // - 120bytes: zero + // - 16bytes: magic + if ( reader.size() % 16 === 0 ) { + + return ( ( reader.getOffset() + 160 + 16 ) & ~ 0xf ) >= reader.size(); + + } else { + + return reader.getOffset() + 160 + 16 >= reader.size(); + + } + + } + + // recursively parse nodes until the end of the file is reached + parseNode( reader, version ) { + + const node = {}; + + // The first three data sizes depends on version. + const endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); + const numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); + + ( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); // the returned propertyListLen is not used + + const nameLen = reader.getUint8(); + const name = reader.getString( nameLen ); + + // Regards this node as NULL-record if endOffset is zero + if ( endOffset === 0 ) return null; + + const propertyList = []; + + for ( let i = 0; i < numProperties; i ++ ) { + + propertyList.push( this.parseProperty( reader ) ); + + } + + // Regards the first three elements in propertyList as id, attrName, and attrType + const id = propertyList.length > 0 ? propertyList[ 0 ] : ''; + const attrName = propertyList.length > 1 ? propertyList[ 1 ] : ''; + const attrType = propertyList.length > 2 ? propertyList[ 2 ] : ''; + + // check if this node represents just a single property + // like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]} + node.singleProperty = ( numProperties === 1 && reader.getOffset() === endOffset ) ? true : false; + + while ( endOffset > reader.getOffset() ) { + + const subNode = this.parseNode( reader, version ); + + if ( subNode !== null ) this.parseSubNode( name, node, subNode ); + + } + + node.propertyList = propertyList; // raw property list used by parent + + if ( typeof id === 'number' ) node.id = id; + if ( attrName !== '' ) node.attrName = attrName; + if ( attrType !== '' ) node.attrType = attrType; + if ( name !== '' ) node.name = name; + + return node; + + } + + parseSubNode( name, node, subNode ) { + + // special case: child node is single property + if ( subNode.singleProperty === true ) { + + const value = subNode.propertyList[ 0 ]; + + if ( Array.isArray( value ) ) { + + node[ subNode.name ] = subNode; + + subNode.a = value; + + } else { + + node[ subNode.name ] = value; + + } + + } else if ( name === 'Connections' && subNode.name === 'C' ) { + + const array = []; + + subNode.propertyList.forEach( function ( property, i ) { + + // first Connection is FBX type (OO, OP, etc.). We'll discard these + if ( i !== 0 ) array.push( property ); + + } ); + + if ( node.connections === undefined ) { + + node.connections = []; + + } + + node.connections.push( array ); + + } else if ( subNode.name === 'Properties70' ) { + + const keys = Object.keys( subNode ); + + keys.forEach( function ( key ) { + + node[ key ] = subNode[ key ]; + + } ); + + } else if ( name === 'Properties70' && subNode.name === 'P' ) { + + let innerPropName = subNode.propertyList[ 0 ]; + let innerPropType1 = subNode.propertyList[ 1 ]; + const innerPropType2 = subNode.propertyList[ 2 ]; + const innerPropFlag = subNode.propertyList[ 3 ]; + let innerPropValue; + + if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' ); + if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' ); + + if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) { + + innerPropValue = [ + subNode.propertyList[ 4 ], + subNode.propertyList[ 5 ], + subNode.propertyList[ 6 ] + ]; + + } else { + + innerPropValue = subNode.propertyList[ 4 ]; + + } + + // this will be copied to parent, see above + node[ innerPropName ] = { + + 'type': innerPropType1, + 'type2': innerPropType2, + 'flag': innerPropFlag, + 'value': innerPropValue + + }; + + } else if ( node[ subNode.name ] === undefined ) { + + if ( typeof subNode.id === 'number' ) { + + node[ subNode.name ] = {}; + node[ subNode.name ][ subNode.id ] = subNode; + + } else { + + node[ subNode.name ] = subNode; + + } + + } else { + + if ( subNode.name === 'PoseNode' ) { + + if ( ! Array.isArray( node[ subNode.name ] ) ) { + + node[ subNode.name ] = [ node[ subNode.name ] ]; + + } + + node[ subNode.name ].push( subNode ); + + } else if ( node[ subNode.name ][ subNode.id ] === undefined ) { + + node[ subNode.name ][ subNode.id ] = subNode; + + } + + } + + } + + parseProperty( reader ) { + + const type = reader.getString( 1 ); + let length; + + switch ( type ) { + + case 'C': + return reader.getBoolean(); + + case 'D': + return reader.getFloat64(); + + case 'F': + return reader.getFloat32(); + + case 'I': + return reader.getInt32(); + + case 'L': + return reader.getInt64(); + + case 'R': + length = reader.getUint32(); + return reader.getArrayBuffer( length ); + + case 'S': + length = reader.getUint32(); + return reader.getString( length ); + + case 'Y': + return reader.getInt16(); + + case 'b': + case 'c': + case 'd': + case 'f': + case 'i': + case 'l': + + const arrayLength = reader.getUint32(); + const encoding = reader.getUint32(); // 0: non-compressed, 1: compressed + const compressedLength = reader.getUint32(); + + if ( encoding === 0 ) { + + switch ( type ) { + + case 'b': + case 'c': + return reader.getBooleanArray( arrayLength ); + + case 'd': + return reader.getFloat64Array( arrayLength ); + + case 'f': + return reader.getFloat32Array( arrayLength ); + + case 'i': + return reader.getInt32Array( arrayLength ); + + case 'l': + return reader.getInt64Array( arrayLength ); + + } + + } + + if ( typeof fflate === 'undefined' ) { + + console.error( 'THREE.FBXLoader: External library fflate.min.js required.' ); + + } + + const data = fflate.unzlibSync( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef + const reader2 = new BinaryReader( data.buffer ); + + switch ( type ) { + + case 'b': + case 'c': + return reader2.getBooleanArray( arrayLength ); + + case 'd': + return reader2.getFloat64Array( arrayLength ); + + case 'f': + return reader2.getFloat32Array( arrayLength ); + + case 'i': + return reader2.getInt32Array( arrayLength ); + + case 'l': + return reader2.getInt64Array( arrayLength ); + + } + + default: + throw new Error( 'THREE.FBXLoader: Unknown property type ' + type ); + + } + + } + +} + +class BinaryReader { + + constructor( buffer, littleEndian ) { + + this.dv = new DataView( buffer ); + this.offset = 0; + this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true; + + } + + getOffset() { + + return this.offset; + + } + + size() { + + return this.dv.buffer.byteLength; + + } + + skip( length ) { + + this.offset += length; + + } + + // seems like true/false representation depends on exporter. + // true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54) + // then sees LSB. + getBoolean() { + + return ( this.getUint8() & 1 ) === 1; + + } + + getBooleanArray( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getBoolean() ); + + } + + return a; + + } + + getUint8() { + + const value = this.dv.getUint8( this.offset ); + this.offset += 1; + return value; + + } + + getInt16() { + + const value = this.dv.getInt16( this.offset, this.littleEndian ); + this.offset += 2; + return value; + + } + + getInt32() { + + const value = this.dv.getInt32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + } + + getInt32Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getInt32() ); + + } + + return a; + + } + + getUint32() { + + const value = this.dv.getUint32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + } + + // JavaScript doesn't support 64-bit integer so calculate this here + // 1 << 32 will return 1 so using multiply operation instead here. + // There's a possibility that this method returns wrong value if the value + // is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER. + // TODO: safely handle 64-bit integer + getInt64() { + + let low, high; + + if ( this.littleEndian ) { + + low = this.getUint32(); + high = this.getUint32(); + + } else { + + high = this.getUint32(); + low = this.getUint32(); + + } + + // calculate negative value + if ( high & 0x80000000 ) { + + high = ~ high & 0xFFFFFFFF; + low = ~ low & 0xFFFFFFFF; + + if ( low === 0xFFFFFFFF ) high = ( high + 1 ) & 0xFFFFFFFF; + + low = ( low + 1 ) & 0xFFFFFFFF; + + return - ( high * 0x100000000 + low ); + + } + + return high * 0x100000000 + low; + + } + + getInt64Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getInt64() ); + + } + + return a; + + } + + // Note: see getInt64() comment + getUint64() { + + let low, high; + + if ( this.littleEndian ) { + + low = this.getUint32(); + high = this.getUint32(); + + } else { + + high = this.getUint32(); + low = this.getUint32(); + + } + + return high * 0x100000000 + low; + + } + + getFloat32() { + + const value = this.dv.getFloat32( this.offset, this.littleEndian ); + this.offset += 4; + return value; + + } + + getFloat32Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getFloat32() ); + + } + + return a; + + } + + getFloat64() { + + const value = this.dv.getFloat64( this.offset, this.littleEndian ); + this.offset += 8; + return value; + + } + + getFloat64Array( size ) { + + const a = []; + + for ( let i = 0; i < size; i ++ ) { + + a.push( this.getFloat64() ); + + } + + return a; + + } + + getArrayBuffer( size ) { + + const value = this.dv.buffer.slice( this.offset, this.offset + size ); + this.offset += size; + return value; + + } + + getString( size ) { + + // note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead + let a = []; + + for ( let i = 0; i < size; i ++ ) { + + a[ i ] = this.getUint8(); + + } + + const nullByte = a.indexOf( 0 ); + if ( nullByte >= 0 ) a = a.slice( 0, nullByte ); + + return LoaderUtils.decodeText( new Uint8Array( a ) ); + + } + +} + +// FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format) +// and BinaryParser( FBX Binary format) +class FBXTree { + + add( key, val ) { + + this[ key ] = val; + + } + +} + +// ************** UTILITY FUNCTIONS ************** + +function isFbxFormatBinary( buffer ) { + + const CORRECT = 'Kaydara\u0020FBX\u0020Binary\u0020\u0020\0'; + + return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length ); + +} + +function isFbxFormatASCII( text ) { + + const CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ]; + + let cursor = 0; + + function read( offset ) { + + const result = text[ offset - 1 ]; + text = text.slice( cursor + offset ); + cursor ++; + return result; + + } + + for ( let i = 0; i < CORRECT.length; ++ i ) { + + const num = read( 1 ); + if ( num === CORRECT[ i ] ) { + + return false; + + } + + } + + return true; + +} + +function getFbxVersion( text ) { + + const versionRegExp = /FBXVersion: (\d+)/; + const match = text.match( versionRegExp ); + + if ( match ) { + + const version = parseInt( match[ 1 ] ); + return version; + + } + + throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' ); + +} + +// Converts FBX ticks into real time seconds. +function convertFBXTimeToSeconds( time ) { + + return time / 46186158000; + +} + +const dataArray = []; + +// extracts the data from the correct position in the FBX array based on indexing type +function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) { + + let index; + + switch ( infoObject.mappingType ) { + + case 'ByPolygonVertex' : + index = polygonVertexIndex; + break; + case 'ByPolygon' : + index = polygonIndex; + break; + case 'ByVertice' : + index = vertexIndex; + break; + case 'AllSame' : + index = infoObject.indices[ 0 ]; + break; + default : + console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType ); + + } + + if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ]; + + const from = index * infoObject.dataSize; + const to = from + infoObject.dataSize; + + return slice( dataArray, infoObject.buffer, from, to ); + +} + +const tempEuler = new Euler(); +const tempVec = 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 +// ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e +function generateTransform( transformData ) { + + const lTranslationM = new Matrix4(); + const lPreRotationM = new Matrix4(); + const lRotationM = new Matrix4(); + const lPostRotationM = new Matrix4(); + + const lScalingM = new Matrix4(); + const lScalingPivotM = new Matrix4(); + const lScalingOffsetM = new Matrix4(); + const lRotationOffsetM = new Matrix4(); + const lRotationPivotM = new Matrix4(); + + const lParentGX = new Matrix4(); + const lParentLX = new Matrix4(); + const lGlobalT = new Matrix4(); + + const inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0; + + if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) ); + + if ( transformData.preRotation ) { + + const array = transformData.preRotation.map( MathUtils.degToRad ); + array.push( transformData.eulerOrder ); + lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); + + } + + if ( transformData.rotation ) { + + const array = transformData.rotation.map( MathUtils.degToRad ); + array.push( transformData.eulerOrder ); + lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); + + } + + if ( transformData.postRotation ) { + + const array = transformData.postRotation.map( MathUtils.degToRad ); + array.push( transformData.eulerOrder ); + lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) ); + lPostRotationM.invert(); + + } + + if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) ); + + // Pivots and offsets + if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) ); + if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) ); + if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) ); + if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) ); + + // parent transform + if ( transformData.parentMatrixWorld ) { + + lParentLX.copy( transformData.parentMatrix ); + lParentGX.copy( transformData.parentMatrixWorld ); + + } + + const lLRM = lPreRotationM.clone().multiply( lRotationM ).multiply( lPostRotationM ); + // Global Rotation + const lParentGRM = new Matrix4(); + lParentGRM.extractRotation( lParentGX ); + + // Global Shear*Scaling + const lParentTM = new Matrix4(); + lParentTM.copyPosition( lParentGX ); + + const lParentGRSM = lParentTM.clone().invert().multiply( lParentGX ); + const lParentGSM = lParentGRM.clone().invert().multiply( lParentGRSM ); + const lLSM = lScalingM; + + const lGlobalRS = new Matrix4(); + + if ( inheritType === 0 ) { + + lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM ); + + } else if ( inheritType === 1 ) { + + lGlobalRS.copy( lParentGRM ).multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM ); + + } else { + + const lParentLSM = new Matrix4().scale( new Vector3().setFromMatrixScale( lParentLX ) ); + const lParentLSM_inv = lParentLSM.clone().invert(); + const lParentGSM_noLocal = lParentGSM.clone().multiply( lParentLSM_inv ); + + lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM ); + + } + + const lRotationPivotM_inv = lRotationPivotM.clone().invert(); + const lScalingPivotM_inv = lScalingPivotM.clone().invert(); + // Calculate the local transform matrix + let lTransform = lTranslationM.clone().multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM_inv ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM_inv ); + + const lLocalTWithAllPivotAndOffsetInfo = new Matrix4().copyPosition( lTransform ); + + const lGlobalTranslation = lParentGX.clone().multiply( lLocalTWithAllPivotAndOffsetInfo ); + lGlobalT.copyPosition( lGlobalTranslation ); + + lTransform = lGlobalT.clone().multiply( lGlobalRS ); + + // from global to local + lTransform.premultiply( lParentGX.invert() ); + + return lTransform; + +} + +// Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order +// ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html +function getEulerOrder( order ) { + + order = order || 0; + + const enums = [ + 'ZYX', // -> XYZ extrinsic + 'YZX', // -> XZY extrinsic + 'XZY', // -> YZX extrinsic + 'ZXY', // -> YXZ extrinsic + 'YXZ', // -> ZXY extrinsic + 'XYZ', // -> ZYX extrinsic + //'SphericXYZ', // not possible to support + ]; + + if ( order === 6 ) { + + console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' ); + return enums[ 0 ]; + + } + + return enums[ order ]; + +} + +// Parses comma separated list of numbers and returns them an array. +// Used internally by the TextParser +function parseNumberArray( value ) { + + const array = value.split( ',' ).map( function ( val ) { + + return parseFloat( val ); + + } ); + + return array; + +} + +function convertArrayBufferToString( buffer, from, to ) { + + if ( from === undefined ) from = 0; + if ( to === undefined ) to = buffer.byteLength; + + return LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) ); + +} + +function append( a, b ) { + + for ( let i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) { + + a[ j ] = b[ i ]; + + } + +} + +function slice( a, b, from, to ) { + + for ( let i = from, j = 0; i < to; i ++, j ++ ) { + + a[ j ] = b[ i ]; + + } + + return a; + +} + +// inject array a2 into array a1 at index +function inject( a1, index, a2 ) { + + return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) ); + +} + +export { FBXLoader }; diff --git a/jsm/loaders/FontLoader.js b/jsm/loaders/FontLoader.js new file mode 100644 index 0000000..2f4f399 --- /dev/null +++ b/jsm/loaders/FontLoader.js @@ -0,0 +1,196 @@ +import { + FileLoader, + Loader, + ShapePath +} from 'three'; + +class FontLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + let json; + + try { + + json = JSON.parse( text ); + + } catch ( e ) { + + console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' ); + json = JSON.parse( text.substring( 65, text.length - 2 ) ); + + } + + const font = scope.parse( json ); + + if ( onLoad ) onLoad( font ); + + }, onProgress, onError ); + + } + + parse( json ) { + + return new Font( json ); + + } + +} + +// + +class Font { + + constructor( data ) { + + this.type = 'Font'; + + this.data = data; + + } + + generateShapes( text, size = 100 ) { + + const shapes = []; + const paths = createPaths( text, size, this.data ); + + for ( let p = 0, pl = paths.length; p < pl; p ++ ) { + + Array.prototype.push.apply( shapes, paths[ p ].toShapes() ); + + } + + return shapes; + + } + +} + +function createPaths( text, size, data ) { + + const chars = Array.from( text ); + const scale = size / data.resolution; + const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale; + + const paths = []; + + let offsetX = 0, offsetY = 0; + + for ( let i = 0; i < chars.length; i ++ ) { + + const char = chars[ i ]; + + if ( char === '\n' ) { + + offsetX = 0; + offsetY -= line_height; + + } else { + + const ret = createPath( char, scale, offsetX, offsetY, data ); + offsetX += ret.offsetX; + paths.push( ret.path ); + + } + + } + + return paths; + +} + +function createPath( char, scale, offsetX, offsetY, data ) { + + const glyph = data.glyphs[ char ] || data.glyphs[ '?' ]; + + if ( ! glyph ) { + + console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' ); + + return; + + } + + const path = new ShapePath(); + + let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2; + + if ( glyph.o ) { + + const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) ); + + for ( let i = 0, l = outline.length; i < l; ) { + + const action = outline[ i ++ ]; + + switch ( action ) { + + case 'm': // moveTo + + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + + path.moveTo( x, y ); + + break; + + case 'l': // lineTo + + x = outline[ i ++ ] * scale + offsetX; + y = outline[ i ++ ] * scale + offsetY; + + path.lineTo( x, y ); + + break; + + case 'q': // quadraticCurveTo + + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + + path.quadraticCurveTo( cpx1, cpy1, cpx, cpy ); + + break; + + case 'b': // bezierCurveTo + + cpx = outline[ i ++ ] * scale + offsetX; + cpy = outline[ i ++ ] * scale + offsetY; + cpx1 = outline[ i ++ ] * scale + offsetX; + cpy1 = outline[ i ++ ] * scale + offsetY; + cpx2 = outline[ i ++ ] * scale + offsetX; + cpy2 = outline[ i ++ ] * scale + offsetY; + + path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy ); + + break; + + } + + } + + } + + return { offsetX: glyph.ha * scale, path: path }; + +} + +Font.prototype.isFont = true; + +export { FontLoader, Font }; diff --git a/jsm/loaders/GCodeLoader.js b/jsm/loaders/GCodeLoader.js new file mode 100644 index 0000000..b438b51 --- /dev/null +++ b/jsm/loaders/GCodeLoader.js @@ -0,0 +1,262 @@ +import { + BufferGeometry, + Euler, + FileLoader, + Float32BufferAttribute, + Group, + LineBasicMaterial, + LineSegments, + Loader +} from 'three'; + +/** + * GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications. + * + * Gcode files are composed by commands used by machines to create objects. + * + * @class GCodeLoader + * @param {Manager} manager Loading manager. + */ + +class GCodeLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.splitLayer = false; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + let state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false }; + const layers = []; + + let currentLayer = undefined; + + const pathMaterial = new LineBasicMaterial( { color: 0xFF0000 } ); + pathMaterial.name = 'path'; + + const extrudingMaterial = new LineBasicMaterial( { color: 0x00FF00 } ); + extrudingMaterial.name = 'extruded'; + + function newLayer( line ) { + + currentLayer = { vertex: [], pathVertex: [], z: line.z }; + layers.push( currentLayer ); + + } + + //Create lie segment between p1 and p2 + function addSegment( p1, p2 ) { + + if ( currentLayer === undefined ) { + + newLayer( p1 ); + + } + + if ( state.extruding ) { + + currentLayer.vertex.push( p1.x, p1.y, p1.z ); + currentLayer.vertex.push( p2.x, p2.y, p2.z ); + + } else { + + currentLayer.pathVertex.push( p1.x, p1.y, p1.z ); + currentLayer.pathVertex.push( p2.x, p2.y, p2.z ); + + } + + } + + function delta( v1, v2 ) { + + return state.relative ? v2 : v2 - v1; + + } + + function absolute( v1, v2 ) { + + return state.relative ? v1 + v2 : v2; + + } + + const lines = data.replace( /;.+/g, '' ).split( '\n' ); + + for ( let i = 0; i < lines.length; i ++ ) { + + const tokens = lines[ i ].split( ' ' ); + const cmd = tokens[ 0 ].toUpperCase(); + + //Argumments + const args = {}; + tokens.splice( 1 ).forEach( function ( token ) { + + if ( token[ 0 ] !== undefined ) { + + const key = token[ 0 ].toLowerCase(); + const value = parseFloat( token.substring( 1 ) ); + args[ key ] = value; + + } + + } ); + + //Process commands + //G0/G1 – Linear Movement + if ( cmd === 'G0' || cmd === 'G1' ) { + + const line = { + x: args.x !== undefined ? absolute( state.x, args.x ) : state.x, + y: args.y !== undefined ? absolute( state.y, args.y ) : state.y, + z: args.z !== undefined ? absolute( state.z, args.z ) : state.z, + e: args.e !== undefined ? absolute( state.e, args.e ) : state.e, + f: args.f !== undefined ? absolute( state.f, args.f ) : state.f, + }; + + //Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position + if ( delta( state.e, line.e ) > 0 ) { + + state.extruding = delta( state.e, line.e ) > 0; + + if ( currentLayer == undefined || line.z != currentLayer.z ) { + + newLayer( line ); + + } + + } + + addSegment( state, line ); + state = line; + + } else if ( cmd === 'G2' || cmd === 'G3' ) { + + //G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise ) + //console.warn( 'THREE.GCodeLoader: Arc command not supported' ); + + } else if ( cmd === 'G90' ) { + + //G90: Set to Absolute Positioning + state.relative = false; + + } else if ( cmd === 'G91' ) { + + //G91: Set to state.relative Positioning + state.relative = true; + + } else if ( cmd === 'G92' ) { + + //G92: Set Position + const line = state; + line.x = args.x !== undefined ? args.x : line.x; + line.y = args.y !== undefined ? args.y : line.y; + line.z = args.z !== undefined ? args.z : line.z; + line.e = args.e !== undefined ? args.e : line.e; + + } else { + + //console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd ); + + } + + } + + function addObject( vertex, extruding, i ) { + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', new Float32BufferAttribute( vertex, 3 ) ); + const segments = new LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial ); + segments.name = 'layer' + i; + object.add( segments ); + + } + + const object = new Group(); + object.name = 'gcode'; + + if ( this.splitLayer ) { + + for ( let i = 0; i < layers.length; i ++ ) { + + const layer = layers[ i ]; + addObject( layer.vertex, true, i ); + addObject( layer.pathVertex, false, i ); + + } + + } else { + + const vertex = [], + pathVertex = []; + + for ( let i = 0; i < layers.length; i ++ ) { + + const layer = layers[ i ]; + const layerVertex = layer.vertex; + const layerPathVertex = layer.pathVertex; + + for ( let j = 0; j < layerVertex.length; j ++ ) { + + vertex.push( layerVertex[ j ] ); + + } + + for ( let j = 0; j < layerPathVertex.length; j ++ ) { + + pathVertex.push( layerPathVertex[ j ] ); + + } + + } + + addObject( vertex, true, layers.length ); + addObject( pathVertex, false, layers.length ); + + } + + object.quaternion.setFromEuler( new Euler( - Math.PI / 2, 0, 0 ) ); + + return object; + + } + +} + +export { GCodeLoader }; diff --git a/jsm/loaders/GLTFLoader.js b/jsm/loaders/GLTFLoader.js new file mode 100644 index 0000000..31cfe0d --- /dev/null +++ b/jsm/loaders/GLTFLoader.js @@ -0,0 +1,4402 @@ +import { + AnimationClip, + Bone, + Box3, + BufferAttribute, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DirectionalLight, + DoubleSide, + FileLoader, + FrontSide, + Group, + ImageBitmapLoader, + InterleavedBuffer, + InterleavedBufferAttribute, + Interpolant, + InterpolateDiscrete, + InterpolateLinear, + Line, + LineBasicMaterial, + LineLoop, + LineSegments, + LinearFilter, + LinearMipmapLinearFilter, + LinearMipmapNearestFilter, + Loader, + LoaderUtils, + Material, + MathUtils, + Matrix4, + Mesh, + MeshBasicMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MirroredRepeatWrapping, + NearestFilter, + NearestMipmapLinearFilter, + NearestMipmapNearestFilter, + NumberKeyframeTrack, + Object3D, + OrthographicCamera, + PerspectiveCamera, + PointLight, + Points, + PointsMaterial, + PropertyBinding, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Skeleton, + SkinnedMesh, + Sphere, + SpotLight, + TangentSpaceNormalMap, + Texture, + TextureLoader, + TriangleFanDrawMode, + TriangleStripDrawMode, + Vector2, + Vector3, + VectorKeyframeTrack, + sRGBEncoding +} from '../../src/Three.js'; + +class GLTFLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.dracoLoader = null; + this.ktx2Loader = null; + this.meshoptDecoder = null; + + this.pluginCallbacks = []; + + this.register( function ( parser ) { + + return new GLTFMaterialsClearcoatExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFTextureBasisUExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFTextureWebPExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsSheenExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsTransmissionExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsVolumeExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsIorExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMaterialsSpecularExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFLightsExtension( parser ); + + } ); + + this.register( function ( parser ) { + + return new GLTFMeshoptCompression( parser ); + + } ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + let resourcePath; + + if ( this.resourcePath !== '' ) { + + resourcePath = this.resourcePath; + + } else if ( this.path !== '' ) { + + resourcePath = this.path; + + } else { + + resourcePath = LoaderUtils.extractUrlBase( url ); + + } + + // Tells the LoadingManager to track an extra item, which resolves after + // the model is fully loaded. This means the count of items loaded will + // be incorrect, but ensures manager.onLoad() does not fire early. + this.manager.itemStart( url ); + + const _onError = function ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + scope.manager.itemEnd( url ); + + }; + + const loader = new FileLoader( this.manager ); + + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, function ( data ) { + + try { + + scope.parse( data, resourcePath, function ( gltf ) { + + onLoad( gltf ); + + scope.manager.itemEnd( url ); + + }, _onError ); + + } catch ( e ) { + + _onError( e ); + + } + + }, onProgress, _onError ); + + } + + setDRACOLoader( dracoLoader ) { + + this.dracoLoader = dracoLoader; + return this; + + } + + setDDSLoader() { + + throw new Error( + + 'THREE.GLTFLoader: "MSFT_texture_dds" no longer supported. Please update to "KHR_texture_basisu".' + + ); + + } + + setKTX2Loader( ktx2Loader ) { + + this.ktx2Loader = ktx2Loader; + return this; + + } + + setMeshoptDecoder( meshoptDecoder ) { + + this.meshoptDecoder = meshoptDecoder; + return this; + + } + + 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( data, path, onLoad, onError ) { + + let content; + const extensions = {}; + const plugins = {}; + + if ( typeof data === 'string' ) { + + content = data; + + } else { + + const magic = LoaderUtils.decodeText( new Uint8Array( data, 0, 4 ) ); + + if ( magic === BINARY_EXTENSION_HEADER_MAGIC ) { + + try { + + extensions[ EXTENSIONS.KHR_BINARY_GLTF ] = new GLTFBinaryExtension( data ); + + } catch ( error ) { + + if ( onError ) onError( error ); + return; + + } + + content = extensions[ EXTENSIONS.KHR_BINARY_GLTF ].content; + + } else { + + content = LoaderUtils.decodeText( new Uint8Array( data ) ); + + } + + } + + const json = JSON.parse( content ); + + if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) { + + if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) ); + return; + + } + + const parser = new GLTFParser( json, { + + path: path || this.resourcePath || '', + crossOrigin: this.crossOrigin, + requestHeader: this.requestHeader, + manager: this.manager, + ktx2Loader: this.ktx2Loader, + meshoptDecoder: this.meshoptDecoder + + } ); + + parser.fileLoader.setRequestHeader( this.requestHeader ); + + for ( let i = 0; i < this.pluginCallbacks.length; i ++ ) { + + const plugin = this.pluginCallbacks[ i ]( parser ); + plugins[ plugin.name ] = plugin; + + // Workaround to avoid determining as unknown extension + // in addUnknownExtensionsToUserData(). + // Remove this workaround if we move all the existing + // extension handlers to plugin system + extensions[ plugin.name ] = true; + + } + + if ( json.extensionsUsed ) { + + for ( let i = 0; i < json.extensionsUsed.length; ++ i ) { + + const extensionName = json.extensionsUsed[ i ]; + const extensionsRequired = json.extensionsRequired || []; + + switch ( extensionName ) { + + case EXTENSIONS.KHR_MATERIALS_UNLIT: + extensions[ extensionName ] = new GLTFMaterialsUnlitExtension(); + break; + + case EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: + extensions[ extensionName ] = new GLTFMaterialsPbrSpecularGlossinessExtension(); + break; + + case EXTENSIONS.KHR_DRACO_MESH_COMPRESSION: + extensions[ extensionName ] = new GLTFDracoMeshCompressionExtension( json, this.dracoLoader ); + break; + + case EXTENSIONS.KHR_TEXTURE_TRANSFORM: + extensions[ extensionName ] = new GLTFTextureTransformExtension(); + break; + + case EXTENSIONS.KHR_MESH_QUANTIZATION: + extensions[ extensionName ] = new GLTFMeshQuantizationExtension(); + break; + + default: + + if ( extensionsRequired.indexOf( extensionName ) >= 0 && plugins[ extensionName ] === undefined ) { + + console.warn( 'THREE.GLTFLoader: Unknown extension "' + extensionName + '".' ); + + } + + } + + } + + } + + parser.setExtensions( extensions ); + parser.setPlugins( plugins ); + parser.parse( onLoad, onError ); + + } + + parseAsync( data, path ) { + + const scope = this; + + return new Promise( function ( resolve, reject ) { + + scope.parse( data, path, resolve, reject ); + + } ); + + } + +} + +/* GLTFREGISTRY */ + +function GLTFRegistry() { + + let objects = {}; + + return { + + get: function ( key ) { + + return objects[ key ]; + + }, + + add: function ( key, object ) { + + objects[ key ] = object; + + }, + + remove: function ( key ) { + + delete objects[ key ]; + + }, + + removeAll: function () { + + objects = {}; + + } + + }; + +} + +/*********************************/ +/********** EXTENSIONS ***********/ +/*********************************/ + +const EXTENSIONS = { + KHR_BINARY_GLTF: 'KHR_binary_glTF', + KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression', + KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual', + KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat', + KHR_MATERIALS_IOR: 'KHR_materials_ior', + KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS: 'KHR_materials_pbrSpecularGlossiness', + KHR_MATERIALS_SHEEN: 'KHR_materials_sheen', + KHR_MATERIALS_SPECULAR: 'KHR_materials_specular', + KHR_MATERIALS_TRANSMISSION: 'KHR_materials_transmission', + KHR_MATERIALS_UNLIT: 'KHR_materials_unlit', + KHR_MATERIALS_VOLUME: 'KHR_materials_volume', + KHR_TEXTURE_BASISU: 'KHR_texture_basisu', + KHR_TEXTURE_TRANSFORM: 'KHR_texture_transform', + KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization', + EXT_TEXTURE_WEBP: 'EXT_texture_webp', + EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression' +}; + +/** + * Punctual Lights Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual + */ +class GLTFLightsExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_LIGHTS_PUNCTUAL; + + // Object3D instance caches + this.cache = { refs: {}, uses: {} }; + + } + + _markDefs() { + + const parser = this.parser; + const nodeDefs = this.parser.json.nodes || []; + + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + + const nodeDef = nodeDefs[ nodeIndex ]; + + if ( nodeDef.extensions + && nodeDef.extensions[ this.name ] + && nodeDef.extensions[ this.name ].light !== undefined ) { + + parser._addNodeRef( this.cache, nodeDef.extensions[ this.name ].light ); + + } + + } + + } + + _loadLight( lightIndex ) { + + const parser = this.parser; + const cacheKey = 'light:' + lightIndex; + let dependency = parser.cache.get( cacheKey ); + + if ( dependency ) return dependency; + + const json = parser.json; + const extensions = ( json.extensions && json.extensions[ this.name ] ) || {}; + const lightDefs = extensions.lights || []; + const lightDef = lightDefs[ lightIndex ]; + let lightNode; + + const color = new Color( 0xffffff ); + + if ( lightDef.color !== undefined ) color.fromArray( lightDef.color ); + + const range = lightDef.range !== undefined ? lightDef.range : 0; + + switch ( lightDef.type ) { + + case 'directional': + lightNode = new DirectionalLight( color ); + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + case 'point': + lightNode = new PointLight( color ); + lightNode.distance = range; + break; + + case 'spot': + lightNode = new SpotLight( color ); + lightNode.distance = range; + // Handle spotlight properties. + lightDef.spot = lightDef.spot || {}; + lightDef.spot.innerConeAngle = lightDef.spot.innerConeAngle !== undefined ? lightDef.spot.innerConeAngle : 0; + lightDef.spot.outerConeAngle = lightDef.spot.outerConeAngle !== undefined ? lightDef.spot.outerConeAngle : Math.PI / 4.0; + lightNode.angle = lightDef.spot.outerConeAngle; + lightNode.penumbra = 1.0 - lightDef.spot.innerConeAngle / lightDef.spot.outerConeAngle; + lightNode.target.position.set( 0, 0, - 1 ); + lightNode.add( lightNode.target ); + break; + + default: + throw new Error( 'THREE.GLTFLoader: Unexpected light type: ' + lightDef.type ); + + } + + // Some lights (e.g. spot) default to a position other than the origin. Reset the position + // here, because node-level parsing will only override position if explicitly specified. + lightNode.position.set( 0, 0, 0 ); + + lightNode.decay = 2; + + if ( lightDef.intensity !== undefined ) lightNode.intensity = lightDef.intensity; + + lightNode.name = parser.createUniqueName( lightDef.name || ( 'light_' + lightIndex ) ); + + dependency = Promise.resolve( lightNode ); + + parser.cache.add( cacheKey, dependency ); + + return dependency; + + } + + createNodeAttachment( nodeIndex ) { + + const self = this; + const parser = this.parser; + const json = parser.json; + const nodeDef = json.nodes[ nodeIndex ]; + const lightDef = ( nodeDef.extensions && nodeDef.extensions[ this.name ] ) || {}; + const lightIndex = lightDef.light; + + if ( lightIndex === undefined ) return null; + + return this._loadLight( lightIndex ).then( function ( light ) { + + return parser._getNodeRef( self.cache, lightIndex, light ); + + } ); + + } + +} + +/** + * Unlit Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit + */ +class GLTFMaterialsUnlitExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_MATERIALS_UNLIT; + + } + + getMaterialType() { + + return MeshBasicMaterial; + + } + + extendParams( materialParams, materialDef, parser ) { + + const pending = []; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + const metallicRoughness = materialDef.pbrMetallicRoughness; + + if ( metallicRoughness ) { + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + const array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, sRGBEncoding ) ); + + } + + } + + return Promise.all( pending ); + + } + +} + +/** + * Clearcoat Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat + */ +class GLTFMaterialsClearcoatExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_CLEARCOAT; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + if ( extension.clearcoatFactor !== undefined ) { + + materialParams.clearcoat = extension.clearcoatFactor; + + } + + if ( extension.clearcoatTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatMap', extension.clearcoatTexture ) ); + + } + + if ( extension.clearcoatRoughnessFactor !== undefined ) { + + materialParams.clearcoatRoughness = extension.clearcoatRoughnessFactor; + + } + + if ( extension.clearcoatRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatRoughnessMap', extension.clearcoatRoughnessTexture ) ); + + } + + if ( extension.clearcoatNormalTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'clearcoatNormalMap', extension.clearcoatNormalTexture ) ); + + if ( extension.clearcoatNormalTexture.scale !== undefined ) { + + const scale = extension.clearcoatNormalTexture.scale; + + materialParams.clearcoatNormalScale = new Vector2( scale, scale ); + + } + + } + + return Promise.all( pending ); + + } + +} + +/** + * Sheen Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen + */ +class GLTFMaterialsSheenExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_SHEEN; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + materialParams.sheenColor = new Color( 0, 0, 0 ); + materialParams.sheenRoughness = 0; + materialParams.sheen = 1; + + const extension = materialDef.extensions[ this.name ]; + + if ( extension.sheenColorFactor !== undefined ) { + + materialParams.sheenColor.fromArray( extension.sheenColorFactor ); + + } + + if ( extension.sheenRoughnessFactor !== undefined ) { + + materialParams.sheenRoughness = extension.sheenRoughnessFactor; + + } + + if ( extension.sheenColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'sheenColorMap', extension.sheenColorTexture, sRGBEncoding ) ); + + } + + if ( extension.sheenRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'sheenRoughnessMap', extension.sheenRoughnessTexture ) ); + + } + + return Promise.all( pending ); + + } + +} + +/** + * Transmission Materials Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission + * Draft: https://github.com/KhronosGroup/glTF/pull/1698 + */ +class GLTFMaterialsTransmissionExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_TRANSMISSION; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + if ( extension.transmissionFactor !== undefined ) { + + materialParams.transmission = extension.transmissionFactor; + + } + + if ( extension.transmissionTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'transmissionMap', extension.transmissionTexture ) ); + + } + + return Promise.all( pending ); + + } + +} + +/** + * Materials Volume Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume + */ +class GLTFMaterialsVolumeExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_VOLUME; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + materialParams.thickness = extension.thicknessFactor !== undefined ? extension.thicknessFactor : 0; + + if ( extension.thicknessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'thicknessMap', extension.thicknessTexture ) ); + + } + + materialParams.attenuationDistance = extension.attenuationDistance || 0; + + const colorArray = extension.attenuationColor || [ 1, 1, 1 ]; + materialParams.attenuationColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] ); + + return Promise.all( pending ); + + } + +} + +/** + * Materials ior Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior + */ +class GLTFMaterialsIorExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_IOR; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const extension = materialDef.extensions[ this.name ]; + + materialParams.ior = extension.ior !== undefined ? extension.ior : 1.5; + + return Promise.resolve(); + + } + +} + +/** + * Materials specular Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular + */ +class GLTFMaterialsSpecularExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_MATERIALS_SPECULAR; + + } + + getMaterialType( materialIndex ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null; + + return MeshPhysicalMaterial; + + } + + extendMaterialParams( materialIndex, materialParams ) { + + const parser = this.parser; + const materialDef = parser.json.materials[ materialIndex ]; + + if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) { + + return Promise.resolve(); + + } + + const pending = []; + + const extension = materialDef.extensions[ this.name ]; + + materialParams.specularIntensity = extension.specularFactor !== undefined ? extension.specularFactor : 1.0; + + if ( extension.specularTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'specularIntensityMap', extension.specularTexture ) ); + + } + + const colorArray = extension.specularColorFactor || [ 1, 1, 1 ]; + materialParams.specularColor = new Color( colorArray[ 0 ], colorArray[ 1 ], colorArray[ 2 ] ); + + if ( extension.specularColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'specularColorMap', extension.specularColorTexture, sRGBEncoding ) ); + + } + + return Promise.all( pending ); + + } + +} + +/** + * BasisU Texture Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_basisu + */ +class GLTFTextureBasisUExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.KHR_TEXTURE_BASISU; + + } + + loadTexture( textureIndex ) { + + const parser = this.parser; + const json = parser.json; + + const textureDef = json.textures[ textureIndex ]; + + if ( ! textureDef.extensions || ! textureDef.extensions[ this.name ] ) { + + return null; + + } + + const extension = textureDef.extensions[ this.name ]; + const loader = parser.options.ktx2Loader; + + if ( ! loader ) { + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: setKTX2Loader must be called before loading KTX2 textures' ); + + } else { + + // Assumes that the extension is optional and that a fallback texture is present + return null; + + } + + } + + return parser.loadTextureImage( textureIndex, extension.source, loader ); + + } + +} + +/** + * WebP Texture Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_texture_webp + */ +class GLTFTextureWebPExtension { + + constructor( parser ) { + + this.parser = parser; + this.name = EXTENSIONS.EXT_TEXTURE_WEBP; + this.isSupported = null; + + } + + loadTexture( textureIndex ) { + + const name = this.name; + const parser = this.parser; + const json = parser.json; + + const textureDef = json.textures[ textureIndex ]; + + if ( ! textureDef.extensions || ! textureDef.extensions[ name ] ) { + + return null; + + } + + const extension = textureDef.extensions[ name ]; + const source = json.images[ extension.source ]; + + let loader = parser.textureLoader; + if ( source.uri ) { + + const handler = parser.options.manager.getHandler( source.uri ); + if ( handler !== null ) loader = handler; + + } + + return this.detectSupport().then( function ( isSupported ) { + + if ( isSupported ) return parser.loadTextureImage( textureIndex, extension.source, loader ); + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: WebP required by asset but unsupported.' ); + + } + + // Fall back to PNG or JPEG. + return parser.loadTexture( textureIndex ); + + } ); + + } + + detectSupport() { + + if ( ! this.isSupported ) { + + this.isSupported = new Promise( function ( resolve ) { + + const image = new Image(); + + // Lossy test image. Support for lossy images doesn't guarantee support for all + // WebP images, unfortunately. + image.src = 'data:image/webp;base64,UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA'; + + image.onload = image.onerror = function () { + + resolve( image.height === 1 ); + + }; + + } ); + + } + + return this.isSupported; + + } + +} + +/** + * meshopt BufferView Compression Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_meshopt_compression + */ +class GLTFMeshoptCompression { + + constructor( parser ) { + + this.name = EXTENSIONS.EXT_MESHOPT_COMPRESSION; + this.parser = parser; + + } + + loadBufferView( index ) { + + const json = this.parser.json; + const bufferView = json.bufferViews[ index ]; + + if ( bufferView.extensions && bufferView.extensions[ this.name ] ) { + + const extensionDef = bufferView.extensions[ this.name ]; + + const buffer = this.parser.getDependency( 'buffer', extensionDef.buffer ); + const decoder = this.parser.options.meshoptDecoder; + + if ( ! decoder || ! decoder.supported ) { + + if ( json.extensionsRequired && json.extensionsRequired.indexOf( this.name ) >= 0 ) { + + throw new Error( 'THREE.GLTFLoader: setMeshoptDecoder must be called before loading compressed files' ); + + } else { + + // Assumes that the extension is optional and that fallback buffer data is present + return null; + + } + + } + + return Promise.all( [ buffer, decoder.ready ] ).then( function ( res ) { + + const byteOffset = extensionDef.byteOffset || 0; + const byteLength = extensionDef.byteLength || 0; + + const count = extensionDef.count; + const stride = extensionDef.byteStride; + + const result = new ArrayBuffer( count * stride ); + const source = new Uint8Array( res[ 0 ], byteOffset, byteLength ); + + decoder.decodeGltfBuffer( new Uint8Array( result ), count, stride, source, extensionDef.mode, extensionDef.filter ); + return result; + + } ); + + } else { + + return null; + + } + + } + +} + +/* BINARY EXTENSION */ +const BINARY_EXTENSION_HEADER_MAGIC = 'glTF'; +const BINARY_EXTENSION_HEADER_LENGTH = 12; +const BINARY_EXTENSION_CHUNK_TYPES = { JSON: 0x4E4F534A, BIN: 0x004E4942 }; + +class GLTFBinaryExtension { + + constructor( data ) { + + this.name = EXTENSIONS.KHR_BINARY_GLTF; + this.content = null; + this.body = null; + + const headerView = new DataView( data, 0, BINARY_EXTENSION_HEADER_LENGTH ); + + this.header = { + magic: LoaderUtils.decodeText( new Uint8Array( data.slice( 0, 4 ) ) ), + version: headerView.getUint32( 4, true ), + length: headerView.getUint32( 8, true ) + }; + + if ( this.header.magic !== BINARY_EXTENSION_HEADER_MAGIC ) { + + throw new Error( 'THREE.GLTFLoader: Unsupported glTF-Binary header.' ); + + } else if ( this.header.version < 2.0 ) { + + throw new Error( 'THREE.GLTFLoader: Legacy binary file detected.' ); + + } + + const chunkContentsLength = this.header.length - BINARY_EXTENSION_HEADER_LENGTH; + const chunkView = new DataView( data, BINARY_EXTENSION_HEADER_LENGTH ); + let chunkIndex = 0; + + while ( chunkIndex < chunkContentsLength ) { + + const chunkLength = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + const chunkType = chunkView.getUint32( chunkIndex, true ); + chunkIndex += 4; + + if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.JSON ) { + + const contentArray = new Uint8Array( data, BINARY_EXTENSION_HEADER_LENGTH + chunkIndex, chunkLength ); + this.content = LoaderUtils.decodeText( contentArray ); + + } else if ( chunkType === BINARY_EXTENSION_CHUNK_TYPES.BIN ) { + + const byteOffset = BINARY_EXTENSION_HEADER_LENGTH + chunkIndex; + this.body = data.slice( byteOffset, byteOffset + chunkLength ); + + } + + // Clients must ignore chunks with unknown types. + + chunkIndex += chunkLength; + + } + + if ( this.content === null ) { + + throw new Error( 'THREE.GLTFLoader: JSON content not found.' ); + + } + + } + +} + +/** + * DRACO Mesh Compression Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression + */ +class GLTFDracoMeshCompressionExtension { + + constructor( json, dracoLoader ) { + + if ( ! dracoLoader ) { + + throw new Error( 'THREE.GLTFLoader: No DRACOLoader instance provided.' ); + + } + + this.name = EXTENSIONS.KHR_DRACO_MESH_COMPRESSION; + this.json = json; + this.dracoLoader = dracoLoader; + this.dracoLoader.preload(); + + } + + decodePrimitive( primitive, parser ) { + + const json = this.json; + const dracoLoader = this.dracoLoader; + const bufferViewIndex = primitive.extensions[ this.name ].bufferView; + const gltfAttributeMap = primitive.extensions[ this.name ].attributes; + const threeAttributeMap = {}; + const attributeNormalizedMap = {}; + const attributeTypeMap = {}; + + for ( const attributeName in gltfAttributeMap ) { + + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + threeAttributeMap[ threeAttributeName ] = gltfAttributeMap[ attributeName ]; + + } + + for ( const attributeName in primitive.attributes ) { + + const threeAttributeName = ATTRIBUTES[ attributeName ] || attributeName.toLowerCase(); + + if ( gltfAttributeMap[ attributeName ] !== undefined ) { + + const accessorDef = json.accessors[ primitive.attributes[ attributeName ] ]; + const componentType = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + attributeTypeMap[ threeAttributeName ] = componentType; + attributeNormalizedMap[ threeAttributeName ] = accessorDef.normalized === true; + + } + + } + + return parser.getDependency( 'bufferView', bufferViewIndex ).then( function ( bufferView ) { + + return new Promise( function ( resolve ) { + + dracoLoader.decodeDracoFile( bufferView, function ( geometry ) { + + for ( const attributeName in geometry.attributes ) { + + const attribute = geometry.attributes[ attributeName ]; + const normalized = attributeNormalizedMap[ attributeName ]; + + if ( normalized !== undefined ) attribute.normalized = normalized; + + } + + resolve( geometry ); + + }, threeAttributeMap, attributeTypeMap ); + + } ); + + } ); + + } + +} + +/** + * Texture Transform Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_texture_transform + */ +class GLTFTextureTransformExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_TEXTURE_TRANSFORM; + + } + + extendTexture( texture, transform ) { + + if ( transform.texCoord !== undefined ) { + + console.warn( 'THREE.GLTFLoader: Custom UV sets in "' + this.name + '" extension not yet supported.' ); + + } + + if ( transform.offset === undefined && transform.rotation === undefined && transform.scale === undefined ) { + + // See https://github.com/mrdoob/three.js/issues/21819. + return texture; + + } + + texture = texture.clone(); + + if ( transform.offset !== undefined ) { + + texture.offset.fromArray( transform.offset ); + + } + + if ( transform.rotation !== undefined ) { + + texture.rotation = transform.rotation; + + } + + if ( transform.scale !== undefined ) { + + texture.repeat.fromArray( transform.scale ); + + } + + texture.needsUpdate = true; + + return texture; + + } + +} + +/** + * Specular-Glossiness Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness + */ + +/** + * A sub class of StandardMaterial with some of the functionality + * changed via the `onBeforeCompile` callback + * @pailhead + */ +class GLTFMeshStandardSGMaterial extends MeshStandardMaterial { + + constructor( params ) { + + super(); + + this.isGLTFSpecularGlossinessMaterial = true; + + //various chunks that need replacing + const specularMapParsFragmentChunk = [ + '#ifdef USE_SPECULARMAP', + ' uniform sampler2D specularMap;', + '#endif' + ].join( '\n' ); + + const glossinessMapParsFragmentChunk = [ + '#ifdef USE_GLOSSINESSMAP', + ' uniform sampler2D glossinessMap;', + '#endif' + ].join( '\n' ); + + const specularMapFragmentChunk = [ + 'vec3 specularFactor = specular;', + '#ifdef USE_SPECULARMAP', + ' vec4 texelSpecular = texture2D( specularMap, vUv );', + ' // reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' specularFactor *= texelSpecular.rgb;', + '#endif' + ].join( '\n' ); + + const glossinessMapFragmentChunk = [ + 'float glossinessFactor = glossiness;', + '#ifdef USE_GLOSSINESSMAP', + ' vec4 texelGlossiness = texture2D( glossinessMap, vUv );', + ' // reads channel A, compatible with a glTF Specular-Glossiness (RGBA) texture', + ' glossinessFactor *= texelGlossiness.a;', + '#endif' + ].join( '\n' ); + + const lightPhysicalFragmentChunk = [ + 'PhysicalMaterial material;', + 'material.diffuseColor = diffuseColor.rgb * ( 1. - max( specularFactor.r, max( specularFactor.g, specularFactor.b ) ) );', + 'vec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );', + 'float geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );', + 'material.roughness = max( 1.0 - glossinessFactor, 0.0525 ); // 0.0525 corresponds to the base mip of a 256 cubemap.', + 'material.roughness += geometryRoughness;', + 'material.roughness = min( material.roughness, 1.0 );', + 'material.specularColor = specularFactor;', + ].join( '\n' ); + + const uniforms = { + specular: { value: new Color().setHex( 0xffffff ) }, + glossiness: { value: 1 }, + specularMap: { value: null }, + glossinessMap: { value: null } + }; + + this._extraUniforms = uniforms; + + this.onBeforeCompile = function ( shader ) { + + for ( const uniformName in uniforms ) { + + shader.uniforms[ uniformName ] = uniforms[ uniformName ]; + + } + + shader.fragmentShader = shader.fragmentShader + .replace( 'uniform float roughness;', 'uniform vec3 specular;' ) + .replace( 'uniform float metalness;', 'uniform float glossiness;' ) + .replace( '#include ', specularMapParsFragmentChunk ) + .replace( '#include ', glossinessMapParsFragmentChunk ) + .replace( '#include ', specularMapFragmentChunk ) + .replace( '#include ', glossinessMapFragmentChunk ) + .replace( '#include ', lightPhysicalFragmentChunk ); + + }; + + Object.defineProperties( this, { + + specular: { + get: function () { + + return uniforms.specular.value; + + }, + set: function ( v ) { + + uniforms.specular.value = v; + + } + }, + + specularMap: { + get: function () { + + return uniforms.specularMap.value; + + }, + set: function ( v ) { + + uniforms.specularMap.value = v; + + if ( v ) { + + this.defines.USE_SPECULARMAP = ''; // USE_UV is set by the renderer for specular maps + + } else { + + delete this.defines.USE_SPECULARMAP; + + } + + } + }, + + glossiness: { + get: function () { + + return uniforms.glossiness.value; + + }, + set: function ( v ) { + + uniforms.glossiness.value = v; + + } + }, + + glossinessMap: { + get: function () { + + return uniforms.glossinessMap.value; + + }, + set: function ( v ) { + + uniforms.glossinessMap.value = v; + + if ( v ) { + + this.defines.USE_GLOSSINESSMAP = ''; + this.defines.USE_UV = ''; + + } else { + + delete this.defines.USE_GLOSSINESSMAP; + delete this.defines.USE_UV; + + } + + } + } + + } ); + + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; + + this.setValues( params ); + + } + + copy( source ) { + + super.copy( source ); + + this.specularMap = source.specularMap; + this.specular.copy( source.specular ); + this.glossinessMap = source.glossinessMap; + this.glossiness = source.glossiness; + delete this.metalness; + delete this.roughness; + delete this.metalnessMap; + delete this.roughnessMap; + return this; + + } + +} + + +class GLTFMaterialsPbrSpecularGlossinessExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS; + + this.specularGlossinessParams = [ + 'color', + 'map', + 'lightMap', + 'lightMapIntensity', + 'aoMap', + 'aoMapIntensity', + 'emissive', + 'emissiveIntensity', + 'emissiveMap', + 'bumpMap', + 'bumpScale', + 'normalMap', + 'normalMapType', + 'displacementMap', + 'displacementScale', + 'displacementBias', + 'specularMap', + 'specular', + 'glossinessMap', + 'glossiness', + 'alphaMap', + 'envMap', + 'envMapIntensity' + ]; + + } + + getMaterialType() { + + return GLTFMeshStandardSGMaterial; + + } + + extendParams( materialParams, materialDef, parser ) { + + const pbrSpecularGlossiness = materialDef.extensions[ this.name ]; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + const pending = []; + + if ( Array.isArray( pbrSpecularGlossiness.diffuseFactor ) ) { + + const array = pbrSpecularGlossiness.diffuseFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( pbrSpecularGlossiness.diffuseTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', pbrSpecularGlossiness.diffuseTexture, sRGBEncoding ) ); + + } + + materialParams.emissive = new Color( 0.0, 0.0, 0.0 ); + materialParams.glossiness = pbrSpecularGlossiness.glossinessFactor !== undefined ? pbrSpecularGlossiness.glossinessFactor : 1.0; + materialParams.specular = new Color( 1.0, 1.0, 1.0 ); + + if ( Array.isArray( pbrSpecularGlossiness.specularFactor ) ) { + + materialParams.specular.fromArray( pbrSpecularGlossiness.specularFactor ); + + } + + if ( pbrSpecularGlossiness.specularGlossinessTexture !== undefined ) { + + const specGlossMapDef = pbrSpecularGlossiness.specularGlossinessTexture; + pending.push( parser.assignTexture( materialParams, 'glossinessMap', specGlossMapDef ) ); + pending.push( parser.assignTexture( materialParams, 'specularMap', specGlossMapDef, sRGBEncoding ) ); + + } + + return Promise.all( pending ); + + } + + createMaterial( materialParams ) { + + const material = new GLTFMeshStandardSGMaterial( materialParams ); + material.fog = true; + + material.color = materialParams.color; + + material.map = materialParams.map === undefined ? null : materialParams.map; + + material.lightMap = null; + material.lightMapIntensity = 1.0; + + material.aoMap = materialParams.aoMap === undefined ? null : materialParams.aoMap; + material.aoMapIntensity = 1.0; + + material.emissive = materialParams.emissive; + material.emissiveIntensity = 1.0; + material.emissiveMap = materialParams.emissiveMap === undefined ? null : materialParams.emissiveMap; + + material.bumpMap = materialParams.bumpMap === undefined ? null : materialParams.bumpMap; + material.bumpScale = 1; + + material.normalMap = materialParams.normalMap === undefined ? null : materialParams.normalMap; + material.normalMapType = TangentSpaceNormalMap; + + if ( materialParams.normalScale ) material.normalScale = materialParams.normalScale; + + material.displacementMap = null; + material.displacementScale = 1; + material.displacementBias = 0; + + material.specularMap = materialParams.specularMap === undefined ? null : materialParams.specularMap; + material.specular = materialParams.specular; + + material.glossinessMap = materialParams.glossinessMap === undefined ? null : materialParams.glossinessMap; + material.glossiness = materialParams.glossiness; + + material.alphaMap = null; + + material.envMap = materialParams.envMap === undefined ? null : materialParams.envMap; + material.envMapIntensity = 1.0; + + return material; + + } + +} + +/** + * Mesh Quantization Extension + * + * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization + */ +class GLTFMeshQuantizationExtension { + + constructor() { + + this.name = EXTENSIONS.KHR_MESH_QUANTIZATION; + + } + +} + +/*********************************/ +/********** INTERPOLATION ********/ +/*********************************/ + +// Spline Interpolation +// Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#appendix-c-spline-interpolation +class GLTFCubicSplineInterpolant extends Interpolant { + + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + } + + copySampleValue_( index ) { + + // Copies a sample value to the result buffer. See description of glTF + // CUBICSPLINE values layout in interpolate_() function below. + + const result = this.resultBuffer, + values = this.sampleValues, + valueSize = this.valueSize, + offset = index * valueSize * 3 + valueSize; + + for ( let i = 0; i !== valueSize; i ++ ) { + + result[ i ] = values[ offset + i ]; + + } + + return result; + + } + +} + +GLTFCubicSplineInterpolant.prototype.beforeStart_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + +GLTFCubicSplineInterpolant.prototype.afterEnd_ = GLTFCubicSplineInterpolant.prototype.copySampleValue_; + +GLTFCubicSplineInterpolant.prototype.interpolate_ = function ( i1, t0, t, t1 ) { + + const result = this.resultBuffer; + const values = this.sampleValues; + const stride = this.valueSize; + + const stride2 = stride * 2; + const stride3 = stride * 3; + + const td = t1 - t0; + + const p = ( t - t0 ) / td; + const pp = p * p; + const ppp = pp * p; + + const offset1 = i1 * stride3; + const offset0 = offset1 - stride3; + + const s2 = - 2 * ppp + 3 * pp; + const s3 = ppp - pp; + const s0 = 1 - s2; + const s1 = s3 - pp + p; + + // Layout of keyframe output values for CUBICSPLINE animations: + // [ inTangent_1, splineVertex_1, outTangent_1, inTangent_2, splineVertex_2, ... ] + for ( let i = 0; i !== stride; i ++ ) { + + const p0 = values[ offset0 + i + stride ]; // splineVertex_k + const m0 = values[ offset0 + i + stride2 ] * td; // outTangent_k * (t_k+1 - t_k) + const p1 = values[ offset1 + i + stride ]; // splineVertex_k+1 + const m1 = values[ offset1 + i ] * td; // inTangent_k+1 * (t_k+1 - t_k) + + result[ i ] = s0 * p0 + s1 * m0 + s2 * p1 + s3 * m1; + + } + + return result; + +}; + +const _q = new Quaternion(); + +class GLTFCubicSplineQuaternionInterpolant extends GLTFCubicSplineInterpolant { + + interpolate_( i1, t0, t, t1 ) { + + const result = super.interpolate_( i1, t0, t, t1 ); + + _q.fromArray( result ).normalize().toArray( result ); + + return result; + + } + +} + + +/*********************************/ +/********** INTERNALS ************/ +/*********************************/ + +/* CONSTANTS */ + +const WEBGL_CONSTANTS = { + FLOAT: 5126, + //FLOAT_MAT2: 35674, + FLOAT_MAT3: 35675, + FLOAT_MAT4: 35676, + FLOAT_VEC2: 35664, + FLOAT_VEC3: 35665, + FLOAT_VEC4: 35666, + LINEAR: 9729, + REPEAT: 10497, + SAMPLER_2D: 35678, + POINTS: 0, + LINES: 1, + LINE_LOOP: 2, + LINE_STRIP: 3, + TRIANGLES: 4, + TRIANGLE_STRIP: 5, + TRIANGLE_FAN: 6, + UNSIGNED_BYTE: 5121, + UNSIGNED_SHORT: 5123 +}; + +const WEBGL_COMPONENT_TYPES = { + 5120: Int8Array, + 5121: Uint8Array, + 5122: Int16Array, + 5123: Uint16Array, + 5125: Uint32Array, + 5126: Float32Array +}; + +const WEBGL_FILTERS = { + 9728: NearestFilter, + 9729: LinearFilter, + 9984: NearestMipmapNearestFilter, + 9985: LinearMipmapNearestFilter, + 9986: NearestMipmapLinearFilter, + 9987: LinearMipmapLinearFilter +}; + +const WEBGL_WRAPPINGS = { + 33071: ClampToEdgeWrapping, + 33648: MirroredRepeatWrapping, + 10497: RepeatWrapping +}; + +const WEBGL_TYPE_SIZES = { + 'SCALAR': 1, + 'VEC2': 2, + 'VEC3': 3, + 'VEC4': 4, + 'MAT2': 4, + 'MAT3': 9, + 'MAT4': 16 +}; + +const ATTRIBUTES = { + POSITION: 'position', + NORMAL: 'normal', + TANGENT: 'tangent', + TEXCOORD_0: 'uv', + TEXCOORD_1: 'uv2', + COLOR_0: 'color', + WEIGHTS_0: 'skinWeight', + JOINTS_0: 'skinIndex', +}; + +const PATH_PROPERTIES = { + scale: 'scale', + translation: 'position', + rotation: 'quaternion', + weights: 'morphTargetInfluences' +}; + +const INTERPOLATION = { + CUBICSPLINE: undefined, // We use a custom interpolant (GLTFCubicSplineInterpolation) for CUBICSPLINE tracks. Each + // keyframe track will be initialized with a default interpolation type, then modified. + LINEAR: InterpolateLinear, + STEP: InterpolateDiscrete +}; + +const ALPHA_MODES = { + OPAQUE: 'OPAQUE', + MASK: 'MASK', + BLEND: 'BLEND' +}; + +/** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#default-material + */ +function createDefaultMaterial( cache ) { + + if ( cache[ 'DefaultMaterial' ] === undefined ) { + + cache[ 'DefaultMaterial' ] = new MeshStandardMaterial( { + color: 0xFFFFFF, + emissive: 0x000000, + metalness: 1, + roughness: 1, + transparent: false, + depthTest: true, + side: FrontSide + } ); + + } + + return cache[ 'DefaultMaterial' ]; + +} + +function addUnknownExtensionsToUserData( knownExtensions, object, objectDef ) { + + // Add unknown glTF extensions to an object's userData. + + for ( const name in objectDef.extensions ) { + + if ( knownExtensions[ name ] === undefined ) { + + object.userData.gltfExtensions = object.userData.gltfExtensions || {}; + object.userData.gltfExtensions[ name ] = objectDef.extensions[ name ]; + + } + + } + +} + +/** + * @param {Object3D|Material|BufferGeometry} object + * @param {GLTF.definition} gltfDef + */ +function assignExtrasToUserData( object, gltfDef ) { + + if ( gltfDef.extras !== undefined ) { + + if ( typeof gltfDef.extras === 'object' ) { + + Object.assign( object.userData, gltfDef.extras ); + + } else { + + console.warn( 'THREE.GLTFLoader: Ignoring primitive type .extras, ' + gltfDef.extras ); + + } + + } + +} + +/** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#morph-targets + * + * @param {BufferGeometry} geometry + * @param {Array} targets + * @param {GLTFParser} parser + * @return {Promise} + */ +function addMorphTargets( geometry, targets, parser ) { + + let hasMorphPosition = false; + let hasMorphNormal = false; + let hasMorphColor = false; + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; + + if ( target.POSITION !== undefined ) hasMorphPosition = true; + if ( target.NORMAL !== undefined ) hasMorphNormal = true; + if ( target.COLOR_0 !== undefined ) hasMorphColor = true; + + if ( hasMorphPosition && hasMorphNormal && hasMorphColor ) break; + + } + + if ( ! hasMorphPosition && ! hasMorphNormal && ! hasMorphColor ) return Promise.resolve( geometry ); + + const pendingPositionAccessors = []; + const pendingNormalAccessors = []; + const pendingColorAccessors = []; + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; + + if ( hasMorphPosition ) { + + const pendingAccessor = target.POSITION !== undefined + ? parser.getDependency( 'accessor', target.POSITION ) + : geometry.attributes.position; + + pendingPositionAccessors.push( pendingAccessor ); + + } + + if ( hasMorphNormal ) { + + const pendingAccessor = target.NORMAL !== undefined + ? parser.getDependency( 'accessor', target.NORMAL ) + : geometry.attributes.normal; + + pendingNormalAccessors.push( pendingAccessor ); + + } + + if ( hasMorphColor ) { + + const pendingAccessor = target.COLOR_0 !== undefined + ? parser.getDependency( 'accessor', target.COLOR_0 ) + : geometry.attributes.color; + + pendingColorAccessors.push( pendingAccessor ); + + } + + } + + return Promise.all( [ + Promise.all( pendingPositionAccessors ), + Promise.all( pendingNormalAccessors ), + Promise.all( pendingColorAccessors ) + ] ).then( function ( accessors ) { + + const morphPositions = accessors[ 0 ]; + const morphNormals = accessors[ 1 ]; + const morphColors = accessors[ 2 ]; + + if ( hasMorphPosition ) geometry.morphAttributes.position = morphPositions; + if ( hasMorphNormal ) geometry.morphAttributes.normal = morphNormals; + if ( hasMorphColor ) geometry.morphAttributes.color = morphColors; + geometry.morphTargetsRelative = true; + + return geometry; + + } ); + +} + +/** + * @param {Mesh} mesh + * @param {GLTF.Mesh} meshDef + */ +function updateMorphTargets( mesh, meshDef ) { + + mesh.updateMorphTargets(); + + if ( meshDef.weights !== undefined ) { + + for ( let i = 0, il = meshDef.weights.length; i < il; i ++ ) { + + mesh.morphTargetInfluences[ i ] = meshDef.weights[ i ]; + + } + + } + + // .extras has user-defined data, so check that .extras.targetNames is an array. + if ( meshDef.extras && Array.isArray( meshDef.extras.targetNames ) ) { + + const targetNames = meshDef.extras.targetNames; + + if ( mesh.morphTargetInfluences.length === targetNames.length ) { + + mesh.morphTargetDictionary = {}; + + for ( let i = 0, il = targetNames.length; i < il; i ++ ) { + + mesh.morphTargetDictionary[ targetNames[ i ] ] = i; + + } + + } else { + + console.warn( 'THREE.GLTFLoader: Invalid extras.targetNames length. Ignoring names.' ); + + } + + } + +} + +function createPrimitiveKey( primitiveDef ) { + + const dracoExtension = primitiveDef.extensions && primitiveDef.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ]; + let geometryKey; + + if ( dracoExtension ) { + + geometryKey = 'draco:' + dracoExtension.bufferView + + ':' + dracoExtension.indices + + ':' + createAttributesKey( dracoExtension.attributes ); + + } else { + + geometryKey = primitiveDef.indices + ':' + createAttributesKey( primitiveDef.attributes ) + ':' + primitiveDef.mode; + + } + + return geometryKey; + +} + +function createAttributesKey( attributes ) { + + let attributesKey = ''; + + const keys = Object.keys( attributes ).sort(); + + for ( let i = 0, il = keys.length; i < il; i ++ ) { + + attributesKey += keys[ i ] + ':' + attributes[ keys[ i ] ] + ';'; + + } + + return attributesKey; + +} + +function getNormalizedComponentScale( constructor ) { + + // Reference: + // https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization#encoding-quantized-data + + switch ( constructor ) { + + case Int8Array: + return 1 / 127; + + case Uint8Array: + return 1 / 255; + + case Int16Array: + return 1 / 32767; + + case Uint16Array: + return 1 / 65535; + + default: + throw new Error( 'THREE.GLTFLoader: Unsupported normalized accessor component type.' ); + + } + +} + +function getImageURIMimeType( uri ) { + + if ( uri.search( /\.jpe?g($|\?)/i ) > 0 || uri.search( /^data\:image\/jpeg/ ) === 0 ) return 'image/jpeg'; + if ( uri.search( /\.webp($|\?)/i ) > 0 || uri.search( /^data\:image\/webp/ ) === 0 ) return 'image/webp'; + + return 'image/png'; + +} + +/* GLTF PARSER */ + +class GLTFParser { + + constructor( json = {}, options = {} ) { + + this.json = json; + this.extensions = {}; + this.plugins = {}; + this.options = options; + + // loader object cache + this.cache = new GLTFRegistry(); + + // associations between Three.js objects and glTF elements + this.associations = new Map(); + + // BufferGeometry caching + this.primitiveCache = {}; + + // Object3D instance caches + this.meshCache = { refs: {}, uses: {} }; + this.cameraCache = { refs: {}, uses: {} }; + this.lightCache = { refs: {}, uses: {} }; + + this.sourceCache = {}; + this.textureCache = {}; + + // Track node names, to ensure no duplicates + this.nodeNamesUsed = {}; + + // Use an ImageBitmapLoader if imageBitmaps are supported. Moves much of the + // expensive work of uploading a texture to the GPU off the main thread. + if ( typeof createImageBitmap !== 'undefined' && /^((?!chrome|android).)*safari/i.test( navigator.userAgent ) === false ) { + + this.textureLoader = new ImageBitmapLoader( this.options.manager ); + + } else { + + this.textureLoader = new TextureLoader( this.options.manager ); + + } + + this.textureLoader.setCrossOrigin( this.options.crossOrigin ); + this.textureLoader.setRequestHeader( this.options.requestHeader ); + + this.fileLoader = new FileLoader( this.options.manager ); + this.fileLoader.setResponseType( 'arraybuffer' ); + + if ( this.options.crossOrigin === 'use-credentials' ) { + + this.fileLoader.setWithCredentials( true ); + + } + + } + + setExtensions( extensions ) { + + this.extensions = extensions; + + } + + setPlugins( plugins ) { + + this.plugins = plugins; + + } + + parse( onLoad, onError ) { + + const parser = this; + const json = this.json; + const extensions = this.extensions; + + // Clear the loader cache + this.cache.removeAll(); + + // Mark the special nodes/meshes in json for efficient parse + this._invokeAll( function ( ext ) { + + return ext._markDefs && ext._markDefs(); + + } ); + + Promise.all( this._invokeAll( function ( ext ) { + + return ext.beforeRoot && ext.beforeRoot(); + + } ) ).then( function () { + + return Promise.all( [ + + parser.getDependencies( 'scene' ), + parser.getDependencies( 'animation' ), + parser.getDependencies( 'camera' ), + + ] ); + + } ).then( function ( dependencies ) { + + const result = { + scene: dependencies[ 0 ][ json.scene || 0 ], + scenes: dependencies[ 0 ], + animations: dependencies[ 1 ], + cameras: dependencies[ 2 ], + asset: json.asset, + parser: parser, + userData: {} + }; + + addUnknownExtensionsToUserData( extensions, result, json ); + + assignExtrasToUserData( result, json ); + + Promise.all( parser._invokeAll( function ( ext ) { + + return ext.afterRoot && ext.afterRoot( result ); + + } ) ).then( function () { + + onLoad( result ); + + } ); + + } ).catch( onError ); + + } + + /** + * Marks the special nodes/meshes in json for efficient parse. + */ + _markDefs() { + + const nodeDefs = this.json.nodes || []; + const skinDefs = this.json.skins || []; + const meshDefs = this.json.meshes || []; + + // Nothing in the node definition indicates whether it is a Bone or an + // Object3D. Use the skins' joint references to mark bones. + for ( let skinIndex = 0, skinLength = skinDefs.length; skinIndex < skinLength; skinIndex ++ ) { + + const joints = skinDefs[ skinIndex ].joints; + + for ( let i = 0, il = joints.length; i < il; i ++ ) { + + nodeDefs[ joints[ i ] ].isBone = true; + + } + + } + + // Iterate over all nodes, marking references to shared resources, + // as well as skeleton joints. + for ( let nodeIndex = 0, nodeLength = nodeDefs.length; nodeIndex < nodeLength; nodeIndex ++ ) { + + const nodeDef = nodeDefs[ nodeIndex ]; + + if ( nodeDef.mesh !== undefined ) { + + this._addNodeRef( this.meshCache, nodeDef.mesh ); + + // Nothing in the mesh definition indicates whether it is + // a SkinnedMesh or Mesh. Use the node's mesh reference + // to mark SkinnedMesh if node has skin. + if ( nodeDef.skin !== undefined ) { + + meshDefs[ nodeDef.mesh ].isSkinnedMesh = true; + + } + + } + + if ( nodeDef.camera !== undefined ) { + + this._addNodeRef( this.cameraCache, nodeDef.camera ); + + } + + } + + } + + /** + * Counts references to shared node / Object3D resources. These resources + * can be reused, or "instantiated", at multiple nodes in the scene + * hierarchy. Mesh, Camera, and Light instances are instantiated and must + * be marked. Non-scenegraph resources (like Materials, Geometries, and + * Textures) can be reused directly and are not marked here. + * + * Example: CesiumMilkTruck sample model reuses "Wheel" meshes. + */ + _addNodeRef( cache, index ) { + + if ( index === undefined ) return; + + if ( cache.refs[ index ] === undefined ) { + + cache.refs[ index ] = cache.uses[ index ] = 0; + + } + + cache.refs[ index ] ++; + + } + + /** Returns a reference to a shared resource, cloning it if necessary. */ + _getNodeRef( cache, index, object ) { + + if ( cache.refs[ index ] <= 1 ) return object; + + const ref = object.clone(); + + // Propagates mappings to the cloned object, prevents mappings on the + // original object from being lost. + const updateMappings = ( original, clone ) => { + + const mappings = this.associations.get( original ); + if ( mappings != null ) { + + this.associations.set( clone, mappings ); + + } + + for ( const [ i, child ] of original.children.entries() ) { + + updateMappings( child, clone.children[ i ] ); + + } + + }; + + updateMappings( object, ref ); + + ref.name += '_instance_' + ( cache.uses[ index ] ++ ); + + return ref; + + } + + _invokeOne( func ) { + + const extensions = Object.values( this.plugins ); + extensions.push( this ); + + for ( let i = 0; i < extensions.length; i ++ ) { + + const result = func( extensions[ i ] ); + + if ( result ) return result; + + } + + return null; + + } + + _invokeAll( func ) { + + const extensions = Object.values( this.plugins ); + extensions.unshift( this ); + + const pending = []; + + for ( let i = 0; i < extensions.length; i ++ ) { + + const result = func( extensions[ i ] ); + + if ( result ) pending.push( result ); + + } + + return pending; + + } + + /** + * Requests the specified dependency asynchronously, with caching. + * @param {string} type + * @param {number} index + * @return {Promise} + */ + getDependency( type, index ) { + + const cacheKey = type + ':' + index; + let dependency = this.cache.get( cacheKey ); + + if ( ! dependency ) { + + switch ( type ) { + + case 'scene': + dependency = this.loadScene( index ); + break; + + case 'node': + dependency = this.loadNode( index ); + break; + + case 'mesh': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadMesh && ext.loadMesh( index ); + + } ); + break; + + case 'accessor': + dependency = this.loadAccessor( index ); + break; + + case 'bufferView': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadBufferView && ext.loadBufferView( index ); + + } ); + break; + + case 'buffer': + dependency = this.loadBuffer( index ); + break; + + case 'material': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadMaterial && ext.loadMaterial( index ); + + } ); + break; + + case 'texture': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadTexture && ext.loadTexture( index ); + + } ); + break; + + case 'skin': + dependency = this.loadSkin( index ); + break; + + case 'animation': + dependency = this._invokeOne( function ( ext ) { + + return ext.loadAnimation && ext.loadAnimation( index ); + + } ); + break; + + case 'camera': + dependency = this.loadCamera( index ); + break; + + default: + throw new Error( 'Unknown type: ' + type ); + + } + + this.cache.add( cacheKey, dependency ); + + } + + return dependency; + + } + + /** + * Requests all dependencies of the specified type asynchronously, with caching. + * @param {string} type + * @return {Promise>} + */ + getDependencies( type ) { + + let dependencies = this.cache.get( type ); + + if ( ! dependencies ) { + + const parser = this; + const defs = this.json[ type + ( type === 'mesh' ? 'es' : 's' ) ] || []; + + dependencies = Promise.all( defs.map( function ( def, index ) { + + return parser.getDependency( type, index ); + + } ) ); + + this.cache.add( type, dependencies ); + + } + + return dependencies; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferIndex + * @return {Promise} + */ + loadBuffer( bufferIndex ) { + + const bufferDef = this.json.buffers[ bufferIndex ]; + const loader = this.fileLoader; + + if ( bufferDef.type && bufferDef.type !== 'arraybuffer' ) { + + throw new Error( 'THREE.GLTFLoader: ' + bufferDef.type + ' buffer type is not supported.' ); + + } + + // If present, GLB container is required to be the first buffer. + if ( bufferDef.uri === undefined && bufferIndex === 0 ) { + + return Promise.resolve( this.extensions[ EXTENSIONS.KHR_BINARY_GLTF ].body ); + + } + + const options = this.options; + + return new Promise( function ( resolve, reject ) { + + loader.load( LoaderUtils.resolveURL( bufferDef.uri, options.path ), resolve, undefined, function () { + + reject( new Error( 'THREE.GLTFLoader: Failed to load buffer "' + bufferDef.uri + '".' ) ); + + } ); + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#buffers-and-buffer-views + * @param {number} bufferViewIndex + * @return {Promise} + */ + loadBufferView( bufferViewIndex ) { + + const bufferViewDef = this.json.bufferViews[ bufferViewIndex ]; + + return this.getDependency( 'buffer', bufferViewDef.buffer ).then( function ( buffer ) { + + const byteLength = bufferViewDef.byteLength || 0; + const byteOffset = bufferViewDef.byteOffset || 0; + return buffer.slice( byteOffset, byteOffset + byteLength ); + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#accessors + * @param {number} accessorIndex + * @return {Promise} + */ + loadAccessor( accessorIndex ) { + + const parser = this; + const json = this.json; + + const accessorDef = this.json.accessors[ accessorIndex ]; + + if ( accessorDef.bufferView === undefined && accessorDef.sparse === undefined ) { + + // Ignore empty accessors, which may be used to declare runtime + // information about attributes coming from another source (e.g. Draco + // compression extension). + return Promise.resolve( null ); + + } + + const pendingBufferViews = []; + + if ( accessorDef.bufferView !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.bufferView ) ); + + } else { + + pendingBufferViews.push( null ); + + } + + if ( accessorDef.sparse !== undefined ) { + + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.indices.bufferView ) ); + pendingBufferViews.push( this.getDependency( 'bufferView', accessorDef.sparse.values.bufferView ) ); + + } + + return Promise.all( pendingBufferViews ).then( function ( bufferViews ) { + + const bufferView = bufferViews[ 0 ]; + + const itemSize = WEBGL_TYPE_SIZES[ accessorDef.type ]; + const TypedArray = WEBGL_COMPONENT_TYPES[ accessorDef.componentType ]; + + // For VEC3: itemSize is 3, elementBytes is 4, itemBytes is 12. + const elementBytes = TypedArray.BYTES_PER_ELEMENT; + const itemBytes = elementBytes * itemSize; + const byteOffset = accessorDef.byteOffset || 0; + const byteStride = accessorDef.bufferView !== undefined ? json.bufferViews[ accessorDef.bufferView ].byteStride : undefined; + const normalized = accessorDef.normalized === true; + let array, bufferAttribute; + + // The buffer is not interleaved if the stride is the item size in bytes. + if ( byteStride && byteStride !== itemBytes ) { + + // Each "slice" of the buffer, as defined by 'count' elements of 'byteStride' bytes, gets its own InterleavedBuffer + // This makes sure that IBA.count reflects accessor.count properly + const ibSlice = Math.floor( byteOffset / byteStride ); + const ibCacheKey = 'InterleavedBuffer:' + accessorDef.bufferView + ':' + accessorDef.componentType + ':' + ibSlice + ':' + accessorDef.count; + let ib = parser.cache.get( ibCacheKey ); + + if ( ! ib ) { + + array = new TypedArray( bufferView, ibSlice * byteStride, accessorDef.count * byteStride / elementBytes ); + + // Integer parameters to IB/IBA are in array elements, not bytes. + ib = new InterleavedBuffer( array, byteStride / elementBytes ); + + parser.cache.add( ibCacheKey, ib ); + + } + + bufferAttribute = new InterleavedBufferAttribute( ib, itemSize, ( byteOffset % byteStride ) / elementBytes, normalized ); + + } else { + + if ( bufferView === null ) { + + array = new TypedArray( accessorDef.count * itemSize ); + + } else { + + array = new TypedArray( bufferView, byteOffset, accessorDef.count * itemSize ); + + } + + bufferAttribute = new BufferAttribute( array, itemSize, normalized ); + + } + + // https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors + if ( accessorDef.sparse !== undefined ) { + + const itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR; + const TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessorDef.sparse.indices.componentType ]; + + const byteOffsetIndices = accessorDef.sparse.indices.byteOffset || 0; + const byteOffsetValues = accessorDef.sparse.values.byteOffset || 0; + + const sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessorDef.sparse.count * itemSizeIndices ); + const sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessorDef.sparse.count * itemSize ); + + if ( bufferView !== null ) { + + // Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes. + bufferAttribute = new BufferAttribute( bufferAttribute.array.slice(), bufferAttribute.itemSize, bufferAttribute.normalized ); + + } + + for ( let i = 0, il = sparseIndices.length; i < il; i ++ ) { + + const index = sparseIndices[ i ]; + + bufferAttribute.setX( index, sparseValues[ i * itemSize ] ); + if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] ); + if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] ); + if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] ); + if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' ); + + } + + } + + return bufferAttribute; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#textures + * @param {number} textureIndex + * @return {Promise} + */ + loadTexture( textureIndex ) { + + const json = this.json; + const options = this.options; + const textureDef = json.textures[ textureIndex ]; + const sourceIndex = textureDef.source; + const sourceDef = json.images[ sourceIndex ]; + + let loader = this.textureLoader; + + if ( sourceDef.uri ) { + + const handler = options.manager.getHandler( sourceDef.uri ); + if ( handler !== null ) loader = handler; + + } + + return this.loadTextureImage( textureIndex, sourceIndex, loader ); + + } + + loadTextureImage( textureIndex, sourceIndex, loader ) { + + const parser = this; + const json = this.json; + + const textureDef = json.textures[ textureIndex ]; + const sourceDef = json.images[ sourceIndex ]; + + const cacheKey = ( sourceDef.uri || sourceDef.bufferView ) + ':' + textureDef.sampler; + + if ( this.textureCache[ cacheKey ] ) { + + // See https://github.com/mrdoob/three.js/issues/21559. + return this.textureCache[ cacheKey ]; + + } + + const promise = this.loadImageSource( sourceIndex, loader ).then( function ( texture ) { + + texture.flipY = false; + + if ( textureDef.name ) texture.name = textureDef.name; + + const samplers = json.samplers || {}; + const sampler = samplers[ textureDef.sampler ] || {}; + + texture.magFilter = WEBGL_FILTERS[ sampler.magFilter ] || LinearFilter; + texture.minFilter = WEBGL_FILTERS[ sampler.minFilter ] || LinearMipmapLinearFilter; + texture.wrapS = WEBGL_WRAPPINGS[ sampler.wrapS ] || RepeatWrapping; + texture.wrapT = WEBGL_WRAPPINGS[ sampler.wrapT ] || RepeatWrapping; + + parser.associations.set( texture, { textures: textureIndex } ); + + return texture; + + } ).catch( function () { + + return null; + + } ); + + this.textureCache[ cacheKey ] = promise; + + return promise; + + } + + loadImageSource( sourceIndex, loader ) { + + const parser = this; + const json = this.json; + const options = this.options; + + if ( this.sourceCache[ sourceIndex ] !== undefined ) { + + return this.sourceCache[ sourceIndex ].then( ( texture ) => texture.clone() ); + + } + + const sourceDef = json.images[ sourceIndex ]; + + const URL = self.URL || self.webkitURL; + + let sourceURI = sourceDef.uri || ''; + let isObjectURL = false; + + if ( sourceDef.bufferView !== undefined ) { + + // Load binary image data from bufferView, if provided. + + sourceURI = parser.getDependency( 'bufferView', sourceDef.bufferView ).then( function ( bufferView ) { + + isObjectURL = true; + const blob = new Blob( [ bufferView ], { type: sourceDef.mimeType } ); + sourceURI = URL.createObjectURL( blob ); + return sourceURI; + + } ); + + } else if ( sourceDef.uri === undefined ) { + + throw new Error( 'THREE.GLTFLoader: Image ' + sourceIndex + ' is missing URI and bufferView' ); + + } + + const promise = Promise.resolve( sourceURI ).then( function ( sourceURI ) { + + return new Promise( function ( resolve, reject ) { + + let onLoad = resolve; + + if ( loader.isImageBitmapLoader === true ) { + + onLoad = function ( imageBitmap ) { + + const texture = new Texture( imageBitmap ); + texture.needsUpdate = true; + + resolve( texture ); + + }; + + } + + loader.load( LoaderUtils.resolveURL( sourceURI, options.path ), onLoad, undefined, reject ); + + } ); + + } ).then( function ( texture ) { + + // Clean up resources and configure Texture. + + if ( isObjectURL === true ) { + + URL.revokeObjectURL( sourceURI ); + + } + + texture.userData.mimeType = sourceDef.mimeType || getImageURIMimeType( sourceDef.uri ); + + return texture; + + } ).catch( function ( error ) { + + console.error( 'THREE.GLTFLoader: Couldn\'t load texture', sourceURI ); + throw error; + + } ); + + this.sourceCache[ sourceIndex ] = promise; + return promise; + + } + + /** + * Asynchronously assigns a texture to the given material parameters. + * @param {Object} materialParams + * @param {string} mapName + * @param {Object} mapDef + * @return {Promise} + */ + assignTexture( materialParams, mapName, mapDef, encoding ) { + + const parser = this; + + return this.getDependency( 'texture', mapDef.index ).then( function ( texture ) { + + // Materials sample aoMap from UV set 1 and other maps from UV set 0 - this can't be configured + // However, we will copy UV set 0 to UV set 1 on demand for aoMap + if ( mapDef.texCoord !== undefined && mapDef.texCoord != 0 && ! ( mapName === 'aoMap' && mapDef.texCoord == 1 ) ) { + + console.warn( 'THREE.GLTFLoader: Custom UV set ' + mapDef.texCoord + ' for texture ' + mapName + ' not yet supported.' ); + + } + + if ( parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] ) { + + const transform = mapDef.extensions !== undefined ? mapDef.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ] : undefined; + + if ( transform ) { + + const gltfReference = parser.associations.get( texture ); + texture = parser.extensions[ EXTENSIONS.KHR_TEXTURE_TRANSFORM ].extendTexture( texture, transform ); + parser.associations.set( texture, gltfReference ); + + } + + } + + if ( encoding !== undefined ) { + + texture.encoding = encoding; + + } + + materialParams[ mapName ] = texture; + + return texture; + + } ); + + } + + /** + * Assigns final material to a Mesh, Line, or Points instance. The instance + * already has a material (generated from the glTF material options alone) + * but reuse of the same glTF material may require multiple threejs materials + * to accommodate different primitive types, defines, etc. New materials will + * be created if necessary, and reused from a cache. + * @param {Object3D} mesh Mesh, Line, or Points instance. + */ + assignFinalMaterial( mesh ) { + + const geometry = mesh.geometry; + let material = mesh.material; + + const useDerivativeTangents = geometry.attributes.tangent === undefined; + const useVertexColors = geometry.attributes.color !== undefined; + const useFlatShading = geometry.attributes.normal === undefined; + + if ( mesh.isPoints ) { + + const cacheKey = 'PointsMaterial:' + material.uuid; + + let pointsMaterial = this.cache.get( cacheKey ); + + if ( ! pointsMaterial ) { + + pointsMaterial = new PointsMaterial(); + Material.prototype.copy.call( pointsMaterial, material ); + pointsMaterial.color.copy( material.color ); + pointsMaterial.map = material.map; + pointsMaterial.sizeAttenuation = false; // glTF spec says points should be 1px + + this.cache.add( cacheKey, pointsMaterial ); + + } + + material = pointsMaterial; + + } else if ( mesh.isLine ) { + + const cacheKey = 'LineBasicMaterial:' + material.uuid; + + let lineMaterial = this.cache.get( cacheKey ); + + if ( ! lineMaterial ) { + + lineMaterial = new LineBasicMaterial(); + Material.prototype.copy.call( lineMaterial, material ); + lineMaterial.color.copy( material.color ); + + this.cache.add( cacheKey, lineMaterial ); + + } + + material = lineMaterial; + + } + + // Clone the material if it will be modified + if ( useDerivativeTangents || useVertexColors || useFlatShading ) { + + let cacheKey = 'ClonedMaterial:' + material.uuid + ':'; + + if ( material.isGLTFSpecularGlossinessMaterial ) cacheKey += 'specular-glossiness:'; + if ( useDerivativeTangents ) cacheKey += 'derivative-tangents:'; + if ( useVertexColors ) cacheKey += 'vertex-colors:'; + if ( useFlatShading ) cacheKey += 'flat-shading:'; + + let cachedMaterial = this.cache.get( cacheKey ); + + if ( ! cachedMaterial ) { + + cachedMaterial = material.clone(); + + if ( useVertexColors ) cachedMaterial.vertexColors = true; + if ( useFlatShading ) cachedMaterial.flatShading = true; + + if ( useDerivativeTangents ) { + + // https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995 + if ( cachedMaterial.normalScale ) cachedMaterial.normalScale.y *= - 1; + if ( cachedMaterial.clearcoatNormalScale ) cachedMaterial.clearcoatNormalScale.y *= - 1; + + } + + this.cache.add( cacheKey, cachedMaterial ); + + this.associations.set( cachedMaterial, this.associations.get( material ) ); + + } + + material = cachedMaterial; + + } + + // workarounds for mesh and geometry + + if ( material.aoMap && geometry.attributes.uv2 === undefined && geometry.attributes.uv !== undefined ) { + + geometry.setAttribute( 'uv2', geometry.attributes.uv ); + + } + + mesh.material = material; + + } + + getMaterialType( /* materialIndex */ ) { + + return MeshStandardMaterial; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#materials + * @param {number} materialIndex + * @return {Promise} + */ + loadMaterial( materialIndex ) { + + const parser = this; + const json = this.json; + const extensions = this.extensions; + const materialDef = json.materials[ materialIndex ]; + + let materialType; + const materialParams = {}; + const materialExtensions = materialDef.extensions || {}; + + const pending = []; + + if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ] ) { + + const sgExtension = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ]; + materialType = sgExtension.getMaterialType(); + pending.push( sgExtension.extendParams( materialParams, materialDef, parser ) ); + + } else if ( materialExtensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ] ) { + + const kmuExtension = extensions[ EXTENSIONS.KHR_MATERIALS_UNLIT ]; + materialType = kmuExtension.getMaterialType(); + pending.push( kmuExtension.extendParams( materialParams, materialDef, parser ) ); + + } else { + + // Specification: + // https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#metallic-roughness-material + + const metallicRoughness = materialDef.pbrMetallicRoughness || {}; + + materialParams.color = new Color( 1.0, 1.0, 1.0 ); + materialParams.opacity = 1.0; + + if ( Array.isArray( metallicRoughness.baseColorFactor ) ) { + + const array = metallicRoughness.baseColorFactor; + + materialParams.color.fromArray( array ); + materialParams.opacity = array[ 3 ]; + + } + + if ( metallicRoughness.baseColorTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'map', metallicRoughness.baseColorTexture, sRGBEncoding ) ); + + } + + materialParams.metalness = metallicRoughness.metallicFactor !== undefined ? metallicRoughness.metallicFactor : 1.0; + materialParams.roughness = metallicRoughness.roughnessFactor !== undefined ? metallicRoughness.roughnessFactor : 1.0; + + if ( metallicRoughness.metallicRoughnessTexture !== undefined ) { + + pending.push( parser.assignTexture( materialParams, 'metalnessMap', metallicRoughness.metallicRoughnessTexture ) ); + pending.push( parser.assignTexture( materialParams, 'roughnessMap', metallicRoughness.metallicRoughnessTexture ) ); + + } + + materialType = this._invokeOne( function ( ext ) { + + return ext.getMaterialType && ext.getMaterialType( materialIndex ); + + } ); + + pending.push( Promise.all( this._invokeAll( function ( ext ) { + + return ext.extendMaterialParams && ext.extendMaterialParams( materialIndex, materialParams ); + + } ) ) ); + + } + + if ( materialDef.doubleSided === true ) { + + materialParams.side = DoubleSide; + + } + + const alphaMode = materialDef.alphaMode || ALPHA_MODES.OPAQUE; + + if ( alphaMode === ALPHA_MODES.BLEND ) { + + materialParams.transparent = true; + + // See: https://github.com/mrdoob/three.js/issues/17706 + materialParams.depthWrite = false; + + } else { + + materialParams.transparent = false; + + if ( alphaMode === ALPHA_MODES.MASK ) { + + materialParams.alphaTest = materialDef.alphaCutoff !== undefined ? materialDef.alphaCutoff : 0.5; + + } + + } + + if ( materialDef.normalTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'normalMap', materialDef.normalTexture ) ); + + materialParams.normalScale = new Vector2( 1, 1 ); + + if ( materialDef.normalTexture.scale !== undefined ) { + + const scale = materialDef.normalTexture.scale; + + materialParams.normalScale.set( scale, scale ); + + } + + } + + if ( materialDef.occlusionTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'aoMap', materialDef.occlusionTexture ) ); + + if ( materialDef.occlusionTexture.strength !== undefined ) { + + materialParams.aoMapIntensity = materialDef.occlusionTexture.strength; + + } + + } + + if ( materialDef.emissiveFactor !== undefined && materialType !== MeshBasicMaterial ) { + + materialParams.emissive = new Color().fromArray( materialDef.emissiveFactor ); + + } + + if ( materialDef.emissiveTexture !== undefined && materialType !== MeshBasicMaterial ) { + + pending.push( parser.assignTexture( materialParams, 'emissiveMap', materialDef.emissiveTexture, sRGBEncoding ) ); + + } + + return Promise.all( pending ).then( function () { + + let material; + + if ( materialType === GLTFMeshStandardSGMaterial ) { + + material = extensions[ EXTENSIONS.KHR_MATERIALS_PBR_SPECULAR_GLOSSINESS ].createMaterial( materialParams ); + + } else { + + material = new materialType( materialParams ); + + } + + if ( materialDef.name ) material.name = materialDef.name; + + assignExtrasToUserData( material, materialDef ); + + parser.associations.set( material, { materials: materialIndex } ); + + if ( materialDef.extensions ) addUnknownExtensionsToUserData( extensions, material, materialDef ); + + return material; + + } ); + + } + + /** When Object3D instances are targeted by animation, they need unique names. */ + createUniqueName( originalName ) { + + const sanitizedName = PropertyBinding.sanitizeNodeName( originalName || '' ); + + let name = sanitizedName; + + for ( let i = 1; this.nodeNamesUsed[ name ]; ++ i ) { + + name = sanitizedName + '_' + i; + + } + + this.nodeNamesUsed[ name ] = true; + + return name; + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#geometry + * + * Creates BufferGeometries from primitives. + * + * @param {Array} primitives + * @return {Promise>} + */ + loadGeometries( primitives ) { + + const parser = this; + const extensions = this.extensions; + const cache = this.primitiveCache; + + function createDracoPrimitive( primitive ) { + + return extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] + .decodePrimitive( primitive, parser ) + .then( function ( geometry ) { + + return addPrimitiveAttributes( geometry, primitive, parser ); + + } ); + + } + + const pending = []; + + for ( let i = 0, il = primitives.length; i < il; i ++ ) { + + const primitive = primitives[ i ]; + const cacheKey = createPrimitiveKey( primitive ); + + // See if we've already created this geometry + const cached = cache[ cacheKey ]; + + if ( cached ) { + + // Use the cached geometry if it exists + pending.push( cached.promise ); + + } else { + + let geometryPromise; + + if ( primitive.extensions && primitive.extensions[ EXTENSIONS.KHR_DRACO_MESH_COMPRESSION ] ) { + + // Use DRACO geometry if available + geometryPromise = createDracoPrimitive( primitive ); + + } else { + + // Otherwise create a new geometry + geometryPromise = addPrimitiveAttributes( new BufferGeometry(), primitive, parser ); + + } + + // Cache this geometry + cache[ cacheKey ] = { primitive: primitive, promise: geometryPromise }; + + pending.push( geometryPromise ); + + } + + } + + return Promise.all( pending ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#meshes + * @param {number} meshIndex + * @return {Promise} + */ + loadMesh( meshIndex ) { + + const parser = this; + const json = this.json; + const extensions = this.extensions; + + const meshDef = json.meshes[ meshIndex ]; + const primitives = meshDef.primitives; + + const pending = []; + + for ( let i = 0, il = primitives.length; i < il; i ++ ) { + + const material = primitives[ i ].material === undefined + ? createDefaultMaterial( this.cache ) + : this.getDependency( 'material', primitives[ i ].material ); + + pending.push( material ); + + } + + pending.push( parser.loadGeometries( primitives ) ); + + return Promise.all( pending ).then( function ( results ) { + + const materials = results.slice( 0, results.length - 1 ); + const geometries = results[ results.length - 1 ]; + + const meshes = []; + + for ( let i = 0, il = geometries.length; i < il; i ++ ) { + + const geometry = geometries[ i ]; + const primitive = primitives[ i ]; + + // 1. create Mesh + + let mesh; + + const material = materials[ i ]; + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLES || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP || + primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN || + primitive.mode === undefined ) { + + // .isSkinnedMesh isn't in glTF spec. See ._markDefs() + mesh = meshDef.isSkinnedMesh === true + ? new SkinnedMesh( geometry, material ) + : new Mesh( geometry, material ); + + if ( mesh.isSkinnedMesh === true && ! mesh.geometry.attributes.skinWeight.normalized ) { + + // we normalize floating point skin weight array to fix malformed assets (see #15319) + // it's important to skip this for non-float32 data since normalizeSkinWeights assumes non-normalized inputs + mesh.normalizeSkinWeights(); + + } + + if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_STRIP ) { + + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleStripDrawMode ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.TRIANGLE_FAN ) { + + mesh.geometry = toTrianglesDrawMode( mesh.geometry, TriangleFanDrawMode ); + + } + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINES ) { + + mesh = new LineSegments( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_STRIP ) { + + mesh = new Line( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.LINE_LOOP ) { + + mesh = new LineLoop( geometry, material ); + + } else if ( primitive.mode === WEBGL_CONSTANTS.POINTS ) { + + mesh = new Points( geometry, material ); + + } else { + + throw new Error( 'THREE.GLTFLoader: Primitive mode unsupported: ' + primitive.mode ); + + } + + if ( Object.keys( mesh.geometry.morphAttributes ).length > 0 ) { + + updateMorphTargets( mesh, meshDef ); + + } + + mesh.name = parser.createUniqueName( meshDef.name || ( 'mesh_' + meshIndex ) ); + + assignExtrasToUserData( mesh, meshDef ); + + if ( primitive.extensions ) addUnknownExtensionsToUserData( extensions, mesh, primitive ); + + parser.assignFinalMaterial( mesh ); + + meshes.push( mesh ); + + } + + for ( let i = 0, il = meshes.length; i < il; i ++ ) { + + parser.associations.set( meshes[ i ], { + meshes: meshIndex, + primitives: i + } ); + + } + + if ( meshes.length === 1 ) { + + return meshes[ 0 ]; + + } + + const group = new Group(); + + parser.associations.set( group, { meshes: meshIndex } ); + + for ( let i = 0, il = meshes.length; i < il; i ++ ) { + + group.add( meshes[ i ] ); + + } + + return group; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#cameras + * @param {number} cameraIndex + * @return {Promise} + */ + loadCamera( cameraIndex ) { + + let camera; + const cameraDef = this.json.cameras[ cameraIndex ]; + const params = cameraDef[ cameraDef.type ]; + + if ( ! params ) { + + console.warn( 'THREE.GLTFLoader: Missing camera parameters.' ); + return; + + } + + if ( cameraDef.type === 'perspective' ) { + + camera = new PerspectiveCamera( MathUtils.radToDeg( params.yfov ), params.aspectRatio || 1, params.znear || 1, params.zfar || 2e6 ); + + } else if ( cameraDef.type === 'orthographic' ) { + + camera = new OrthographicCamera( - params.xmag, params.xmag, params.ymag, - params.ymag, params.znear, params.zfar ); + + } + + if ( cameraDef.name ) camera.name = this.createUniqueName( cameraDef.name ); + + assignExtrasToUserData( camera, cameraDef ); + + return Promise.resolve( camera ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins + * @param {number} skinIndex + * @return {Promise} + */ + loadSkin( skinIndex ) { + + const skinDef = this.json.skins[ skinIndex ]; + + const skinEntry = { joints: skinDef.joints }; + + if ( skinDef.inverseBindMatrices === undefined ) { + + return Promise.resolve( skinEntry ); + + } + + return this.getDependency( 'accessor', skinDef.inverseBindMatrices ).then( function ( accessor ) { + + skinEntry.inverseBindMatrices = accessor; + + return skinEntry; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations + * @param {number} animationIndex + * @return {Promise} + */ + loadAnimation( animationIndex ) { + + const json = this.json; + + const animationDef = json.animations[ animationIndex ]; + + const pendingNodes = []; + const pendingInputAccessors = []; + const pendingOutputAccessors = []; + const pendingSamplers = []; + const pendingTargets = []; + + for ( let i = 0, il = animationDef.channels.length; i < il; i ++ ) { + + const channel = animationDef.channels[ i ]; + const sampler = animationDef.samplers[ channel.sampler ]; + const target = channel.target; + const name = target.node !== undefined ? target.node : target.id; // NOTE: target.id is deprecated. + const input = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.input ] : sampler.input; + const output = animationDef.parameters !== undefined ? animationDef.parameters[ sampler.output ] : sampler.output; + + pendingNodes.push( this.getDependency( 'node', name ) ); + pendingInputAccessors.push( this.getDependency( 'accessor', input ) ); + pendingOutputAccessors.push( this.getDependency( 'accessor', output ) ); + pendingSamplers.push( sampler ); + pendingTargets.push( target ); + + } + + return Promise.all( [ + + Promise.all( pendingNodes ), + Promise.all( pendingInputAccessors ), + Promise.all( pendingOutputAccessors ), + Promise.all( pendingSamplers ), + Promise.all( pendingTargets ) + + ] ).then( function ( dependencies ) { + + const nodes = dependencies[ 0 ]; + const inputAccessors = dependencies[ 1 ]; + const outputAccessors = dependencies[ 2 ]; + const samplers = dependencies[ 3 ]; + const targets = dependencies[ 4 ]; + + const tracks = []; + + for ( let i = 0, il = nodes.length; i < il; i ++ ) { + + const node = nodes[ i ]; + const inputAccessor = inputAccessors[ i ]; + const outputAccessor = outputAccessors[ i ]; + const sampler = samplers[ i ]; + const target = targets[ i ]; + + if ( node === undefined ) continue; + + node.updateMatrix(); + node.matrixAutoUpdate = true; + + let TypedKeyframeTrack; + + switch ( PATH_PROPERTIES[ target.path ] ) { + + case PATH_PROPERTIES.weights: + + TypedKeyframeTrack = NumberKeyframeTrack; + break; + + case PATH_PROPERTIES.rotation: + + TypedKeyframeTrack = QuaternionKeyframeTrack; + break; + + case PATH_PROPERTIES.position: + case PATH_PROPERTIES.scale: + default: + + TypedKeyframeTrack = VectorKeyframeTrack; + break; + + } + + const targetName = node.name ? node.name : node.uuid; + + const interpolation = sampler.interpolation !== undefined ? INTERPOLATION[ sampler.interpolation ] : InterpolateLinear; + + const targetNames = []; + + if ( PATH_PROPERTIES[ target.path ] === PATH_PROPERTIES.weights ) { + + node.traverse( function ( object ) { + + if ( object.morphTargetInfluences ) { + + targetNames.push( object.name ? object.name : object.uuid ); + + } + + } ); + + } else { + + targetNames.push( targetName ); + + } + + let outputArray = outputAccessor.array; + + if ( outputAccessor.normalized ) { + + const scale = getNormalizedComponentScale( outputArray.constructor ); + const scaled = new Float32Array( outputArray.length ); + + for ( let j = 0, jl = outputArray.length; j < jl; j ++ ) { + + scaled[ j ] = outputArray[ j ] * scale; + + } + + outputArray = scaled; + + } + + for ( let j = 0, jl = targetNames.length; j < jl; j ++ ) { + + const track = new TypedKeyframeTrack( + targetNames[ j ] + '.' + PATH_PROPERTIES[ target.path ], + inputAccessor.array, + outputArray, + interpolation + ); + + // Override interpolation with custom factory method. + if ( sampler.interpolation === 'CUBICSPLINE' ) { + + track.createInterpolant = function InterpolantFactoryMethodGLTFCubicSpline( result ) { + + // A CUBICSPLINE keyframe in glTF has three output values for each input value, + // representing inTangent, splineVertex, and outTangent. As a result, track.getValueSize() + // must be divided by three to get the interpolant's sampleSize argument. + + const interpolantType = ( this instanceof QuaternionKeyframeTrack ) ? GLTFCubicSplineQuaternionInterpolant : GLTFCubicSplineInterpolant; + + return new interpolantType( this.times, this.values, this.getValueSize() / 3, result ); + + }; + + // Mark as CUBICSPLINE. `track.getInterpolation()` doesn't support custom interpolants. + track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline = true; + + } + + tracks.push( track ); + + } + + } + + const name = animationDef.name ? animationDef.name : 'animation_' + animationIndex; + + return new AnimationClip( name, undefined, tracks ); + + } ); + + } + + createNodeMesh( nodeIndex ) { + + const json = this.json; + const parser = this; + const nodeDef = json.nodes[ nodeIndex ]; + + if ( nodeDef.mesh === undefined ) return null; + + return parser.getDependency( 'mesh', nodeDef.mesh ).then( function ( mesh ) { + + const node = parser._getNodeRef( parser.meshCache, nodeDef.mesh, mesh ); + + // if weights are provided on the node, override weights on the mesh. + if ( nodeDef.weights !== undefined ) { + + node.traverse( function ( o ) { + + if ( ! o.isMesh ) return; + + for ( let i = 0, il = nodeDef.weights.length; i < il; i ++ ) { + + o.morphTargetInfluences[ i ] = nodeDef.weights[ i ]; + + } + + } ); + + } + + return node; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy + * @param {number} nodeIndex + * @return {Promise} + */ + loadNode( nodeIndex ) { + + const json = this.json; + const extensions = this.extensions; + const parser = this; + + const nodeDef = json.nodes[ nodeIndex ]; + + // reserve node's name before its dependencies, so the root has the intended name. + const nodeName = nodeDef.name ? parser.createUniqueName( nodeDef.name ) : ''; + + return ( function () { + + const pending = []; + + const meshPromise = parser._invokeOne( function ( ext ) { + + return ext.createNodeMesh && ext.createNodeMesh( nodeIndex ); + + } ); + + if ( meshPromise ) { + + pending.push( meshPromise ); + + } + + if ( nodeDef.camera !== undefined ) { + + pending.push( parser.getDependency( 'camera', nodeDef.camera ).then( function ( camera ) { + + return parser._getNodeRef( parser.cameraCache, nodeDef.camera, camera ); + + } ) ); + + } + + parser._invokeAll( function ( ext ) { + + return ext.createNodeAttachment && ext.createNodeAttachment( nodeIndex ); + + } ).forEach( function ( promise ) { + + pending.push( promise ); + + } ); + + return Promise.all( pending ); + + }() ).then( function ( objects ) { + + let node; + + // .isBone isn't in glTF spec. See ._markDefs + if ( nodeDef.isBone === true ) { + + node = new Bone(); + + } else if ( objects.length > 1 ) { + + node = new Group(); + + } else if ( objects.length === 1 ) { + + node = objects[ 0 ]; + + } else { + + node = new Object3D(); + + } + + if ( node !== objects[ 0 ] ) { + + for ( let i = 0, il = objects.length; i < il; i ++ ) { + + node.add( objects[ i ] ); + + } + + } + + if ( nodeDef.name ) { + + node.userData.name = nodeDef.name; + node.name = nodeName; + + } + + assignExtrasToUserData( node, nodeDef ); + + if ( nodeDef.extensions ) addUnknownExtensionsToUserData( extensions, node, nodeDef ); + + if ( nodeDef.matrix !== undefined ) { + + const matrix = new Matrix4(); + matrix.fromArray( nodeDef.matrix ); + node.applyMatrix4( matrix ); + + } else { + + if ( nodeDef.translation !== undefined ) { + + node.position.fromArray( nodeDef.translation ); + + } + + if ( nodeDef.rotation !== undefined ) { + + node.quaternion.fromArray( nodeDef.rotation ); + + } + + if ( nodeDef.scale !== undefined ) { + + node.scale.fromArray( nodeDef.scale ); + + } + + } + + if ( ! parser.associations.has( node ) ) { + + parser.associations.set( node, {} ); + + } + + parser.associations.get( node ).nodes = nodeIndex; + + return node; + + } ); + + } + + /** + * Specification: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes + * @param {number} sceneIndex + * @return {Promise} + */ + loadScene( sceneIndex ) { + + const json = this.json; + const extensions = this.extensions; + const sceneDef = this.json.scenes[ sceneIndex ]; + const parser = this; + + // Loader returns Group, not Scene. + // See: https://github.com/mrdoob/three.js/issues/18342#issuecomment-578981172 + const scene = new Group(); + if ( sceneDef.name ) scene.name = parser.createUniqueName( sceneDef.name ); + + assignExtrasToUserData( scene, sceneDef ); + + if ( sceneDef.extensions ) addUnknownExtensionsToUserData( extensions, scene, sceneDef ); + + const nodeIds = sceneDef.nodes || []; + + const pending = []; + + for ( let i = 0, il = nodeIds.length; i < il; i ++ ) { + + pending.push( buildNodeHierarchy( nodeIds[ i ], scene, json, parser ) ); + + } + + return Promise.all( pending ).then( function () { + + // Removes dangling associations, associations that reference a node that + // didn't make it into the scene. + const reduceAssociations = ( node ) => { + + const reducedAssociations = new Map(); + + for ( const [ key, value ] of parser.associations ) { + + if ( key instanceof Material || key instanceof Texture ) { + + reducedAssociations.set( key, value ); + + } + + } + + node.traverse( ( node ) => { + + const mappings = parser.associations.get( node ); + + if ( mappings != null ) { + + reducedAssociations.set( node, mappings ); + + } + + } ); + + return reducedAssociations; + + }; + + parser.associations = reduceAssociations( scene ); + + return scene; + + } ); + + } + +} + +function buildNodeHierarchy( nodeId, parentObject, json, parser ) { + + const nodeDef = json.nodes[ nodeId ]; + + return parser.getDependency( 'node', nodeId ).then( function ( node ) { + + if ( nodeDef.skin === undefined ) return node; + + // build skeleton here as well + + let skinEntry; + + return parser.getDependency( 'skin', nodeDef.skin ).then( function ( skin ) { + + skinEntry = skin; + + const pendingJoints = []; + + for ( let i = 0, il = skinEntry.joints.length; i < il; i ++ ) { + + pendingJoints.push( parser.getDependency( 'node', skinEntry.joints[ i ] ) ); + + } + + return Promise.all( pendingJoints ); + + } ).then( function ( jointNodes ) { + + node.traverse( function ( mesh ) { + + if ( ! mesh.isMesh ) return; + + const bones = []; + const boneInverses = []; + + for ( let j = 0, jl = jointNodes.length; j < jl; j ++ ) { + + const jointNode = jointNodes[ j ]; + + if ( jointNode ) { + + bones.push( jointNode ); + + const mat = new Matrix4(); + + if ( skinEntry.inverseBindMatrices !== undefined ) { + + mat.fromArray( skinEntry.inverseBindMatrices.array, j * 16 ); + + } + + boneInverses.push( mat ); + + } else { + + console.warn( 'THREE.GLTFLoader: Joint "%s" could not be found.', skinEntry.joints[ j ] ); + + } + + } + + mesh.bind( new Skeleton( bones, boneInverses ), mesh.matrixWorld ); + + } ); + + return node; + + } ); + + } ).then( function ( node ) { + + // build node hierachy + + parentObject.add( node ); + + const pending = []; + + if ( nodeDef.children ) { + + const children = nodeDef.children; + + for ( let i = 0, il = children.length; i < il; i ++ ) { + + const child = children[ i ]; + pending.push( buildNodeHierarchy( child, node, json, parser ) ); + + } + + } + + return Promise.all( pending ); + + } ); + +} + +/** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + */ +function computeBounds( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + + const box = new Box3(); + + if ( attributes.POSITION !== undefined ) { + + const accessor = parser.json.accessors[ attributes.POSITION ]; + + const min = accessor.min; + const max = accessor.max; + + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + box.set( + new Vector3( min[ 0 ], min[ 1 ], min[ 2 ] ), + new Vector3( max[ 0 ], max[ 1 ], max[ 2 ] ) + ); + + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + box.min.multiplyScalar( boxScale ); + box.max.multiplyScalar( boxScale ); + + } + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + return; + + } + + } else { + + return; + + } + + const targets = primitiveDef.targets; + + if ( targets !== undefined ) { + + const maxDisplacement = new Vector3(); + const vector = new Vector3(); + + for ( let i = 0, il = targets.length; i < il; i ++ ) { + + const target = targets[ i ]; + + if ( target.POSITION !== undefined ) { + + const accessor = parser.json.accessors[ target.POSITION ]; + const min = accessor.min; + const max = accessor.max; + + // glTF requires 'min' and 'max', but VRM (which extends glTF) currently ignores that requirement. + + if ( min !== undefined && max !== undefined ) { + + // we need to get max of absolute components because target weight is [-1,1] + vector.setX( Math.max( Math.abs( min[ 0 ] ), Math.abs( max[ 0 ] ) ) ); + vector.setY( Math.max( Math.abs( min[ 1 ] ), Math.abs( max[ 1 ] ) ) ); + vector.setZ( Math.max( Math.abs( min[ 2 ] ), Math.abs( max[ 2 ] ) ) ); + + + if ( accessor.normalized ) { + + const boxScale = getNormalizedComponentScale( WEBGL_COMPONENT_TYPES[ accessor.componentType ] ); + vector.multiplyScalar( boxScale ); + + } + + // Note: this assumes that the sum of all weights is at most 1. This isn't quite correct - it's more conservative + // to assume that each target can have a max weight of 1. However, for some use cases - notably, when morph targets + // are used to implement key-frame animations and as such only two are active at a time - this results in very large + // boxes. So for now we make a box that's sometimes a touch too small but is hopefully mostly of reasonable size. + maxDisplacement.max( vector ); + + } else { + + console.warn( 'THREE.GLTFLoader: Missing min/max properties for accessor POSITION.' ); + + } + + } + + } + + // As per comment above this box isn't conservative, but has a reasonable size for a very large number of morph targets. + box.expandByVector( maxDisplacement ); + + } + + geometry.boundingBox = box; + + const sphere = new Sphere(); + + box.getCenter( sphere.center ); + sphere.radius = box.min.distanceTo( box.max ) / 2; + + geometry.boundingSphere = sphere; + +} + +/** + * @param {BufferGeometry} geometry + * @param {GLTF.Primitive} primitiveDef + * @param {GLTFParser} parser + * @return {Promise} + */ +function addPrimitiveAttributes( geometry, primitiveDef, parser ) { + + const attributes = primitiveDef.attributes; + + const pending = []; + + function assignAttributeAccessor( accessorIndex, attributeName ) { + + return parser.getDependency( 'accessor', accessorIndex ) + .then( function ( accessor ) { + + geometry.setAttribute( attributeName, accessor ); + + } ); + + } + + for ( const gltfAttributeName in attributes ) { + + const threeAttributeName = ATTRIBUTES[ gltfAttributeName ] || gltfAttributeName.toLowerCase(); + + // Skip attributes already provided by e.g. Draco extension. + if ( threeAttributeName in geometry.attributes ) continue; + + pending.push( assignAttributeAccessor( attributes[ gltfAttributeName ], threeAttributeName ) ); + + } + + if ( primitiveDef.indices !== undefined && ! geometry.index ) { + + const accessor = parser.getDependency( 'accessor', primitiveDef.indices ).then( function ( accessor ) { + + geometry.setIndex( accessor ); + + } ); + + pending.push( accessor ); + + } + + assignExtrasToUserData( geometry, primitiveDef ); + + computeBounds( geometry, primitiveDef, parser ); + + return Promise.all( pending ).then( function () { + + return primitiveDef.targets !== undefined + ? addMorphTargets( geometry, primitiveDef.targets, parser ) + : geometry; + + } ); + +} + +/** + * @param {BufferGeometry} geometry + * @param {Number} drawMode + * @return {BufferGeometry} + */ +function toTrianglesDrawMode( geometry, drawMode ) { + + let index = geometry.getIndex(); + + // generate index if not present + + if ( index === null ) { + + const indices = []; + + const position = geometry.getAttribute( 'position' ); + + if ( position !== undefined ) { + + for ( let i = 0; i < position.count; i ++ ) { + + indices.push( i ); + + } + + geometry.setIndex( indices ); + index = geometry.getIndex(); + + } else { + + console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Undefined position attribute. Processing not possible.' ); + return geometry; + + } + + } + + // + + const numberOfTriangles = index.count - 2; + const newIndices = []; + + if ( drawMode === TriangleFanDrawMode ) { + + // gl.TRIANGLE_FAN + + for ( let i = 1; i <= numberOfTriangles; i ++ ) { + + newIndices.push( index.getX( 0 ) ); + newIndices.push( index.getX( i ) ); + newIndices.push( index.getX( i + 1 ) ); + + } + + } else { + + // gl.TRIANGLE_STRIP + + for ( let i = 0; i < numberOfTriangles; i ++ ) { + + if ( i % 2 === 0 ) { + + newIndices.push( index.getX( i ) ); + newIndices.push( index.getX( i + 1 ) ); + newIndices.push( index.getX( i + 2 ) ); + + + } else { + + newIndices.push( index.getX( i + 2 ) ); + newIndices.push( index.getX( i + 1 ) ); + newIndices.push( index.getX( i ) ); + + } + + } + + } + + if ( ( newIndices.length / 3 ) !== numberOfTriangles ) { + + console.error( 'THREE.GLTFLoader.toTrianglesDrawMode(): Unable to generate correct amount of triangles.' ); + + } + + // build final geometry + + const newGeometry = geometry.clone(); + newGeometry.setIndex( newIndices ); + + return newGeometry; + +} + +export { GLTFLoader }; diff --git a/jsm/loaders/HDRCubeTextureLoader.js b/jsm/loaders/HDRCubeTextureLoader.js new file mode 100644 index 0000000..643f664 --- /dev/null +++ b/jsm/loaders/HDRCubeTextureLoader.js @@ -0,0 +1,128 @@ +import { + CubeTexture, + DataTexture, + FileLoader, + FloatType, + HalfFloatType, + LinearEncoding, + LinearFilter, + Loader +} from 'three'; +import { RGBELoader } from '../loaders/RGBELoader.js'; + +class HDRCubeTextureLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.hdrLoader = new RGBELoader(); + this.type = HalfFloatType; + + } + + load( urls, onLoad, onProgress, onError ) { + + if ( ! Array.isArray( urls ) ) { + + console.warn( 'THREE.HDRCubeTextureLoader signature has changed. Use .setDataType() instead.' ); + + this.setDataType( urls ); + + urls = onLoad; + onLoad = onProgress; + onProgress = onError; + onError = arguments[ 4 ]; + + } + + const texture = new CubeTexture(); + + texture.type = this.type; + + switch ( texture.type ) { + + case FloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + break; + + case HalfFloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + break; + + } + + const scope = this; + + let loaded = 0; + + function loadHDRData( i, onLoad, onProgress, onError ) { + + new FileLoader( scope.manager ) + .setPath( scope.path ) + .setResponseType( 'arraybuffer' ) + .setWithCredentials( scope.withCredentials ) + .load( urls[ i ], function ( buffer ) { + + loaded ++; + + const texData = scope.hdrLoader.parse( buffer ); + + if ( ! texData ) return; + + if ( texData.data !== undefined ) { + + const dataTexture = new DataTexture( texData.data, texData.width, texData.height ); + + dataTexture.type = texture.type; + dataTexture.encoding = texture.encoding; + dataTexture.format = texture.format; + dataTexture.minFilter = texture.minFilter; + dataTexture.magFilter = texture.magFilter; + dataTexture.generateMipmaps = texture.generateMipmaps; + + texture.images[ i ] = dataTexture; + + } + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + if ( onLoad ) onLoad( texture ); + + } + + }, onProgress, onError ); + + } + + for ( let i = 0; i < urls.length; i ++ ) { + + loadHDRData( i, onLoad, onProgress, onError ); + + } + + return texture; + + } + + setDataType( value ) { + + this.type = value; + this.hdrLoader.setDataType( value ); + + return this; + + } + +} + +export { HDRCubeTextureLoader }; diff --git a/jsm/loaders/IFCLoader.js b/jsm/loaders/IFCLoader.js new file mode 100644 index 0000000..b79ac0b --- /dev/null +++ b/jsm/loaders/IFCLoader.js @@ -0,0 +1,2431 @@ +import { + IFCRELAGGREGATES, + IFCRELCONTAINEDINSPATIALSTRUCTURE, + IFCRELDEFINESBYPROPERTIES, + IFCRELASSOCIATESMATERIAL, + IFCRELDEFINESBYTYPE, + IFCPROJECT, + IfcAPI +} from './ifc/web-ifc-api.js'; +import { + BufferAttribute, + Mesh, + Matrix4, + BufferGeometry, + Color, + MeshLambertMaterial, + DoubleSide, + Loader, + FileLoader +} from 'three'; +import { mergeBufferGeometries } from '../utils/BufferGeometryUtils.js'; + +const IdAttrName = 'expressID'; +const merge = ( geoms, createGroups = false ) => { + + return mergeBufferGeometries( geoms, createGroups ); + +}; + +const newFloatAttr = ( data, size ) => { + + return new BufferAttribute( new Float32Array( data ), size ); + +}; + +const newIntAttr = ( data, size ) => { + + return new BufferAttribute( new Uint32Array( data ), size ); + +}; + +const DEFAULT = 'default'; +const PropsNames = { + aggregates: { + name: IFCRELAGGREGATES, + relating: 'RelatingObject', + related: 'RelatedObjects', + key: 'children' + }, + spatial: { + name: IFCRELCONTAINEDINSPATIALSTRUCTURE, + relating: 'RelatingStructure', + related: 'RelatedElements', + key: 'children' + }, + psets: { + name: IFCRELDEFINESBYPROPERTIES, + relating: 'RelatingPropertyDefinition', + related: 'RelatedObjects', + key: 'hasPsets' + }, + materials: { + name: IFCRELASSOCIATESMATERIAL, + relating: 'RelatingMaterial', + related: 'RelatedObjects', + key: 'hasMaterial' + }, + type: { + name: IFCRELDEFINESBYTYPE, + relating: 'RelatingType', + related: 'RelatedObjects', + key: 'hasType' + } +}; + +class IFCParser { + + constructor( state, BVH ) { + + this.state = state; + this.BVH = BVH; + this.loadedModels = 0; + this.currentWebIfcID = - 1; + this.currentModelID = - 1; + + } + + async parse( buffer ) { + + if ( this.state.api.wasmModule === undefined ) + await this.state.api.Init(); + this.newIfcModel( buffer ); + this.loadedModels ++; + return this.loadAllGeometry(); + + } + + newIfcModel( buffer ) { + + const data = new Uint8Array( buffer ); + this.currentWebIfcID = this.state.api.OpenModel( data, this.state.webIfcSettings ); + this.currentModelID = this.state.useJSON ? this.loadedModels : this.currentWebIfcID; + this.state.models[ this.currentModelID ] = { + modelID: this.currentModelID, + mesh: {}, + items: {}, + types: {}, + jsonData: {} + }; + + } + + loadAllGeometry() { + + this.saveAllPlacedGeometriesByMaterial(); + return this.generateAllGeometriesByMaterial(); + + } + + generateAllGeometriesByMaterial() { + + const { geometry, materials } = this.getGeometryAndMaterials(); + this.BVH.applyThreeMeshBVH( geometry ); + const mesh = new Mesh( geometry, materials ); + mesh.modelID = this.currentModelID; + this.state.models[ this.currentModelID ].mesh = mesh; + return mesh; + + } + + getGeometryAndMaterials() { + + const items = this.state.models[ this.currentModelID ].items; + const mergedByMaterial = []; + const materials = []; + for ( const materialID in items ) { + + materials.push( items[ materialID ].material ); + const geometries = Object.values( items[ materialID ].geometries ); + mergedByMaterial.push( merge( geometries ) ); + + } + + const geometry = merge( mergedByMaterial, true ); + return { + geometry, + materials + }; + + } + + saveAllPlacedGeometriesByMaterial() { + + const flatMeshes = this.state.api.LoadAllGeometry( this.currentWebIfcID ); + for ( let i = 0; i < flatMeshes.size(); i ++ ) { + + const flatMesh = flatMeshes.get( i ); + const placedGeom = flatMesh.geometries; + for ( let j = 0; j < placedGeom.size(); j ++ ) { + + this.savePlacedGeometry( placedGeom.get( j ), flatMesh.expressID ); + + } + + } + + } + + savePlacedGeometry( placedGeometry, id ) { + + const geometry = this.getBufferGeometry( placedGeometry ); + geometry.computeVertexNormals(); + const matrix = this.getMeshMatrix( placedGeometry.flatTransformation ); + geometry.applyMatrix4( matrix ); + this.saveGeometryByMaterial( geometry, placedGeometry, id ); + + } + + getBufferGeometry( placed ) { + + const geometry = this.state.api.GetGeometry( this.currentWebIfcID, placed.geometryExpressID ); + const vertexData = this.getVertices( geometry ); + const indices = this.getIndices( geometry ); + const { vertices, normals } = this.extractVertexData( vertexData ); + return this.ifcGeomToBufferGeom( vertices, normals, indices ); + + } + + getVertices( geometry ) { + + const vData = geometry.GetVertexData(); + const vDataSize = geometry.GetVertexDataSize(); + return this.state.api.GetVertexArray( vData, vDataSize ); + + } + + getIndices( geometry ) { + + const iData = geometry.GetIndexData(); + const iDataSize = geometry.GetIndexDataSize(); + return this.state.api.GetIndexArray( iData, iDataSize ); + + } + + getMeshMatrix( matrix ) { + + const mat = new Matrix4(); + mat.fromArray( matrix ); + return mat; + + } + + ifcGeomToBufferGeom( vertices, normals, indexData ) { + + const geometry = new BufferGeometry(); + geometry.setAttribute( 'position', newFloatAttr( vertices, 3 ) ); + geometry.setAttribute( 'normal', newFloatAttr( normals, 3 ) ); + geometry.setIndex( new BufferAttribute( indexData, 1 ) ); + return geometry; + + } + + extractVertexData( vertexData ) { + + const vertices = []; + const normals = []; + let isNormalData = false; + for ( let i = 0; i < vertexData.length; i ++ ) { + + isNormalData ? normals.push( vertexData[ i ] ) : vertices.push( vertexData[ i ] ); + if ( ( i + 1 ) % 3 == 0 ) + isNormalData = ! isNormalData; + + } + + return { + vertices, + normals + }; + + } + + saveGeometryByMaterial( geom, placedGeom, id ) { + + const color = placedGeom.color; + const colorID = `${color.x}${color.y}${color.z}${color.w}`; + this.storeGeometryAttribute( id, geom ); + this.createMaterial( colorID, color ); + const item = this.state.models[ this.currentModelID ].items[ colorID ]; + const currentGeom = item.geometries[ id ]; + if ( ! currentGeom ) + return ( item.geometries[ id ] = geom ); + const merged = merge( [ currentGeom, geom ] ); + item.geometries[ id ] = merged; + + } + + storeGeometryAttribute( id, geometry ) { + + const size = geometry.attributes.position.count; + const idAttribute = new Array( size ).fill( id ); + geometry.setAttribute( IdAttrName, newIntAttr( idAttribute, 1 ) ); + + } + + createMaterial( colorID, color ) { + + const items = this.state.models[ this.currentModelID ].items; + if ( items[ colorID ] ) + return; + const col = new Color( color.x, color.y, color.z ); + const newMaterial = new MeshLambertMaterial( { + color: col, + side: DoubleSide + } ); + newMaterial.transparent = color.w !== 1; + if ( newMaterial.transparent ) + newMaterial.opacity = color.w; + items[ colorID ] = { + material: newMaterial, + geometries: {} + }; + + } + +} + +class SubsetManager { + + constructor( state, BVH ) { + + this.selected = {}; + this.state = state; + this.BVH = BVH; + + } + + getSubset( modelID, material ) { + + const currentMat = this.matIDNoConfig( modelID, material ); + if ( ! this.selected[ currentMat ] ) + return null; + return this.selected[ currentMat ].mesh; + + } + + removeSubset( modelID, parent, material ) { + + const currentMat = this.matIDNoConfig( modelID, material ); + if ( ! this.selected[ currentMat ] ) + return; + if ( parent ) + parent.remove( this.selected[ currentMat ].mesh ); + delete this.selected[ currentMat ]; + + } + + createSubset( config ) { + + if ( ! this.isConfigValid( config ) ) + return; + if ( this.isPreviousSelection( config ) ) + return; + if ( this.isEasySelection( config ) ) + return this.addToPreviousSelection( config ); + this.updatePreviousSelection( config.scene, config ); + return this.createSelectionInScene( config ); + + } + + createSelectionInScene( config ) { + + const filtered = this.filter( config ); + const { geomsByMaterial, materials } = this.getGeomAndMat( filtered ); + const isDefMaterial = this.isDefaultMat( config ); + const geometry = this.getMergedGeometry( geomsByMaterial, isDefMaterial ); + const mats = isDefMaterial ? materials : config.material; + this.BVH.applyThreeMeshBVH( geometry ); + const mesh = new Mesh( geometry, mats ); + this.selected[ this.matID( config ) ].mesh = mesh; + mesh.modelID = config.modelID; + config.scene.add( mesh ); + return mesh; + + } + + getMergedGeometry( geomsByMaterial, hasDefaultMaterial ) { + + return geomsByMaterial.length > 0 + ? merge( geomsByMaterial, hasDefaultMaterial ) + : new BufferGeometry(); + + } + + isConfigValid( config ) { + + return ( this.isValid( config.scene ) && + this.isValid( config.modelID ) && + this.isValid( config.ids ) && + this.isValid( config.removePrevious ) ); + + } + + isValid( item ) { + + return item != undefined && item != null; + + } + + getGeomAndMat( filtered ) { + + const geomsByMaterial = []; + const materials = []; + for ( const matID in filtered ) { + + const geoms = Object.values( filtered[ matID ].geometries ); + if ( ! geoms.length ) + continue; + materials.push( filtered[ matID ].material ); + if ( geoms.length > 1 ) + geomsByMaterial.push( merge( geoms ) ); + else + geomsByMaterial.push( ...geoms ); + + } + + return { + geomsByMaterial, + materials + }; + + } + + updatePreviousSelection( parent, config ) { + + const previous = this.selected[ this.matID( config ) ]; + if ( ! previous ) + return this.newSelectionGroup( config ); + parent.remove( previous.mesh ); + config.removePrevious + ? ( previous.ids = new Set( config.ids ) ) + : config.ids.forEach( ( id ) => previous.ids.add( id ) ); + + } + + newSelectionGroup( config ) { + + this.selected[ this.matID( config ) ] = { + ids: new Set( config.ids ), + mesh: {} + }; + + } + + isPreviousSelection( config ) { + + if ( ! this.selected[ this.matID( config ) ] ) + return false; + if ( this.containsIds( config ) ) + return true; + const previousIds = this.selected[ this.matID( config ) ].ids; + return JSON.stringify( config.ids ) === JSON.stringify( previousIds ); + + } + + containsIds( config ) { + + const newIds = config.ids; + const previous = Array.from( this.selected[ this.matID( config ) ].ids ); + return newIds.every( ( i => v => ( i = previous.indexOf( v, i ) + 1 ) )( 0 ) ); + + } + + addToPreviousSelection( config ) { + + const previous = this.selected[ this.matID( config ) ]; + const filtered = this.filter( config ); + const geometries = Object.values( filtered ).map( ( i ) => Object.values( i.geometries ) ).flat(); + const previousGeom = previous.mesh.geometry; + previous.mesh.geometry = merge( [ previousGeom, ...geometries ] ); + config.ids.forEach( ( id ) => previous.ids.add( id ) ); + + } + + filter( config ) { + + const ids = this.selected[ this.matID( config ) ].ids; + const items = this.state.models[ config.modelID ].items; + const filtered = {}; + for ( const matID in items ) { + + filtered[ matID ] = { + material: items[ matID ].material, + geometries: this.filterGeometries( ids, items[ matID ].geometries ) + }; + + } + + return filtered; + + } + + filterGeometries( selectedIDs, geometries ) { + + const ids = Array.from( selectedIDs ); + return Object.keys( geometries ) + .filter( ( key ) => ids.includes( parseInt( key, 10 ) ) ) + .reduce( ( obj, key ) => { + + return { + ...obj, + [ key ]: geometries[ key ] + }; + + }, {} ); + + } + + isEasySelection( config ) { + + const matID = this.matID( config ); + if ( ! config.removePrevious && ! this.isDefaultMat( config ) && this.selected[ matID ] ) + return true; + + } + + isDefaultMat( config ) { + + return this.matIDNoConfig( config.modelID ) === this.matID( config ); + + } + + matID( config ) { + + let name; + if ( ! config.material ) + name = DEFAULT; + else + name = config.material.uuid || DEFAULT; + return name.concat( ' - ' ).concat( config.modelID.toString() ); + + } + + matIDNoConfig( modelID, material ) { + + let name = DEFAULT; + if ( material ) + name = material.uuid; + return name.concat( ' - ' ).concat( modelID.toString() ); + + } + +} + +const IfcElements = { + 103090709: 'IFCPROJECT', + 4097777520: 'IFCSITE', + 4031249490: 'IFCBUILDING', + 3124254112: 'IFCBUILDINGSTOREY', + 3856911033: 'IFCSPACE', + 1674181508: 'IFCANNOTATION', + 25142252: 'IFCCONTROLLER', + 32344328: 'IFCBOILER', + 76236018: 'IFCLAMP', + 90941305: 'IFCPUMP', + 177149247: 'IFCAIRTERMINALBOX', + 182646315: 'IFCFLOWINSTRUMENT', + 263784265: 'IFCFURNISHINGELEMENT', + 264262732: 'IFCELECTRICGENERATOR', + 277319702: 'IFCAUDIOVISUALAPPLIANCE', + 310824031: 'IFCPIPEFITTING', + 331165859: 'IFCSTAIR', + 342316401: 'IFCDUCTFITTING', + 377706215: 'IFCMECHANICALFASTENER', + 395920057: 'IFCDOOR', + 402227799: 'IFCELECTRICMOTOR', + 413509423: 'IFCSYSTEMFURNITUREELEMENT', + 484807127: 'IFCEVAPORATOR', + 486154966: 'IFCWINDOWSTANDARDCASE', + 629592764: 'IFCLIGHTFIXTURE', + 630975310: 'IFCUNITARYCONTROLELEMENT', + 635142910: 'IFCCABLECARRIERFITTING', + 639361253: 'IFCCOIL', + 647756555: 'IFCFASTENER', + 707683696: 'IFCFLOWSTORAGEDEVICE', + 738039164: 'IFCPROTECTIVEDEVICE', + 753842376: 'IFCBEAM', + 812556717: 'IFCTANK', + 819412036: 'IFCFILTER', + 843113511: 'IFCCOLUMN', + 862014818: 'IFCELECTRICDISTRIBUTIONBOARD', + 900683007: 'IFCFOOTING', + 905975707: 'IFCCOLUMNSTANDARDCASE', + 926996030: 'IFCVOIDINGFEATURE', + 979691226: 'IFCREINFORCINGBAR', + 987401354: 'IFCFLOWSEGMENT', + 1003880860: 'IFCELECTRICTIMECONTROL', + 1051757585: 'IFCCABLEFITTING', + 1052013943: 'IFCDISTRIBUTIONCHAMBERELEMENT', + 1062813311: 'IFCDISTRIBUTIONCONTROLELEMENT', + 1073191201: 'IFCMEMBER', + 1095909175: 'IFCBUILDINGELEMENTPROXY', + 1156407060: 'IFCPLATESTANDARDCASE', + 1162798199: 'IFCSWITCHINGDEVICE', + 1329646415: 'IFCSHADINGDEVICE', + 1335981549: 'IFCDISCRETEACCESSORY', + 1360408905: 'IFCDUCTSILENCER', + 1404847402: 'IFCSTACKTERMINAL', + 1426591983: 'IFCFIRESUPPRESSIONTERMINAL', + 1437502449: 'IFCMEDICALDEVICE', + 1509553395: 'IFCFURNITURE', + 1529196076: 'IFCSLAB', + 1620046519: 'IFCTRANSPORTELEMENT', + 1634111441: 'IFCAIRTERMINAL', + 1658829314: 'IFCENERGYCONVERSIONDEVICE', + 1677625105: 'IFCCIVILELEMENT', + 1687234759: 'IFCPILE', + 1904799276: 'IFCELECTRICAPPLIANCE', + 1911478936: 'IFCMEMBERSTANDARDCASE', + 1945004755: 'IFCDISTRIBUTIONELEMENT', + 1973544240: 'IFCCOVERING', + 1999602285: 'IFCSPACEHEATER', + 2016517767: 'IFCROOF', + 2056796094: 'IFCAIRTOAIRHEATRECOVERY', + 2058353004: 'IFCFLOWCONTROLLER', + 2068733104: 'IFCHUMIDIFIER', + 2176052936: 'IFCJUNCTIONBOX', + 2188021234: 'IFCFLOWMETER', + 2223149337: 'IFCFLOWTERMINAL', + 2262370178: 'IFCRAILING', + 2272882330: 'IFCCONDENSER', + 2295281155: 'IFCPROTECTIVEDEVICETRIPPINGUNIT', + 2320036040: 'IFCREINFORCINGMESH', + 2347447852: 'IFCTENDONANCHOR', + 2391383451: 'IFCVIBRATIONISOLATOR', + 2391406946: 'IFCWALL', + 2474470126: 'IFCMOTORCONNECTION', + 2769231204: 'IFCVIRTUALELEMENT', + 2814081492: 'IFCENGINE', + 2906023776: 'IFCBEAMSTANDARDCASE', + 2938176219: 'IFCBURNER', + 2979338954: 'IFCBUILDINGELEMENTPART', + 3024970846: 'IFCRAMP', + 3026737570: 'IFCTUBEBUNDLE', + 3027962421: 'IFCSLABSTANDARDCASE', + 3040386961: 'IFCDISTRIBUTIONFLOWELEMENT', + 3053780830: 'IFCSANITARYTERMINAL', + 3079942009: 'IFCOPENINGSTANDARDCASE', + 3087945054: 'IFCALARM', + 3101698114: 'IFCSURFACEFEATURE', + 3127900445: 'IFCSLABELEMENTEDCASE', + 3132237377: 'IFCFLOWMOVINGDEVICE', + 3171933400: 'IFCPLATE', + 3221913625: 'IFCCOMMUNICATIONSAPPLIANCE', + 3242481149: 'IFCDOORSTANDARDCASE', + 3283111854: 'IFCRAMPFLIGHT', + 3296154744: 'IFCCHIMNEY', + 3304561284: 'IFCWINDOW', + 3310460725: 'IFCELECTRICFLOWSTORAGEDEVICE', + 3319311131: 'IFCHEATEXCHANGER', + 3415622556: 'IFCFAN', + 3420628829: 'IFCSOLARDEVICE', + 3493046030: 'IFCGEOGRAPHICELEMENT', + 3495092785: 'IFCCURTAINWALL', + 3508470533: 'IFCFLOWTREATMENTDEVICE', + 3512223829: 'IFCWALLSTANDARDCASE', + 3518393246: 'IFCDUCTSEGMENT', + 3571504051: 'IFCCOMPRESSOR', + 3588315303: 'IFCOPENINGELEMENT', + 3612865200: 'IFCPIPESEGMENT', + 3640358203: 'IFCCOOLINGTOWER', + 3651124850: 'IFCPROJECTIONELEMENT', + 3694346114: 'IFCOUTLET', + 3747195512: 'IFCEVAPORATIVECOOLER', + 3758799889: 'IFCCABLECARRIERSEGMENT', + 3824725483: 'IFCTENDON', + 3825984169: 'IFCTRANSFORMER', + 3902619387: 'IFCCHILLER', + 4074379575: 'IFCDAMPER', + 4086658281: 'IFCSENSOR', + 4123344466: 'IFCELEMENTASSEMBLY', + 4136498852: 'IFCCOOLEDBEAM', + 4156078855: 'IFCWALLELEMENTEDCASE', + 4175244083: 'IFCINTERCEPTOR', + 4207607924: 'IFCVALVE', + 4217484030: 'IFCCABLESEGMENT', + 4237592921: 'IFCWASTETERMINAL', + 4252922144: 'IFCSTAIRFLIGHT', + 4278956645: 'IFCFLOWFITTING', + 4288193352: 'IFCACTUATOR', + 4292641817: 'IFCUNITARYEQUIPMENT', + 3009204131: 'IFCGRID' +}; + +const IfcTypesMap = { + 3821786052: 'IFCACTIONREQUEST', + 2296667514: 'IFCACTOR', + 3630933823: 'IFCACTORROLE', + 4288193352: 'IFCACTUATOR', + 2874132201: 'IFCACTUATORTYPE', + 618182010: 'IFCADDRESS', + 1635779807: 'IFCADVANCEDBREP', + 2603310189: 'IFCADVANCEDBREPWITHVOIDS', + 3406155212: 'IFCADVANCEDFACE', + 1634111441: 'IFCAIRTERMINAL', + 177149247: 'IFCAIRTERMINALBOX', + 1411407467: 'IFCAIRTERMINALBOXTYPE', + 3352864051: 'IFCAIRTERMINALTYPE', + 2056796094: 'IFCAIRTOAIRHEATRECOVERY', + 1871374353: 'IFCAIRTOAIRHEATRECOVERYTYPE', + 3087945054: 'IFCALARM', + 3001207471: 'IFCALARMTYPE', + 325726236: 'IFCALIGNMENT', + 749761778: 'IFCALIGNMENT2DHORIZONTAL', + 3199563722: 'IFCALIGNMENT2DHORIZONTALSEGMENT', + 2483840362: 'IFCALIGNMENT2DSEGMENT', + 3379348081: 'IFCALIGNMENT2DVERSEGCIRCULARARC', + 3239324667: 'IFCALIGNMENT2DVERSEGLINE', + 4263986512: 'IFCALIGNMENT2DVERSEGPARABOLICARC', + 53199957: 'IFCALIGNMENT2DVERTICAL', + 2029264950: 'IFCALIGNMENT2DVERTICALSEGMENT', + 3512275521: 'IFCALIGNMENTCURVE', + 1674181508: 'IFCANNOTATION', + 669184980: 'IFCANNOTATIONFILLAREA', + 639542469: 'IFCAPPLICATION', + 411424972: 'IFCAPPLIEDVALUE', + 130549933: 'IFCAPPROVAL', + 3869604511: 'IFCAPPROVALRELATIONSHIP', + 3798115385: 'IFCARBITRARYCLOSEDPROFILEDEF', + 1310608509: 'IFCARBITRARYOPENPROFILEDEF', + 2705031697: 'IFCARBITRARYPROFILEDEFWITHVOIDS', + 3460190687: 'IFCASSET', + 3207858831: 'IFCASYMMETRICISHAPEPROFILEDEF', + 277319702: 'IFCAUDIOVISUALAPPLIANCE', + 1532957894: 'IFCAUDIOVISUALAPPLIANCETYPE', + 4261334040: 'IFCAXIS1PLACEMENT', + 3125803723: 'IFCAXIS2PLACEMENT2D', + 2740243338: 'IFCAXIS2PLACEMENT3D', + 1967976161: 'IFCBSPLINECURVE', + 2461110595: 'IFCBSPLINECURVEWITHKNOTS', + 2887950389: 'IFCBSPLINESURFACE', + 167062518: 'IFCBSPLINESURFACEWITHKNOTS', + 753842376: 'IFCBEAM', + 2906023776: 'IFCBEAMSTANDARDCASE', + 819618141: 'IFCBEAMTYPE', + 4196446775: 'IFCBEARING', + 3649138523: 'IFCBEARINGTYPE', + 616511568: 'IFCBLOBTEXTURE', + 1334484129: 'IFCBLOCK', + 32344328: 'IFCBOILER', + 231477066: 'IFCBOILERTYPE', + 3649129432: 'IFCBOOLEANCLIPPINGRESULT', + 2736907675: 'IFCBOOLEANRESULT', + 4037036970: 'IFCBOUNDARYCONDITION', + 1136057603: 'IFCBOUNDARYCURVE', + 1560379544: 'IFCBOUNDARYEDGECONDITION', + 3367102660: 'IFCBOUNDARYFACECONDITION', + 1387855156: 'IFCBOUNDARYNODECONDITION', + 2069777674: 'IFCBOUNDARYNODECONDITIONWARPING', + 1260505505: 'IFCBOUNDEDCURVE', + 4182860854: 'IFCBOUNDEDSURFACE', + 2581212453: 'IFCBOUNDINGBOX', + 2713105998: 'IFCBOXEDHALFSPACE', + 644574406: 'IFCBRIDGE', + 963979645: 'IFCBRIDGEPART', + 4031249490: 'IFCBUILDING', + 3299480353: 'IFCBUILDINGELEMENT', + 2979338954: 'IFCBUILDINGELEMENTPART', + 39481116: 'IFCBUILDINGELEMENTPARTTYPE', + 1095909175: 'IFCBUILDINGELEMENTPROXY', + 1909888760: 'IFCBUILDINGELEMENTPROXYTYPE', + 1950629157: 'IFCBUILDINGELEMENTTYPE', + 3124254112: 'IFCBUILDINGSTOREY', + 1177604601: 'IFCBUILDINGSYSTEM', + 2938176219: 'IFCBURNER', + 2188180465: 'IFCBURNERTYPE', + 2898889636: 'IFCCSHAPEPROFILEDEF', + 635142910: 'IFCCABLECARRIERFITTING', + 395041908: 'IFCCABLECARRIERFITTINGTYPE', + 3758799889: 'IFCCABLECARRIERSEGMENT', + 3293546465: 'IFCCABLECARRIERSEGMENTTYPE', + 1051757585: 'IFCCABLEFITTING', + 2674252688: 'IFCCABLEFITTINGTYPE', + 4217484030: 'IFCCABLESEGMENT', + 1285652485: 'IFCCABLESEGMENTTYPE', + 3999819293: 'IFCCAISSONFOUNDATION', + 3203706013: 'IFCCAISSONFOUNDATIONTYPE', + 1123145078: 'IFCCARTESIANPOINT', + 574549367: 'IFCCARTESIANPOINTLIST', + 1675464909: 'IFCCARTESIANPOINTLIST2D', + 2059837836: 'IFCCARTESIANPOINTLIST3D', + 59481748: 'IFCCARTESIANTRANSFORMATIONOPERATOR', + 3749851601: 'IFCCARTESIANTRANSFORMATIONOPERATOR2D', + 3486308946: 'IFCCARTESIANTRANSFORMATIONOPERATOR2DNONUNIFORM', + 3331915920: 'IFCCARTESIANTRANSFORMATIONOPERATOR3D', + 1416205885: 'IFCCARTESIANTRANSFORMATIONOPERATOR3DNONUNIFORM', + 3150382593: 'IFCCENTERLINEPROFILEDEF', + 3902619387: 'IFCCHILLER', + 2951183804: 'IFCCHILLERTYPE', + 3296154744: 'IFCCHIMNEY', + 2197970202: 'IFCCHIMNEYTYPE', + 2611217952: 'IFCCIRCLE', + 2937912522: 'IFCCIRCLEHOLLOWPROFILEDEF', + 1383045692: 'IFCCIRCLEPROFILEDEF', + 1062206242: 'IFCCIRCULARARCSEGMENT2D', + 1677625105: 'IFCCIVILELEMENT', + 3893394355: 'IFCCIVILELEMENTTYPE', + 747523909: 'IFCCLASSIFICATION', + 647927063: 'IFCCLASSIFICATIONREFERENCE', + 2205249479: 'IFCCLOSEDSHELL', + 639361253: 'IFCCOIL', + 2301859152: 'IFCCOILTYPE', + 776857604: 'IFCCOLOURRGB', + 3285139300: 'IFCCOLOURRGBLIST', + 3264961684: 'IFCCOLOURSPECIFICATION', + 843113511: 'IFCCOLUMN', + 905975707: 'IFCCOLUMNSTANDARDCASE', + 300633059: 'IFCCOLUMNTYPE', + 3221913625: 'IFCCOMMUNICATIONSAPPLIANCE', + 400855858: 'IFCCOMMUNICATIONSAPPLIANCETYPE', + 2542286263: 'IFCCOMPLEXPROPERTY', + 3875453745: 'IFCCOMPLEXPROPERTYTEMPLATE', + 3732776249: 'IFCCOMPOSITECURVE', + 15328376: 'IFCCOMPOSITECURVEONSURFACE', + 2485617015: 'IFCCOMPOSITECURVESEGMENT', + 1485152156: 'IFCCOMPOSITEPROFILEDEF', + 3571504051: 'IFCCOMPRESSOR', + 3850581409: 'IFCCOMPRESSORTYPE', + 2272882330: 'IFCCONDENSER', + 2816379211: 'IFCCONDENSERTYPE', + 2510884976: 'IFCCONIC', + 370225590: 'IFCCONNECTEDFACESET', + 1981873012: 'IFCCONNECTIONCURVEGEOMETRY', + 2859738748: 'IFCCONNECTIONGEOMETRY', + 45288368: 'IFCCONNECTIONPOINTECCENTRICITY', + 2614616156: 'IFCCONNECTIONPOINTGEOMETRY', + 2732653382: 'IFCCONNECTIONSURFACEGEOMETRY', + 775493141: 'IFCCONNECTIONVOLUMEGEOMETRY', + 1959218052: 'IFCCONSTRAINT', + 3898045240: 'IFCCONSTRUCTIONEQUIPMENTRESOURCE', + 2185764099: 'IFCCONSTRUCTIONEQUIPMENTRESOURCETYPE', + 1060000209: 'IFCCONSTRUCTIONMATERIALRESOURCE', + 4105962743: 'IFCCONSTRUCTIONMATERIALRESOURCETYPE', + 488727124: 'IFCCONSTRUCTIONPRODUCTRESOURCE', + 1525564444: 'IFCCONSTRUCTIONPRODUCTRESOURCETYPE', + 2559216714: 'IFCCONSTRUCTIONRESOURCE', + 2574617495: 'IFCCONSTRUCTIONRESOURCETYPE', + 3419103109: 'IFCCONTEXT', + 3050246964: 'IFCCONTEXTDEPENDENTUNIT', + 3293443760: 'IFCCONTROL', + 25142252: 'IFCCONTROLLER', + 578613899: 'IFCCONTROLLERTYPE', + 2889183280: 'IFCCONVERSIONBASEDUNIT', + 2713554722: 'IFCCONVERSIONBASEDUNITWITHOFFSET', + 4136498852: 'IFCCOOLEDBEAM', + 335055490: 'IFCCOOLEDBEAMTYPE', + 3640358203: 'IFCCOOLINGTOWER', + 2954562838: 'IFCCOOLINGTOWERTYPE', + 1785450214: 'IFCCOORDINATEOPERATION', + 1466758467: 'IFCCOORDINATEREFERENCESYSTEM', + 3895139033: 'IFCCOSTITEM', + 1419761937: 'IFCCOSTSCHEDULE', + 602808272: 'IFCCOSTVALUE', + 1973544240: 'IFCCOVERING', + 1916426348: 'IFCCOVERINGTYPE', + 3295246426: 'IFCCREWRESOURCE', + 1815067380: 'IFCCREWRESOURCETYPE', + 2506170314: 'IFCCSGPRIMITIVE3D', + 2147822146: 'IFCCSGSOLID', + 539742890: 'IFCCURRENCYRELATIONSHIP', + 3495092785: 'IFCCURTAINWALL', + 1457835157: 'IFCCURTAINWALLTYPE', + 2601014836: 'IFCCURVE', + 2827736869: 'IFCCURVEBOUNDEDPLANE', + 2629017746: 'IFCCURVEBOUNDEDSURFACE', + 1186437898: 'IFCCURVESEGMENT2D', + 3800577675: 'IFCCURVESTYLE', + 1105321065: 'IFCCURVESTYLEFONT', + 2367409068: 'IFCCURVESTYLEFONTANDSCALING', + 3510044353: 'IFCCURVESTYLEFONTPATTERN', + 1213902940: 'IFCCYLINDRICALSURFACE', + 4074379575: 'IFCDAMPER', + 3961806047: 'IFCDAMPERTYPE', + 3426335179: 'IFCDEEPFOUNDATION', + 1306400036: 'IFCDEEPFOUNDATIONTYPE', + 3632507154: 'IFCDERIVEDPROFILEDEF', + 1765591967: 'IFCDERIVEDUNIT', + 1045800335: 'IFCDERIVEDUNITELEMENT', + 2949456006: 'IFCDIMENSIONALEXPONENTS', + 32440307: 'IFCDIRECTION', + 1335981549: 'IFCDISCRETEACCESSORY', + 2635815018: 'IFCDISCRETEACCESSORYTYPE', + 1945343521: 'IFCDISTANCEEXPRESSION', + 1052013943: 'IFCDISTRIBUTIONCHAMBERELEMENT', + 1599208980: 'IFCDISTRIBUTIONCHAMBERELEMENTTYPE', + 562808652: 'IFCDISTRIBUTIONCIRCUIT', + 1062813311: 'IFCDISTRIBUTIONCONTROLELEMENT', + 2063403501: 'IFCDISTRIBUTIONCONTROLELEMENTTYPE', + 1945004755: 'IFCDISTRIBUTIONELEMENT', + 3256556792: 'IFCDISTRIBUTIONELEMENTTYPE', + 3040386961: 'IFCDISTRIBUTIONFLOWELEMENT', + 3849074793: 'IFCDISTRIBUTIONFLOWELEMENTTYPE', + 3041715199: 'IFCDISTRIBUTIONPORT', + 3205830791: 'IFCDISTRIBUTIONSYSTEM', + 1154170062: 'IFCDOCUMENTINFORMATION', + 770865208: 'IFCDOCUMENTINFORMATIONRELATIONSHIP', + 3732053477: 'IFCDOCUMENTREFERENCE', + 395920057: 'IFCDOOR', + 2963535650: 'IFCDOORLININGPROPERTIES', + 1714330368: 'IFCDOORPANELPROPERTIES', + 3242481149: 'IFCDOORSTANDARDCASE', + 526551008: 'IFCDOORSTYLE', + 2323601079: 'IFCDOORTYPE', + 445594917: 'IFCDRAUGHTINGPREDEFINEDCOLOUR', + 4006246654: 'IFCDRAUGHTINGPREDEFINEDCURVEFONT', + 342316401: 'IFCDUCTFITTING', + 869906466: 'IFCDUCTFITTINGTYPE', + 3518393246: 'IFCDUCTSEGMENT', + 3760055223: 'IFCDUCTSEGMENTTYPE', + 1360408905: 'IFCDUCTSILENCER', + 2030761528: 'IFCDUCTSILENCERTYPE', + 3900360178: 'IFCEDGE', + 476780140: 'IFCEDGECURVE', + 1472233963: 'IFCEDGELOOP', + 1904799276: 'IFCELECTRICAPPLIANCE', + 663422040: 'IFCELECTRICAPPLIANCETYPE', + 862014818: 'IFCELECTRICDISTRIBUTIONBOARD', + 2417008758: 'IFCELECTRICDISTRIBUTIONBOARDTYPE', + 3310460725: 'IFCELECTRICFLOWSTORAGEDEVICE', + 3277789161: 'IFCELECTRICFLOWSTORAGEDEVICETYPE', + 264262732: 'IFCELECTRICGENERATOR', + 1534661035: 'IFCELECTRICGENERATORTYPE', + 402227799: 'IFCELECTRICMOTOR', + 1217240411: 'IFCELECTRICMOTORTYPE', + 1003880860: 'IFCELECTRICTIMECONTROL', + 712377611: 'IFCELECTRICTIMECONTROLTYPE', + 1758889154: 'IFCELEMENT', + 4123344466: 'IFCELEMENTASSEMBLY', + 2397081782: 'IFCELEMENTASSEMBLYTYPE', + 1623761950: 'IFCELEMENTCOMPONENT', + 2590856083: 'IFCELEMENTCOMPONENTTYPE', + 1883228015: 'IFCELEMENTQUANTITY', + 339256511: 'IFCELEMENTTYPE', + 2777663545: 'IFCELEMENTARYSURFACE', + 1704287377: 'IFCELLIPSE', + 2835456948: 'IFCELLIPSEPROFILEDEF', + 1658829314: 'IFCENERGYCONVERSIONDEVICE', + 2107101300: 'IFCENERGYCONVERSIONDEVICETYPE', + 2814081492: 'IFCENGINE', + 132023988: 'IFCENGINETYPE', + 3747195512: 'IFCEVAPORATIVECOOLER', + 3174744832: 'IFCEVAPORATIVECOOLERTYPE', + 484807127: 'IFCEVAPORATOR', + 3390157468: 'IFCEVAPORATORTYPE', + 4148101412: 'IFCEVENT', + 211053100: 'IFCEVENTTIME', + 4024345920: 'IFCEVENTTYPE', + 297599258: 'IFCEXTENDEDPROPERTIES', + 4294318154: 'IFCEXTERNALINFORMATION', + 3200245327: 'IFCEXTERNALREFERENCE', + 1437805879: 'IFCEXTERNALREFERENCERELATIONSHIP', + 1209101575: 'IFCEXTERNALSPATIALELEMENT', + 2853485674: 'IFCEXTERNALSPATIALSTRUCTUREELEMENT', + 2242383968: 'IFCEXTERNALLYDEFINEDHATCHSTYLE', + 1040185647: 'IFCEXTERNALLYDEFINEDSURFACESTYLE', + 3548104201: 'IFCEXTERNALLYDEFINEDTEXTFONT', + 477187591: 'IFCEXTRUDEDAREASOLID', + 2804161546: 'IFCEXTRUDEDAREASOLIDTAPERED', + 2556980723: 'IFCFACE', + 2047409740: 'IFCFACEBASEDSURFACEMODEL', + 1809719519: 'IFCFACEBOUND', + 803316827: 'IFCFACEOUTERBOUND', + 3008276851: 'IFCFACESURFACE', + 807026263: 'IFCFACETEDBREP', + 3737207727: 'IFCFACETEDBREPWITHVOIDS', + 24185140: 'IFCFACILITY', + 1310830890: 'IFCFACILITYPART', + 4219587988: 'IFCFAILURECONNECTIONCONDITION', + 3415622556: 'IFCFAN', + 346874300: 'IFCFANTYPE', + 647756555: 'IFCFASTENER', + 2489546625: 'IFCFASTENERTYPE', + 2827207264: 'IFCFEATUREELEMENT', + 2143335405: 'IFCFEATUREELEMENTADDITION', + 1287392070: 'IFCFEATUREELEMENTSUBTRACTION', + 738692330: 'IFCFILLAREASTYLE', + 374418227: 'IFCFILLAREASTYLEHATCHING', + 315944413: 'IFCFILLAREASTYLETILES', + 819412036: 'IFCFILTER', + 1810631287: 'IFCFILTERTYPE', + 1426591983: 'IFCFIRESUPPRESSIONTERMINAL', + 4222183408: 'IFCFIRESUPPRESSIONTERMINALTYPE', + 2652556860: 'IFCFIXEDREFERENCESWEPTAREASOLID', + 2058353004: 'IFCFLOWCONTROLLER', + 3907093117: 'IFCFLOWCONTROLLERTYPE', + 4278956645: 'IFCFLOWFITTING', + 3198132628: 'IFCFLOWFITTINGTYPE', + 182646315: 'IFCFLOWINSTRUMENT', + 4037862832: 'IFCFLOWINSTRUMENTTYPE', + 2188021234: 'IFCFLOWMETER', + 3815607619: 'IFCFLOWMETERTYPE', + 3132237377: 'IFCFLOWMOVINGDEVICE', + 1482959167: 'IFCFLOWMOVINGDEVICETYPE', + 987401354: 'IFCFLOWSEGMENT', + 1834744321: 'IFCFLOWSEGMENTTYPE', + 707683696: 'IFCFLOWSTORAGEDEVICE', + 1339347760: 'IFCFLOWSTORAGEDEVICETYPE', + 2223149337: 'IFCFLOWTERMINAL', + 2297155007: 'IFCFLOWTERMINALTYPE', + 3508470533: 'IFCFLOWTREATMENTDEVICE', + 3009222698: 'IFCFLOWTREATMENTDEVICETYPE', + 900683007: 'IFCFOOTING', + 1893162501: 'IFCFOOTINGTYPE', + 263784265: 'IFCFURNISHINGELEMENT', + 4238390223: 'IFCFURNISHINGELEMENTTYPE', + 1509553395: 'IFCFURNITURE', + 1268542332: 'IFCFURNITURETYPE', + 3493046030: 'IFCGEOGRAPHICELEMENT', + 4095422895: 'IFCGEOGRAPHICELEMENTTYPE', + 987898635: 'IFCGEOMETRICCURVESET', + 3448662350: 'IFCGEOMETRICREPRESENTATIONCONTEXT', + 2453401579: 'IFCGEOMETRICREPRESENTATIONITEM', + 4142052618: 'IFCGEOMETRICREPRESENTATIONSUBCONTEXT', + 3590301190: 'IFCGEOMETRICSET', + 3009204131: 'IFCGRID', + 852622518: 'IFCGRIDAXIS', + 178086475: 'IFCGRIDPLACEMENT', + 2706460486: 'IFCGROUP', + 812098782: 'IFCHALFSPACESOLID', + 3319311131: 'IFCHEATEXCHANGER', + 1251058090: 'IFCHEATEXCHANGERTYPE', + 2068733104: 'IFCHUMIDIFIER', + 1806887404: 'IFCHUMIDIFIERTYPE', + 1484403080: 'IFCISHAPEPROFILEDEF', + 3905492369: 'IFCIMAGETEXTURE', + 3570813810: 'IFCINDEXEDCOLOURMAP', + 2571569899: 'IFCINDEXEDPOLYCURVE', + 178912537: 'IFCINDEXEDPOLYGONALFACE', + 2294589976: 'IFCINDEXEDPOLYGONALFACEWITHVOIDS', + 1437953363: 'IFCINDEXEDTEXTUREMAP', + 2133299955: 'IFCINDEXEDTRIANGLETEXTUREMAP', + 4175244083: 'IFCINTERCEPTOR', + 3946677679: 'IFCINTERCEPTORTYPE', + 3113134337: 'IFCINTERSECTIONCURVE', + 2391368822: 'IFCINVENTORY', + 3741457305: 'IFCIRREGULARTIMESERIES', + 3020489413: 'IFCIRREGULARTIMESERIESVALUE', + 2176052936: 'IFCJUNCTIONBOX', + 4288270099: 'IFCJUNCTIONBOXTYPE', + 572779678: 'IFCLSHAPEPROFILEDEF', + 3827777499: 'IFCLABORRESOURCE', + 428585644: 'IFCLABORRESOURCETYPE', + 1585845231: 'IFCLAGTIME', + 76236018: 'IFCLAMP', + 1051575348: 'IFCLAMPTYPE', + 2655187982: 'IFCLIBRARYINFORMATION', + 3452421091: 'IFCLIBRARYREFERENCE', + 4162380809: 'IFCLIGHTDISTRIBUTIONDATA', + 629592764: 'IFCLIGHTFIXTURE', + 1161773419: 'IFCLIGHTFIXTURETYPE', + 1566485204: 'IFCLIGHTINTENSITYDISTRIBUTION', + 1402838566: 'IFCLIGHTSOURCE', + 125510826: 'IFCLIGHTSOURCEAMBIENT', + 2604431987: 'IFCLIGHTSOURCEDIRECTIONAL', + 4266656042: 'IFCLIGHTSOURCEGONIOMETRIC', + 1520743889: 'IFCLIGHTSOURCEPOSITIONAL', + 3422422726: 'IFCLIGHTSOURCESPOT', + 1281925730: 'IFCLINE', + 3092502836: 'IFCLINESEGMENT2D', + 388784114: 'IFCLINEARPLACEMENT', + 1154579445: 'IFCLINEARPOSITIONINGELEMENT', + 2624227202: 'IFCLOCALPLACEMENT', + 1008929658: 'IFCLOOP', + 1425443689: 'IFCMANIFOLDSOLIDBREP', + 3057273783: 'IFCMAPCONVERSION', + 2347385850: 'IFCMAPPEDITEM', + 1838606355: 'IFCMATERIAL', + 1847130766: 'IFCMATERIALCLASSIFICATIONRELATIONSHIP', + 3708119000: 'IFCMATERIALCONSTITUENT', + 2852063980: 'IFCMATERIALCONSTITUENTSET', + 760658860: 'IFCMATERIALDEFINITION', + 2022407955: 'IFCMATERIALDEFINITIONREPRESENTATION', + 248100487: 'IFCMATERIALLAYER', + 3303938423: 'IFCMATERIALLAYERSET', + 1303795690: 'IFCMATERIALLAYERSETUSAGE', + 1847252529: 'IFCMATERIALLAYERWITHOFFSETS', + 2199411900: 'IFCMATERIALLIST', + 2235152071: 'IFCMATERIALPROFILE', + 164193824: 'IFCMATERIALPROFILESET', + 3079605661: 'IFCMATERIALPROFILESETUSAGE', + 3404854881: 'IFCMATERIALPROFILESETUSAGETAPERING', + 552965576: 'IFCMATERIALPROFILEWITHOFFSETS', + 3265635763: 'IFCMATERIALPROPERTIES', + 853536259: 'IFCMATERIALRELATIONSHIP', + 1507914824: 'IFCMATERIALUSAGEDEFINITION', + 2597039031: 'IFCMEASUREWITHUNIT', + 377706215: 'IFCMECHANICALFASTENER', + 2108223431: 'IFCMECHANICALFASTENERTYPE', + 1437502449: 'IFCMEDICALDEVICE', + 1114901282: 'IFCMEDICALDEVICETYPE', + 1073191201: 'IFCMEMBER', + 1911478936: 'IFCMEMBERSTANDARDCASE', + 3181161470: 'IFCMEMBERTYPE', + 3368373690: 'IFCMETRIC', + 2998442950: 'IFCMIRROREDPROFILEDEF', + 2706619895: 'IFCMONETARYUNIT', + 2474470126: 'IFCMOTORCONNECTION', + 977012517: 'IFCMOTORCONNECTIONTYPE', + 1918398963: 'IFCNAMEDUNIT', + 3888040117: 'IFCOBJECT', + 219451334: 'IFCOBJECTDEFINITION', + 3701648758: 'IFCOBJECTPLACEMENT', + 2251480897: 'IFCOBJECTIVE', + 4143007308: 'IFCOCCUPANT', + 590820931: 'IFCOFFSETCURVE', + 3388369263: 'IFCOFFSETCURVE2D', + 3505215534: 'IFCOFFSETCURVE3D', + 2485787929: 'IFCOFFSETCURVEBYDISTANCES', + 2665983363: 'IFCOPENSHELL', + 3588315303: 'IFCOPENINGELEMENT', + 3079942009: 'IFCOPENINGSTANDARDCASE', + 4251960020: 'IFCORGANIZATION', + 1411181986: 'IFCORGANIZATIONRELATIONSHIP', + 643959842: 'IFCORIENTATIONEXPRESSION', + 1029017970: 'IFCORIENTEDEDGE', + 144952367: 'IFCOUTERBOUNDARYCURVE', + 3694346114: 'IFCOUTLET', + 2837617999: 'IFCOUTLETTYPE', + 1207048766: 'IFCOWNERHISTORY', + 2529465313: 'IFCPARAMETERIZEDPROFILEDEF', + 2519244187: 'IFCPATH', + 1682466193: 'IFCPCURVE', + 2382730787: 'IFCPERFORMANCEHISTORY', + 3566463478: 'IFCPERMEABLECOVERINGPROPERTIES', + 3327091369: 'IFCPERMIT', + 2077209135: 'IFCPERSON', + 101040310: 'IFCPERSONANDORGANIZATION', + 3021840470: 'IFCPHYSICALCOMPLEXQUANTITY', + 2483315170: 'IFCPHYSICALQUANTITY', + 2226359599: 'IFCPHYSICALSIMPLEQUANTITY', + 1687234759: 'IFCPILE', + 1158309216: 'IFCPILETYPE', + 310824031: 'IFCPIPEFITTING', + 804291784: 'IFCPIPEFITTINGTYPE', + 3612865200: 'IFCPIPESEGMENT', + 4231323485: 'IFCPIPESEGMENTTYPE', + 597895409: 'IFCPIXELTEXTURE', + 2004835150: 'IFCPLACEMENT', + 603570806: 'IFCPLANARBOX', + 1663979128: 'IFCPLANAREXTENT', + 220341763: 'IFCPLANE', + 3171933400: 'IFCPLATE', + 1156407060: 'IFCPLATESTANDARDCASE', + 4017108033: 'IFCPLATETYPE', + 2067069095: 'IFCPOINT', + 4022376103: 'IFCPOINTONCURVE', + 1423911732: 'IFCPOINTONSURFACE', + 2924175390: 'IFCPOLYLOOP', + 2775532180: 'IFCPOLYGONALBOUNDEDHALFSPACE', + 2839578677: 'IFCPOLYGONALFACESET', + 3724593414: 'IFCPOLYLINE', + 3740093272: 'IFCPORT', + 1946335990: 'IFCPOSITIONINGELEMENT', + 3355820592: 'IFCPOSTALADDRESS', + 759155922: 'IFCPREDEFINEDCOLOUR', + 2559016684: 'IFCPREDEFINEDCURVEFONT', + 3727388367: 'IFCPREDEFINEDITEM', + 3778827333: 'IFCPREDEFINEDPROPERTIES', + 3967405729: 'IFCPREDEFINEDPROPERTYSET', + 1775413392: 'IFCPREDEFINEDTEXTFONT', + 677532197: 'IFCPRESENTATIONITEM', + 2022622350: 'IFCPRESENTATIONLAYERASSIGNMENT', + 1304840413: 'IFCPRESENTATIONLAYERWITHSTYLE', + 3119450353: 'IFCPRESENTATIONSTYLE', + 2417041796: 'IFCPRESENTATIONSTYLEASSIGNMENT', + 2744685151: 'IFCPROCEDURE', + 569719735: 'IFCPROCEDURETYPE', + 2945172077: 'IFCPROCESS', + 4208778838: 'IFCPRODUCT', + 673634403: 'IFCPRODUCTDEFINITIONSHAPE', + 2095639259: 'IFCPRODUCTREPRESENTATION', + 3958567839: 'IFCPROFILEDEF', + 2802850158: 'IFCPROFILEPROPERTIES', + 103090709: 'IFCPROJECT', + 653396225: 'IFCPROJECTLIBRARY', + 2904328755: 'IFCPROJECTORDER', + 3843373140: 'IFCPROJECTEDCRS', + 3651124850: 'IFCPROJECTIONELEMENT', + 2598011224: 'IFCPROPERTY', + 986844984: 'IFCPROPERTYABSTRACTION', + 871118103: 'IFCPROPERTYBOUNDEDVALUE', + 1680319473: 'IFCPROPERTYDEFINITION', + 148025276: 'IFCPROPERTYDEPENDENCYRELATIONSHIP', + 4166981789: 'IFCPROPERTYENUMERATEDVALUE', + 3710013099: 'IFCPROPERTYENUMERATION', + 2752243245: 'IFCPROPERTYLISTVALUE', + 941946838: 'IFCPROPERTYREFERENCEVALUE', + 1451395588: 'IFCPROPERTYSET', + 3357820518: 'IFCPROPERTYSETDEFINITION', + 492091185: 'IFCPROPERTYSETTEMPLATE', + 3650150729: 'IFCPROPERTYSINGLEVALUE', + 110355661: 'IFCPROPERTYTABLEVALUE', + 3521284610: 'IFCPROPERTYTEMPLATE', + 1482703590: 'IFCPROPERTYTEMPLATEDEFINITION', + 738039164: 'IFCPROTECTIVEDEVICE', + 2295281155: 'IFCPROTECTIVEDEVICETRIPPINGUNIT', + 655969474: 'IFCPROTECTIVEDEVICETRIPPINGUNITTYPE', + 1842657554: 'IFCPROTECTIVEDEVICETYPE', + 3219374653: 'IFCPROXY', + 90941305: 'IFCPUMP', + 2250791053: 'IFCPUMPTYPE', + 2044713172: 'IFCQUANTITYAREA', + 2093928680: 'IFCQUANTITYCOUNT', + 931644368: 'IFCQUANTITYLENGTH', + 2090586900: 'IFCQUANTITYSET', + 3252649465: 'IFCQUANTITYTIME', + 2405470396: 'IFCQUANTITYVOLUME', + 825690147: 'IFCQUANTITYWEIGHT', + 2262370178: 'IFCRAILING', + 2893384427: 'IFCRAILINGTYPE', + 3024970846: 'IFCRAMP', + 3283111854: 'IFCRAMPFLIGHT', + 2324767716: 'IFCRAMPFLIGHTTYPE', + 1469900589: 'IFCRAMPTYPE', + 1232101972: 'IFCRATIONALBSPLINECURVEWITHKNOTS', + 683857671: 'IFCRATIONALBSPLINESURFACEWITHKNOTS', + 2770003689: 'IFCRECTANGLEHOLLOWPROFILEDEF', + 3615266464: 'IFCRECTANGLEPROFILEDEF', + 2798486643: 'IFCRECTANGULARPYRAMID', + 3454111270: 'IFCRECTANGULARTRIMMEDSURFACE', + 3915482550: 'IFCRECURRENCEPATTERN', + 2433181523: 'IFCREFERENCE', + 4021432810: 'IFCREFERENT', + 3413951693: 'IFCREGULARTIMESERIES', + 1580146022: 'IFCREINFORCEMENTBARPROPERTIES', + 3765753017: 'IFCREINFORCEMENTDEFINITIONPROPERTIES', + 979691226: 'IFCREINFORCINGBAR', + 2572171363: 'IFCREINFORCINGBARTYPE', + 3027567501: 'IFCREINFORCINGELEMENT', + 964333572: 'IFCREINFORCINGELEMENTTYPE', + 2320036040: 'IFCREINFORCINGMESH', + 2310774935: 'IFCREINFORCINGMESHTYPE', + 160246688: 'IFCRELAGGREGATES', + 3939117080: 'IFCRELASSIGNS', + 1683148259: 'IFCRELASSIGNSTOACTOR', + 2495723537: 'IFCRELASSIGNSTOCONTROL', + 1307041759: 'IFCRELASSIGNSTOGROUP', + 1027710054: 'IFCRELASSIGNSTOGROUPBYFACTOR', + 4278684876: 'IFCRELASSIGNSTOPROCESS', + 2857406711: 'IFCRELASSIGNSTOPRODUCT', + 205026976: 'IFCRELASSIGNSTORESOURCE', + 1865459582: 'IFCRELASSOCIATES', + 4095574036: 'IFCRELASSOCIATESAPPROVAL', + 919958153: 'IFCRELASSOCIATESCLASSIFICATION', + 2728634034: 'IFCRELASSOCIATESCONSTRAINT', + 982818633: 'IFCRELASSOCIATESDOCUMENT', + 3840914261: 'IFCRELASSOCIATESLIBRARY', + 2655215786: 'IFCRELASSOCIATESMATERIAL', + 826625072: 'IFCRELCONNECTS', + 1204542856: 'IFCRELCONNECTSELEMENTS', + 3945020480: 'IFCRELCONNECTSPATHELEMENTS', + 4201705270: 'IFCRELCONNECTSPORTTOELEMENT', + 3190031847: 'IFCRELCONNECTSPORTS', + 2127690289: 'IFCRELCONNECTSSTRUCTURALACTIVITY', + 1638771189: 'IFCRELCONNECTSSTRUCTURALMEMBER', + 504942748: 'IFCRELCONNECTSWITHECCENTRICITY', + 3678494232: 'IFCRELCONNECTSWITHREALIZINGELEMENTS', + 3242617779: 'IFCRELCONTAINEDINSPATIALSTRUCTURE', + 886880790: 'IFCRELCOVERSBLDGELEMENTS', + 2802773753: 'IFCRELCOVERSSPACES', + 2565941209: 'IFCRELDECLARES', + 2551354335: 'IFCRELDECOMPOSES', + 693640335: 'IFCRELDEFINES', + 1462361463: 'IFCRELDEFINESBYOBJECT', + 4186316022: 'IFCRELDEFINESBYPROPERTIES', + 307848117: 'IFCRELDEFINESBYTEMPLATE', + 781010003: 'IFCRELDEFINESBYTYPE', + 3940055652: 'IFCRELFILLSELEMENT', + 279856033: 'IFCRELFLOWCONTROLELEMENTS', + 427948657: 'IFCRELINTERFERESELEMENTS', + 3268803585: 'IFCRELNESTS', + 1441486842: 'IFCRELPOSITIONS', + 750771296: 'IFCRELPROJECTSELEMENT', + 1245217292: 'IFCRELREFERENCEDINSPATIALSTRUCTURE', + 4122056220: 'IFCRELSEQUENCE', + 366585022: 'IFCRELSERVICESBUILDINGS', + 3451746338: 'IFCRELSPACEBOUNDARY', + 3523091289: 'IFCRELSPACEBOUNDARY1STLEVEL', + 1521410863: 'IFCRELSPACEBOUNDARY2NDLEVEL', + 1401173127: 'IFCRELVOIDSELEMENT', + 478536968: 'IFCRELATIONSHIP', + 816062949: 'IFCREPARAMETRISEDCOMPOSITECURVESEGMENT', + 1076942058: 'IFCREPRESENTATION', + 3377609919: 'IFCREPRESENTATIONCONTEXT', + 3008791417: 'IFCREPRESENTATIONITEM', + 1660063152: 'IFCREPRESENTATIONMAP', + 2914609552: 'IFCRESOURCE', + 2943643501: 'IFCRESOURCEAPPROVALRELATIONSHIP', + 1608871552: 'IFCRESOURCECONSTRAINTRELATIONSHIP', + 2439245199: 'IFCRESOURCELEVELRELATIONSHIP', + 1042787934: 'IFCRESOURCETIME', + 1856042241: 'IFCREVOLVEDAREASOLID', + 3243963512: 'IFCREVOLVEDAREASOLIDTAPERED', + 4158566097: 'IFCRIGHTCIRCULARCONE', + 3626867408: 'IFCRIGHTCIRCULARCYLINDER', + 2016517767: 'IFCROOF', + 2781568857: 'IFCROOFTYPE', + 2341007311: 'IFCROOT', + 2778083089: 'IFCROUNDEDRECTANGLEPROFILEDEF', + 448429030: 'IFCSIUNIT', + 3053780830: 'IFCSANITARYTERMINAL', + 1768891740: 'IFCSANITARYTERMINALTYPE', + 1054537805: 'IFCSCHEDULINGTIME', + 2157484638: 'IFCSEAMCURVE', + 2042790032: 'IFCSECTIONPROPERTIES', + 4165799628: 'IFCSECTIONREINFORCEMENTPROPERTIES', + 1862484736: 'IFCSECTIONEDSOLID', + 1290935644: 'IFCSECTIONEDSOLIDHORIZONTAL', + 1509187699: 'IFCSECTIONEDSPINE', + 4086658281: 'IFCSENSOR', + 1783015770: 'IFCSENSORTYPE', + 1329646415: 'IFCSHADINGDEVICE', + 4074543187: 'IFCSHADINGDEVICETYPE', + 867548509: 'IFCSHAPEASPECT', + 3982875396: 'IFCSHAPEMODEL', + 4240577450: 'IFCSHAPEREPRESENTATION', + 4124623270: 'IFCSHELLBASEDSURFACEMODEL', + 3692461612: 'IFCSIMPLEPROPERTY', + 3663146110: 'IFCSIMPLEPROPERTYTEMPLATE', + 4097777520: 'IFCSITE', + 1529196076: 'IFCSLAB', + 3127900445: 'IFCSLABELEMENTEDCASE', + 3027962421: 'IFCSLABSTANDARDCASE', + 2533589738: 'IFCSLABTYPE', + 2609359061: 'IFCSLIPPAGECONNECTIONCONDITION', + 3420628829: 'IFCSOLARDEVICE', + 1072016465: 'IFCSOLARDEVICETYPE', + 723233188: 'IFCSOLIDMODEL', + 3856911033: 'IFCSPACE', + 1999602285: 'IFCSPACEHEATER', + 1305183839: 'IFCSPACEHEATERTYPE', + 3812236995: 'IFCSPACETYPE', + 1412071761: 'IFCSPATIALELEMENT', + 710998568: 'IFCSPATIALELEMENTTYPE', + 2706606064: 'IFCSPATIALSTRUCTUREELEMENT', + 3893378262: 'IFCSPATIALSTRUCTUREELEMENTTYPE', + 463610769: 'IFCSPATIALZONE', + 2481509218: 'IFCSPATIALZONETYPE', + 451544542: 'IFCSPHERE', + 4015995234: 'IFCSPHERICALSURFACE', + 1404847402: 'IFCSTACKTERMINAL', + 3112655638: 'IFCSTACKTERMINALTYPE', + 331165859: 'IFCSTAIR', + 4252922144: 'IFCSTAIRFLIGHT', + 1039846685: 'IFCSTAIRFLIGHTTYPE', + 338393293: 'IFCSTAIRTYPE', + 682877961: 'IFCSTRUCTURALACTION', + 3544373492: 'IFCSTRUCTURALACTIVITY', + 2515109513: 'IFCSTRUCTURALANALYSISMODEL', + 1179482911: 'IFCSTRUCTURALCONNECTION', + 2273995522: 'IFCSTRUCTURALCONNECTIONCONDITION', + 1004757350: 'IFCSTRUCTURALCURVEACTION', + 4243806635: 'IFCSTRUCTURALCURVECONNECTION', + 214636428: 'IFCSTRUCTURALCURVEMEMBER', + 2445595289: 'IFCSTRUCTURALCURVEMEMBERVARYING', + 2757150158: 'IFCSTRUCTURALCURVEREACTION', + 3136571912: 'IFCSTRUCTURALITEM', + 1807405624: 'IFCSTRUCTURALLINEARACTION', + 2162789131: 'IFCSTRUCTURALLOAD', + 385403989: 'IFCSTRUCTURALLOADCASE', + 3478079324: 'IFCSTRUCTURALLOADCONFIGURATION', + 1252848954: 'IFCSTRUCTURALLOADGROUP', + 1595516126: 'IFCSTRUCTURALLOADLINEARFORCE', + 609421318: 'IFCSTRUCTURALLOADORRESULT', + 2668620305: 'IFCSTRUCTURALLOADPLANARFORCE', + 2473145415: 'IFCSTRUCTURALLOADSINGLEDISPLACEMENT', + 1973038258: 'IFCSTRUCTURALLOADSINGLEDISPLACEMENTDISTORTION', + 1597423693: 'IFCSTRUCTURALLOADSINGLEFORCE', + 1190533807: 'IFCSTRUCTURALLOADSINGLEFORCEWARPING', + 2525727697: 'IFCSTRUCTURALLOADSTATIC', + 3408363356: 'IFCSTRUCTURALLOADTEMPERATURE', + 530289379: 'IFCSTRUCTURALMEMBER', + 1621171031: 'IFCSTRUCTURALPLANARACTION', + 2082059205: 'IFCSTRUCTURALPOINTACTION', + 734778138: 'IFCSTRUCTURALPOINTCONNECTION', + 1235345126: 'IFCSTRUCTURALPOINTREACTION', + 3689010777: 'IFCSTRUCTURALREACTION', + 2986769608: 'IFCSTRUCTURALRESULTGROUP', + 3657597509: 'IFCSTRUCTURALSURFACEACTION', + 1975003073: 'IFCSTRUCTURALSURFACECONNECTION', + 3979015343: 'IFCSTRUCTURALSURFACEMEMBER', + 2218152070: 'IFCSTRUCTURALSURFACEMEMBERVARYING', + 603775116: 'IFCSTRUCTURALSURFACEREACTION', + 2830218821: 'IFCSTYLEMODEL', + 3958052878: 'IFCSTYLEDITEM', + 3049322572: 'IFCSTYLEDREPRESENTATION', + 148013059: 'IFCSUBCONTRACTRESOURCE', + 4095615324: 'IFCSUBCONTRACTRESOURCETYPE', + 2233826070: 'IFCSUBEDGE', + 2513912981: 'IFCSURFACE', + 699246055: 'IFCSURFACECURVE', + 2028607225: 'IFCSURFACECURVESWEPTAREASOLID', + 3101698114: 'IFCSURFACEFEATURE', + 2809605785: 'IFCSURFACEOFLINEAREXTRUSION', + 4124788165: 'IFCSURFACEOFREVOLUTION', + 2934153892: 'IFCSURFACEREINFORCEMENTAREA', + 1300840506: 'IFCSURFACESTYLE', + 3303107099: 'IFCSURFACESTYLELIGHTING', + 1607154358: 'IFCSURFACESTYLEREFRACTION', + 1878645084: 'IFCSURFACESTYLERENDERING', + 846575682: 'IFCSURFACESTYLESHADING', + 1351298697: 'IFCSURFACESTYLEWITHTEXTURES', + 626085974: 'IFCSURFACETEXTURE', + 2247615214: 'IFCSWEPTAREASOLID', + 1260650574: 'IFCSWEPTDISKSOLID', + 1096409881: 'IFCSWEPTDISKSOLIDPOLYGONAL', + 230924584: 'IFCSWEPTSURFACE', + 1162798199: 'IFCSWITCHINGDEVICE', + 2315554128: 'IFCSWITCHINGDEVICETYPE', + 2254336722: 'IFCSYSTEM', + 413509423: 'IFCSYSTEMFURNITUREELEMENT', + 1580310250: 'IFCSYSTEMFURNITUREELEMENTTYPE', + 3071757647: 'IFCTSHAPEPROFILEDEF', + 985171141: 'IFCTABLE', + 2043862942: 'IFCTABLECOLUMN', + 531007025: 'IFCTABLEROW', + 812556717: 'IFCTANK', + 5716631: 'IFCTANKTYPE', + 3473067441: 'IFCTASK', + 1549132990: 'IFCTASKTIME', + 2771591690: 'IFCTASKTIMERECURRING', + 3206491090: 'IFCTASKTYPE', + 912023232: 'IFCTELECOMADDRESS', + 3824725483: 'IFCTENDON', + 2347447852: 'IFCTENDONANCHOR', + 3081323446: 'IFCTENDONANCHORTYPE', + 3663046924: 'IFCTENDONCONDUIT', + 2281632017: 'IFCTENDONCONDUITTYPE', + 2415094496: 'IFCTENDONTYPE', + 2387106220: 'IFCTESSELLATEDFACESET', + 901063453: 'IFCTESSELLATEDITEM', + 4282788508: 'IFCTEXTLITERAL', + 3124975700: 'IFCTEXTLITERALWITHEXTENT', + 1447204868: 'IFCTEXTSTYLE', + 1983826977: 'IFCTEXTSTYLEFONTMODEL', + 2636378356: 'IFCTEXTSTYLEFORDEFINEDFONT', + 1640371178: 'IFCTEXTSTYLETEXTMODEL', + 280115917: 'IFCTEXTURECOORDINATE', + 1742049831: 'IFCTEXTURECOORDINATEGENERATOR', + 2552916305: 'IFCTEXTUREMAP', + 1210645708: 'IFCTEXTUREVERTEX', + 3611470254: 'IFCTEXTUREVERTEXLIST', + 1199560280: 'IFCTIMEPERIOD', + 3101149627: 'IFCTIMESERIES', + 581633288: 'IFCTIMESERIESVALUE', + 1377556343: 'IFCTOPOLOGICALREPRESENTATIONITEM', + 1735638870: 'IFCTOPOLOGYREPRESENTATION', + 1935646853: 'IFCTOROIDALSURFACE', + 3825984169: 'IFCTRANSFORMER', + 1692211062: 'IFCTRANSFORMERTYPE', + 2595432518: 'IFCTRANSITIONCURVESEGMENT2D', + 1620046519: 'IFCTRANSPORTELEMENT', + 2097647324: 'IFCTRANSPORTELEMENTTYPE', + 2715220739: 'IFCTRAPEZIUMPROFILEDEF', + 2916149573: 'IFCTRIANGULATEDFACESET', + 1229763772: 'IFCTRIANGULATEDIRREGULARNETWORK', + 3593883385: 'IFCTRIMMEDCURVE', + 3026737570: 'IFCTUBEBUNDLE', + 1600972822: 'IFCTUBEBUNDLETYPE', + 1628702193: 'IFCTYPEOBJECT', + 3736923433: 'IFCTYPEPROCESS', + 2347495698: 'IFCTYPEPRODUCT', + 3698973494: 'IFCTYPERESOURCE', + 427810014: 'IFCUSHAPEPROFILEDEF', + 180925521: 'IFCUNITASSIGNMENT', + 630975310: 'IFCUNITARYCONTROLELEMENT', + 3179687236: 'IFCUNITARYCONTROLELEMENTTYPE', + 4292641817: 'IFCUNITARYEQUIPMENT', + 1911125066: 'IFCUNITARYEQUIPMENTTYPE', + 4207607924: 'IFCVALVE', + 728799441: 'IFCVALVETYPE', + 1417489154: 'IFCVECTOR', + 2799835756: 'IFCVERTEX', + 2759199220: 'IFCVERTEXLOOP', + 1907098498: 'IFCVERTEXPOINT', + 1530820697: 'IFCVIBRATIONDAMPER', + 3956297820: 'IFCVIBRATIONDAMPERTYPE', + 2391383451: 'IFCVIBRATIONISOLATOR', + 3313531582: 'IFCVIBRATIONISOLATORTYPE', + 2769231204: 'IFCVIRTUALELEMENT', + 891718957: 'IFCVIRTUALGRIDINTERSECTION', + 926996030: 'IFCVOIDINGFEATURE', + 2391406946: 'IFCWALL', + 4156078855: 'IFCWALLELEMENTEDCASE', + 3512223829: 'IFCWALLSTANDARDCASE', + 1898987631: 'IFCWALLTYPE', + 4237592921: 'IFCWASTETERMINAL', + 1133259667: 'IFCWASTETERMINALTYPE', + 3304561284: 'IFCWINDOW', + 336235671: 'IFCWINDOWLININGPROPERTIES', + 512836454: 'IFCWINDOWPANELPROPERTIES', + 486154966: 'IFCWINDOWSTANDARDCASE', + 1299126871: 'IFCWINDOWSTYLE', + 4009809668: 'IFCWINDOWTYPE', + 4088093105: 'IFCWORKCALENDAR', + 1028945134: 'IFCWORKCONTROL', + 4218914973: 'IFCWORKPLAN', + 3342526732: 'IFCWORKSCHEDULE', + 1236880293: 'IFCWORKTIME', + 2543172580: 'IFCZSHAPEPROFILEDEF', + 1033361043: 'IFCZONE', +}; + +class PropertyManager { + + constructor( state ) { + + this.state = state; + + } + + getExpressId( geometry, faceIndex ) { + + if ( ! geometry.index ) + return; + const geoIndex = geometry.index.array; + return geometry.attributes[ IdAttrName ].getX( geoIndex[ 3 * faceIndex ] ); + + } + + getItemProperties( modelID, id, recursive = false ) { + + return this.state.useJSON ? + { + ...this.state.models[ modelID ].jsonData[ id ] + } : + this.state.api.GetLine( modelID, id, recursive ); + + } + + getAllItemsOfType( modelID, type, verbose ) { + + return this.state.useJSON ? + this.getAllItemsOfTypeJSON( modelID, type, verbose ) : + this.getAllItemsOfTypeWebIfcAPI( modelID, type, verbose ); + + } + + getPropertySets( modelID, elementID, recursive = false ) { + + return this.state.useJSON ? + this.getPropertyJSON( modelID, elementID, recursive, PropsNames.psets ) : + this.getPropertyWebIfcAPI( modelID, elementID, recursive, PropsNames.psets ); + + } + + getTypeProperties( modelID, elementID, recursive = false ) { + + return this.state.useJSON ? + this.getPropertyJSON( modelID, elementID, recursive, PropsNames.type ) : + this.getPropertyWebIfcAPI( modelID, elementID, recursive, PropsNames.type ); + + } + + getMaterialsProperties( modelID, elementID, recursive = false ) { + + return this.state.useJSON ? + this.getPropertyJSON( modelID, elementID, recursive, PropsNames.materials ) : + this.getPropertyWebIfcAPI( modelID, elementID, recursive, PropsNames.materials ); + + } + + getSpatialStructure( modelID ) { + + return this.state.useJSON ? + this.getSpatialStructureJSON( modelID ) : + this.getSpatialStructureWebIfcAPI( modelID ); + + } + + getSpatialStructureJSON( modelID ) { + + const chunks = this.getSpatialTreeChunks( modelID ); + const projectID = this.getAllItemsOfTypeJSON( modelID, IFCPROJECT, false )[ 0 ]; + const project = this.newIfcProject( projectID ); + this.getSpatialNode( modelID, project, chunks ); + return { + ...project + }; + + } + + getSpatialStructureWebIfcAPI( modelID ) { + + const chunks = this.getSpatialTreeChunks( modelID ); + const projectID = this.state.api.GetLineIDsWithType( modelID, IFCPROJECT ).get( 0 ); + const project = this.newIfcProject( projectID ); + this.getSpatialNode( modelID, project, chunks ); + return project; + + } + + getAllItemsOfTypeJSON( modelID, type, verbose ) { + + const data = this.state.models[ modelID ].jsonData; + const typeName = IfcTypesMap[ type ]; + if ( ! typeName ) { + + throw new Error( `Type not found: ${type}` ); + + } + + return this.filterJSONItemsByType( data, typeName, verbose ); + + } + + filterJSONItemsByType( data, typeName, verbose ) { + + const result = []; + Object.keys( data ).forEach( key => { + + const numKey = parseInt( key ); + if ( data[ numKey ].type.toUpperCase() === typeName ) { + + result.push( verbose ? { + ...data[ numKey ] + } : numKey ); + + } + + } ); + return result; + + } + + getItemsByIDJSON( modelID, ids ) { + + const data = this.state.models[ modelID ].jsonData; + const result = []; + ids.forEach( id => result.push( { + ...data[ id ] + } ) ); + return result; + + } + + getPropertyJSON( modelID, elementID, recursive = false, propName ) { + + const resultIDs = this.getAllRelatedItemsOfTypeJSON( modelID, elementID, propName ); + const result = this.getItemsByIDJSON( modelID, resultIDs ); + if ( recursive ) { + + result.forEach( result => this.getJSONReferencesRecursively( modelID, result ) ); + + } + + return result; + + } + + getJSONReferencesRecursively( modelID, jsonObject ) { + + if ( jsonObject == undefined ) + return; + const keys = Object.keys( jsonObject ); + for ( let i = 0; i < keys.length; i ++ ) { + + const key = keys[ i ]; + this.getJSONItem( modelID, jsonObject, key ); + + } + + } + + getJSONItem( modelID, jsonObject, key ) { + + if ( Array.isArray( jsonObject[ key ] ) ) { + + return this.getMultipleJSONItems( modelID, jsonObject, key ); + + } + + if ( jsonObject[ key ] && jsonObject[ key ].type === 5 ) { + + jsonObject[ key ] = this.getItemsByIDJSON( modelID, [ jsonObject[ key ].value ] )[ 0 ]; + this.getJSONReferencesRecursively( modelID, jsonObject[ key ] ); + + } + + } + + getMultipleJSONItems( modelID, jsonObject, key ) { + + jsonObject[ key ] = jsonObject[ key ].map( ( item ) => { + + if ( item.type === 5 ) { + + item = this.getItemsByIDJSON( modelID, [ item.value ] )[ 0 ]; + this.getJSONReferencesRecursively( modelID, item ); + + } + + return item; + + } ); + + } + + getPropertyWebIfcAPI( modelID, elementID, recursive = false, propName ) { + + const propSetIds = this.getAllRelatedItemsOfTypeWebIfcAPI( modelID, elementID, propName ); + return propSetIds.map( ( id ) => this.state.api.GetLine( modelID, id, recursive ) ); + + } + + getAllItemsOfTypeWebIfcAPI( modelID, type, verbose ) { + + const items = []; + const lines = this.state.api.GetLineIDsWithType( modelID, type ); + for ( let i = 0; i < lines.size(); i ++ ) + items.push( lines.get( i ) ); + if ( verbose ) + return items.map( ( id ) => this.state.api.GetLine( modelID, id ) ); + return items; + + } + + newIfcProject( id ) { + + return { + expressID: id, + type: 'IFCPROJECT', + children: [] + }; + + } + + getSpatialTreeChunks( modelID ) { + + const treeChunks = {}; + const json = this.state.useJSON; + if ( json ) { + + this.getChunksJSON( modelID, treeChunks, PropsNames.aggregates ); + this.getChunksJSON( modelID, treeChunks, PropsNames.spatial ); + + } else { + + this.getChunksWebIfcAPI( modelID, treeChunks, PropsNames.aggregates ); + this.getChunksWebIfcAPI( modelID, treeChunks, PropsNames.spatial ); + + } + + return treeChunks; + + } + + getChunksJSON( modelID, chunks, propNames ) { + + const relation = this.getAllItemsOfTypeJSON( modelID, propNames.name, true ); + relation.forEach( rel => { + + this.saveChunk( chunks, propNames, rel ); + + } ); + + } + + getChunksWebIfcAPI( modelID, chunks, propNames ) { + + const relation = this.state.api.GetLineIDsWithType( modelID, propNames.name ); + for ( let i = 0; i < relation.size(); i ++ ) { + + const rel = this.state.api.GetLine( modelID, relation.get( i ), false ); + this.saveChunk( chunks, propNames, rel ); + + } + + } + + saveChunk( chunks, propNames, rel ) { + + const relating = rel[ propNames.relating ].value; + const related = rel[ propNames.related ].map( ( r ) => r.value ); + if ( chunks[ relating ] == undefined ) { + + chunks[ relating ] = related; + + } else { + + chunks[ relating ] = chunks[ relating ].concat( related ); + + } + + } + + getSpatialNode( modelID, node, treeChunks ) { + + this.getChildren( modelID, node, treeChunks, PropsNames.aggregates ); + this.getChildren( modelID, node, treeChunks, PropsNames.spatial ); + + } + + getChildren( modelID, node, treeChunks, propNames ) { + + const children = treeChunks[ node.expressID ]; + if ( children == undefined ) + return; + const prop = propNames.key; + node[ prop ] = children.map( ( child ) => { + + const node = this.newNode( modelID, child ); + this.getSpatialNode( modelID, node, treeChunks ); + return node; + + } ); + + } + + newNode( modelID, id ) { + + const typeName = this.getNodeType( modelID, id ); + return { + expressID: id, + type: typeName, + children: [] + }; + + } + + getNodeType( modelID, id ) { + + if ( this.state.useJSON ) + return this.state.models[ modelID ].jsonData[ id ].type; + const typeID = this.state.models[ modelID ].types[ id ]; + return IfcElements[ typeID ]; + + } + + getAllRelatedItemsOfTypeJSON( modelID, id, propNames ) { + + const lines = this.getAllItemsOfTypeJSON( modelID, propNames.name, true ); + const IDs = []; + lines.forEach( line => { + + const isRelated = this.isRelated( id, line, propNames ); + if ( isRelated ) + this.getRelated( line, propNames, IDs ); + + } ); + return IDs; + + } + + getAllRelatedItemsOfTypeWebIfcAPI( modelID, id, propNames ) { + + const lines = this.state.api.GetLineIDsWithType( modelID, propNames.name ); + const IDs = []; + for ( let i = 0; i < lines.size(); i ++ ) { + + const rel = this.state.api.GetLine( modelID, lines.get( i ) ); + const isRelated = this.isRelated( id, rel, propNames ); + if ( isRelated ) + this.getRelated( rel, propNames, IDs ); + + } + + return IDs; + + } + + getRelated( rel, propNames, IDs ) { + + const element = rel[ propNames.relating ]; + if ( ! Array.isArray( element ) ) + IDs.push( element.value ); + else + element.forEach( ( ele ) => IDs.push( ele.value ) ); + + } + + isRelated( id, rel, propNames ) { + + const relatedItems = rel[ propNames.related ]; + if ( Array.isArray( relatedItems ) ) { + + const values = relatedItems.map( ( item ) => item.value ); + return values.includes( id ); + + } + + return relatedItems.value === id; + + } + +} + +class TypeManager { + + constructor( state ) { + + this.state = state; + + } + + getAllTypes() { + + for ( const modelID in this.state.models ) { + + const types = this.state.models[ modelID ].types; + if ( Object.keys( types ).length == 0 ) + this.getAllTypesOfModel( parseInt( modelID ) ); + + } + + } + + getAllTypesOfModel( modelID ) { + + this.state.models[ modelID ].types; + const elements = Object.keys( IfcElements ).map( ( e ) => parseInt( e ) ); + const types = this.state.models[ modelID ].types; + elements.forEach( ( type ) => { + + const lines = this.state.api.GetLineIDsWithType( modelID, type ); + for ( let i = 0; i < lines.size(); i ++ ) + types[ lines.get( i ) ] = type; + + } ); + + } + +} + +let modelIdCounter = 0; +const nullIfcManagerErrorMessage = 'IfcManager is null!'; + +class IFCModel extends Mesh { + + constructor() { + + super( ...arguments ); + this.modelID = modelIdCounter ++; + this.ifcManager = null; + this.mesh = this; + + } + + setIFCManager( manager ) { + + this.ifcManager = manager; + + } + + setWasmPath( path ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.setWasmPath( path ); + + } + + close( scene ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.close( this.modelID, scene ); + + } + + getExpressId( geometry, faceIndex ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getExpressId( geometry, faceIndex ); + + } + + getAllItemsOfType( type, verbose ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getAllItemsOfType( this.modelID, type, verbose ); + + } + + getItemProperties( id, recursive = false ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getItemProperties( this.modelID, id, recursive ); + + } + + getPropertySets( id, recursive = false ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getPropertySets( this.modelID, id, recursive ); + + } + + getTypeProperties( id, recursive = false ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getTypeProperties( this.modelID, id, recursive ); + + } + + getIfcType( id ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getIfcType( this.modelID, id ); + + } + + getSpatialStructure() { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getSpatialStructure( this.modelID ); + + } + + getSubset( material ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + return this.ifcManager.getSubset( this.modelID, material ); + + } + + removeSubset( parent, material ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.removeSubset( this.modelID, parent, material ); + + } + + createSubset( config ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + const modelConfig = { + ...config, + modelID: this.modelID + }; + return this.ifcManager.createSubset( modelConfig ); + + } + + hideItems( ids ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.hideItems( this.modelID, ids ); + + } + + hideAllItems() { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.hideAllItems( this.modelID ); + + } + + showItems( ids ) { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.showItems( this.modelID, ids ); + + } + + showAllItems() { + + if ( this.ifcManager === null ) + throw new Error( nullIfcManagerErrorMessage ); + this.ifcManager.showAllItems( this.modelID ); + + } + +} + +class BvhManager { + + initializeMeshBVH( computeBoundsTree, disposeBoundsTree, acceleratedRaycast ) { + + this.computeBoundsTree = computeBoundsTree; + this.disposeBoundsTree = disposeBoundsTree; + this.acceleratedRaycast = acceleratedRaycast; + this.setupThreeMeshBVH(); + + } + + applyThreeMeshBVH( geometry ) { + + if ( this.computeBoundsTree ) + geometry.computeBoundsTree(); + + } + + setupThreeMeshBVH() { + + if ( ! this.computeBoundsTree || ! this.disposeBoundsTree || ! this.acceleratedRaycast ) + return; + BufferGeometry.prototype.computeBoundsTree = this.computeBoundsTree; + BufferGeometry.prototype.disposeBoundsTree = this.disposeBoundsTree; + Mesh.prototype.raycast = this.acceleratedRaycast; + + } + +} + +class ItemsHider { + + constructor( state ) { + + this.modelCoordinates = {}; + this.expressIDCoordinatesMap = {}; + this.state = state; + + } + + + + processCoordinates( modelID ) { + + const attributes = this.getAttributes( modelID ); + const ids = Array.from( attributes.expressID.array ); + this.expressIDCoordinatesMap[ modelID ] = {}; + for ( let i = 0; i < ids.length; i ++ ) { + + if ( ! this.expressIDCoordinatesMap[ modelID ][ ids[ i ] ] ) { + + this.expressIDCoordinatesMap[ modelID ][ ids[ i ] ] = []; + + } + + const current = this.expressIDCoordinatesMap[ modelID ]; + current[ ids[ i ] ].push( 3 * i ); + + } + + this.initializeCoordinates( modelID ); + + } + + hideItems( modelID, ids ) { + + this.editCoordinates( modelID, ids, true ); + + } + + showItems( modelID, ids ) { + + this.editCoordinates( modelID, ids, false ); + + } + + editCoordinates( modelID, ids, hide ) { + + const current = this.expressIDCoordinatesMap[ modelID ]; + const indices = []; + ids.forEach( ( id ) => { + + if ( current[ id ] ) + indices.push( ...current[ id ] ); + + } ); + const coords = this.getCoordinates( modelID ); + const initial = this.modelCoordinates[ modelID ]; + if ( hide ) + indices.forEach( i => coords.set( [ 0, 0, 0 ], i ) ); + else + indices.forEach( i => coords.set( [ initial[ i ], initial[ i + 1 ], initial[ i + 2 ] ], i ) ); + this.getAttributes( modelID ).position.needsUpdate = true; + + } + + showAllItems( modelID ) { + + if ( this.modelCoordinates[ modelID ] ) { + + this.resetCoordinates( modelID ); + this.getAttributes( modelID ).position.needsUpdate = true; + + } + + } + + hideAllItems( modelID ) { + + this.getCoordinates( modelID ).fill( 0 ); + this.getAttributes( modelID ).position.needsUpdate = true; + + } + + initializeCoordinates( modelID ) { + + const coordinates = this.getCoordinates( modelID ); + if ( ! this.modelCoordinates[ modelID ] ) { + + this.modelCoordinates[ modelID ] = new Float32Array( coordinates ); + + } + + } + + resetCoordinates( modelID ) { + + const initial = this.modelCoordinates[ modelID ]; + this.getCoordinates( modelID ).set( initial ); + + } + + getCoordinates( modelID ) { + + return this.getAttributes( modelID ).position.array; + + } + + getAttributes( modelID ) { + + return this.state.models[ modelID ].mesh.geometry.attributes; + + } + +} + +class IFCManager { + + constructor() { + + this.state = { + models: [], + api: new IfcAPI(), + useJSON: false + }; + this.BVH = new BvhManager(); + this.parser = new IFCParser( this.state, this.BVH ); + this.subsets = new SubsetManager( this.state, this.BVH ); + this.properties = new PropertyManager( this.state ); + this.types = new TypeManager( this.state ); + this.hider = new ItemsHider( this.state ); + + } + + async parse( buffer ) { + + const mesh = await this.parser.parse( buffer ); + this.state.useJSON ? this.disposeMemory() : this.types.getAllTypes(); + this.hider.processCoordinates( mesh.modelID ); + const model = new IFCModel( mesh.geometry, mesh.material ); + model.setIFCManager( this ); + return model; + + } + + setWasmPath( path ) { + + this.state.api.SetWasmPath( path ); + + } + + applyWebIfcConfig( settings ) { + + this.state.webIfcSettings = settings; + + } + + useJSONData( useJSON = true ) { + + this.state.useJSON = useJSON; + this.disposeMemory(); + + } + + addModelJSONData( modelID, data ) { + + const model = this.state.models[ modelID ]; + if ( model ) { + + model.jsonData = data; + + } + + } + + disposeMemory() { + + this.state.api = null; + this.state.api = new IfcAPI(); + + } + + setupThreeMeshBVH( computeBoundsTree, disposeBoundsTree, acceleratedRaycast ) { + + this.BVH.initializeMeshBVH( computeBoundsTree, disposeBoundsTree, acceleratedRaycast ); + + } + + close( modelID, scene ) { + + this.state.api.CloseModel( modelID ); + if ( scene ) { + + scene.remove( this.state.models[ modelID ].mesh ); + + } + + delete this.state.models[ modelID ]; + + } + + getExpressId( geometry, faceIndex ) { + + return this.properties.getExpressId( geometry, faceIndex ); + + } + + getAllItemsOfType( modelID, type, verbose ) { + + return this.properties.getAllItemsOfType( modelID, type, verbose ); + + } + + getItemProperties( modelID, id, recursive = false ) { + + return this.properties.getItemProperties( modelID, id, recursive ); + + } + + getPropertySets( modelID, id, recursive = false ) { + + return this.properties.getPropertySets( modelID, id, recursive ); + + } + + getTypeProperties( modelID, id, recursive = false ) { + + return this.properties.getTypeProperties( modelID, id, recursive ); + + } + + getMaterialsProperties( modelID, id, recursive = false ) { + + return this.properties.getMaterialsProperties( modelID, id, recursive ); + + } + + getIfcType( modelID, id ) { + + const typeID = this.state.models[ modelID ].types[ id ]; + return IfcElements[ typeID ]; + + } + + getSpatialStructure( modelID ) { + + return this.properties.getSpatialStructure( modelID ); + + } + + getSubset( modelID, material ) { + + return this.subsets.getSubset( modelID, material ); + + } + + removeSubset( modelID, parent, material ) { + + this.subsets.removeSubset( modelID, parent, material ); + + } + + createSubset( config ) { + + return this.subsets.createSubset( config ); + + } + + hideItems( modelID, ids ) { + + this.hider.hideItems( modelID, ids ); + + } + + hideAllItems( modelID ) { + + this.hider.hideAllItems( modelID ); + + } + + showItems( modelID, ids ) { + + this.hider.showItems( modelID, ids ); + + } + + showAllItems( modelID ) { + + this.hider.showAllItems( modelID ); + + } + +} + +class IFCLoader extends Loader { + + constructor( manager ) { + + super( manager ); + this.ifcManager = new IFCManager(); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, async function ( buffer ) { + + try { + + if ( typeof buffer == 'string' ) { + + throw new Error( 'IFC files must be given as a buffer!' ); + + } + + onLoad( await scope.parse( buffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( buffer ) { + + return this.ifcManager.parse( buffer ); + + } + +} + +export { IFCLoader }; +//# sourceMappingURL=IFCLoader.js.map diff --git a/jsm/loaders/KMZLoader.js b/jsm/loaders/KMZLoader.js new file mode 100644 index 0000000..5f43eb3 --- /dev/null +++ b/jsm/loaders/KMZLoader.js @@ -0,0 +1,130 @@ +import { + FileLoader, + Group, + Loader, + LoadingManager +} from 'three'; +import { ColladaLoader } from '../loaders/ColladaLoader.js'; +import * as fflate from '../libs/fflate.module.js'; + +class KMZLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + function findFile( url ) { + + for ( const path in zip ) { + + if ( path.slice( - url.length ) === url ) { + + return zip[ path ]; + + } + + } + + } + + const manager = new LoadingManager(); + manager.setURLModifier( function ( url ) { + + const image = findFile( url ); + + if ( image ) { + + console.log( 'Loading', url ); + + const blob = new Blob( [ image.buffer ], { type: 'application/octet-stream' } ); + return URL.createObjectURL( blob ); + + } + + return url; + + } ); + + // + + const zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef + + if ( zip[ 'doc.kml' ] ) { + + const xml = new DOMParser().parseFromString( fflate.strFromU8( zip[ 'doc.kml' ] ), 'application/xml' ); // eslint-disable-line no-undef + + const model = xml.querySelector( 'Placemark Model Link href' ); + + if ( model ) { + + const loader = new ColladaLoader( manager ); + return loader.parse( fflate.strFromU8( zip[ model.textContent ] ) ); // eslint-disable-line no-undef + + } + + } else { + + console.warn( 'KMZLoader: Missing doc.kml file.' ); + + for ( const path in zip ) { + + const extension = path.split( '.' ).pop().toLowerCase(); + + if ( extension === 'dae' ) { + + const loader = new ColladaLoader( manager ); + return loader.parse( fflate.strFromU8( zip[ path ] ) ); // eslint-disable-line no-undef + + } + + } + + } + + console.error( 'KMZLoader: Couldn\'t find .dae file.' ); + return { scene: new Group() }; + + } + +} + +export { KMZLoader }; diff --git a/jsm/loaders/KTX2Loader.js b/jsm/loaders/KTX2Loader.js new file mode 100644 index 0000000..3aaa757 --- /dev/null +++ b/jsm/loaders/KTX2Loader.js @@ -0,0 +1,591 @@ +/** + * Loader for KTX 2.0 GPU Texture containers. + * + * KTX 2.0 is a container format for various GPU texture formats. The loader + * supports Basis Universal GPU textures, which can be quickly transcoded to + * a wide variety of GPU texture compression formats. While KTX 2.0 also allows + * other hardware-specific formats, this loader does not yet parse them. + * + * References: + * - KTX: http://github.khronos.org/KTX-Specification/ + * - DFD: https://www.khronos.org/registry/DataFormat/specs/1.3/dataformat.1.3.html#basicdescriptor + */ + +import { + CompressedTexture, + FileLoader, + LinearEncoding, + LinearFilter, + LinearMipmapLinearFilter, + Loader, + RGBAFormat, + RGBA_ASTC_4x4_Format, + RGBA_BPTC_Format, + RGBA_ETC2_EAC_Format, + RGBA_PVRTC_4BPPV1_Format, + RGBA_S3TC_DXT5_Format, + RGB_ETC1_Format, + RGB_ETC2_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_S3TC_DXT1_Format, + sRGBEncoding, + UnsignedByteType +} from 'three'; +import { WorkerPool } from '../utils/WorkerPool.js'; + +const KTX2TransferSRGB = 2; +const KTX2_ALPHA_PREMULTIPLIED = 1; +const _taskCache = new WeakMap(); + +let _activeLoaders = 0; + +class KTX2Loader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.transcoderPath = ''; + this.transcoderBinary = null; + this.transcoderPending = null; + + this.workerPool = new WorkerPool(); + this.workerSourceURL = ''; + this.workerConfig = null; + + if ( typeof MSC_TRANSCODER !== 'undefined' ) { + + console.warn( + + 'THREE.KTX2Loader: Please update to latest "basis_transcoder".' + + ' "msc_basis_transcoder" is no longer supported in three.js r125+.' + + ); + + } + + } + + setTranscoderPath( path ) { + + this.transcoderPath = path; + + return this; + + } + + setWorkerLimit( num ) { + + this.workerPool.setWorkerLimit( num ); + + return this; + + } + + detectSupport( renderer ) { + + this.workerConfig = { + astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ), + etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ), + etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ), + dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ), + bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ), + pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) + || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' ) + }; + + + if ( renderer.capabilities.isWebGL2 ) { + + // https://github.com/mrdoob/three.js/pull/22928 + this.workerConfig.etc1Supported = false; + + } + + return this; + + } + + init() { + + if ( ! this.transcoderPending ) { + + // Load transcoder wrapper. + const jsLoader = new FileLoader( this.manager ); + jsLoader.setPath( this.transcoderPath ); + jsLoader.setWithCredentials( this.withCredentials ); + const jsContent = jsLoader.loadAsync( 'basis_transcoder.js' ); + + // Load transcoder WASM binary. + const binaryLoader = new FileLoader( this.manager ); + binaryLoader.setPath( this.transcoderPath ); + binaryLoader.setResponseType( 'arraybuffer' ); + binaryLoader.setWithCredentials( this.withCredentials ); + const binaryContent = binaryLoader.loadAsync( 'basis_transcoder.wasm' ); + + this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ) + .then( ( [ jsContent, binaryContent ] ) => { + + const fn = KTX2Loader.BasisWorker.toString(); + + const body = [ + '/* constants */', + 'let _EngineFormat = ' + JSON.stringify( KTX2Loader.EngineFormat ), + 'let _TranscoderFormat = ' + JSON.stringify( KTX2Loader.TranscoderFormat ), + 'let _BasisFormat = ' + JSON.stringify( KTX2Loader.BasisFormat ), + '/* basis_transcoder.js */', + jsContent, + '/* worker */', + fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) + ].join( '\n' ); + + this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) ); + this.transcoderBinary = binaryContent; + + this.workerPool.setWorkerCreator( () => { + + const worker = new Worker( this.workerSourceURL ); + const transcoderBinary = this.transcoderBinary.slice( 0 ); + + worker.postMessage( { type: 'init', config: this.workerConfig, transcoderBinary }, [ transcoderBinary ] ); + + return worker; + + } ); + + } ); + + if ( _activeLoaders > 0 ) { + + // Each instance loads a transcoder and allocates workers, increasing network and memory cost. + + console.warn( + + 'THREE.KTX2Loader: Multiple active KTX2 loaders may cause performance issues.' + + ' Use a single KTX2Loader instance, or call .dispose() on old instances.' + + ); + + } + + _activeLoaders ++; + + } + + return this.transcoderPending; + + } + + load( url, onLoad, onProgress, onError ) { + + if ( this.workerConfig === null ) { + + throw new Error( 'THREE.KTX2Loader: Missing initialization with `.detectSupport( renderer )`.' ); + + } + + const loader = new FileLoader( this.manager ); + + loader.setResponseType( 'arraybuffer' ); + loader.setWithCredentials( this.withCredentials ); + + const texture = new CompressedTexture(); + + loader.load( url, ( buffer ) => { + + // Check for an existing task using this buffer. A transferred buffer cannot be transferred + // again from this thread. + if ( _taskCache.has( buffer ) ) { + + const cachedTask = _taskCache.get( buffer ); + + return cachedTask.promise.then( onLoad ).catch( onError ); + + } + + this._createTexture( [ buffer ] ) + .then( function ( _texture ) { + + texture.copy( _texture ); + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } ) + .catch( onError ); + + }, onProgress, onError ); + + return texture; + + } + + _createTextureFrom( transcodeResult ) { + + const { mipmaps, width, height, format, type, error, dfdTransferFn, dfdFlags } = transcodeResult; + + if ( type === 'error' ) return Promise.reject( error ); + + const texture = new CompressedTexture( mipmaps, width, height, format, UnsignedByteType ); + texture.minFilter = mipmaps.length === 1 ? LinearFilter : LinearMipmapLinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.needsUpdate = true; + texture.encoding = dfdTransferFn === KTX2TransferSRGB ? sRGBEncoding : LinearEncoding; + texture.premultiplyAlpha = !! ( dfdFlags & KTX2_ALPHA_PREMULTIPLIED ); + + return texture; + + } + + /** + * @param {ArrayBuffer[]} buffers + * @param {object?} config + * @return {Promise} + */ + _createTexture( buffers, config = {} ) { + + const taskConfig = config; + const texturePending = this.init().then( () => { + + return this.workerPool.postMessage( { type: 'transcode', buffers, taskConfig: taskConfig }, buffers ); + + } ).then( ( e ) => this._createTextureFrom( e.data ) ); + + // Cache the task result. + _taskCache.set( buffers[ 0 ], { promise: texturePending } ); + + return texturePending; + + } + + dispose() { + + this.workerPool.dispose(); + if ( this.workerSourceURL ) URL.revokeObjectURL( this.workerSourceURL ); + + _activeLoaders --; + + return this; + + } + +} + + +/* 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; // eslint-disable-line no-undef + const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef + const BasisFormat = _BasisFormat; // eslint-disable-line no-undef + + 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.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, 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 ); // eslint-disable-line no-undef + + } ).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 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 levelInfo = ktx2File.getImageLevelInfo( mip, 0, 0 ); + const mipWidth = levelInfo.origWidth; + const mipHeight = levelInfo.origHeight; + const dst = new Uint8Array( ktx2File.getImageTranscodedSizeInBytes( mip, 0, 0, transcoderFormat ) ); + + const status = ktx2File.transcodeImage( + dst, + mip, + 0, + 0, + transcoderFormat, + 0, + - 1, + - 1, + ); + + if ( ! status ) { + + cleanup(); + throw new Error( 'THREE.KTX2Loader: .transcodeImage failed.' ); + + } + + mipmaps.push( { data: dst, 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; + + } + +}; + +export { KTX2Loader }; diff --git a/jsm/loaders/KTXLoader.js b/jsm/loaders/KTXLoader.js new file mode 100644 index 0000000..4e4e1c1 --- /dev/null +++ b/jsm/loaders/KTXLoader.js @@ -0,0 +1,176 @@ +import { + CompressedTextureLoader +} from 'three'; + +/** + * for description see https://www.khronos.org/opengles/sdk/tools/KTX/ + * for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ + * + * ported from https://github.com/BabylonJS/Babylon.js/blob/master/src/Misc/khronosTextureContainer.ts + */ + + +class KTXLoader extends CompressedTextureLoader { + + constructor( manager ) { + + super( manager ); + + } + + parse( buffer, loadMipmaps ) { + + const ktx = new KhronosTextureContainer( buffer, 1 ); + + return { + mipmaps: ktx.mipmaps( loadMipmaps ), + width: ktx.pixelWidth, + height: ktx.pixelHeight, + format: ktx.glInternalFormat, + isCubemap: ktx.numberOfFaces === 6, + mipmapCount: ktx.numberOfMipmapLevels + }; + + } + +} + + +const HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs) +// load types +const COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D() +//const COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D() +//const TEX_2D = 2; // uses a gl.texImage2D() +//const TEX_3D = 3; // uses a gl.texImage3D() + +class KhronosTextureContainer { + + /** + * @param {ArrayBuffer} arrayBuffer- contents of the KTX container file + * @param {number} facesExpected- should be either 1 or 6, based whether a cube texture or or + * @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented + * @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented + */ + constructor( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) { + + this.arrayBuffer = arrayBuffer; + + // Test that it is a ktx formatted file, based on the first 12 bytes, character representation is: + // '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n' + // 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A + const identifier = new Uint8Array( this.arrayBuffer, 0, 12 ); + if ( identifier[ 0 ] !== 0xAB || + identifier[ 1 ] !== 0x4B || + identifier[ 2 ] !== 0x54 || + identifier[ 3 ] !== 0x58 || + identifier[ 4 ] !== 0x20 || + identifier[ 5 ] !== 0x31 || + identifier[ 6 ] !== 0x31 || + identifier[ 7 ] !== 0xBB || + identifier[ 8 ] !== 0x0D || + identifier[ 9 ] !== 0x0A || + identifier[ 10 ] !== 0x1A || + identifier[ 11 ] !== 0x0A ) { + + console.error( 'texture missing KTX identifier' ); + return; + + } + + // load the reset of the header in native 32 bit uint + const dataSize = Uint32Array.BYTES_PER_ELEMENT; + const headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize ); + const endianness = headerDataView.getUint32( 0, true ); + const littleEndian = endianness === 0x04030201; + + this.glType = headerDataView.getUint32( 1 * dataSize, littleEndian ); // must be 0 for compressed textures + this.glTypeSize = headerDataView.getUint32( 2 * dataSize, littleEndian ); // must be 1 for compressed textures + this.glFormat = headerDataView.getUint32( 3 * dataSize, littleEndian ); // must be 0 for compressed textures + this.glInternalFormat = headerDataView.getUint32( 4 * dataSize, littleEndian ); // the value of arg passed to gl.compressedTexImage2D(,,x,,,,) + this.glBaseInternalFormat = headerDataView.getUint32( 5 * dataSize, littleEndian ); // specify GL_RGB, GL_RGBA, GL_ALPHA, etc (un-compressed only) + this.pixelWidth = headerDataView.getUint32( 6 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,x,,,) + this.pixelHeight = headerDataView.getUint32( 7 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,,x,,) + this.pixelDepth = headerDataView.getUint32( 8 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage3D(,,,,,x,,) + this.numberOfArrayElements = headerDataView.getUint32( 9 * dataSize, littleEndian ); // used for texture arrays + this.numberOfFaces = headerDataView.getUint32( 10 * dataSize, littleEndian ); // used for cubemap textures, should either be 1 or 6 + this.numberOfMipmapLevels = headerDataView.getUint32( 11 * dataSize, littleEndian ); // number of levels; disregard possibility of 0 for compressed textures + this.bytesOfKeyValueData = headerDataView.getUint32( 12 * dataSize, littleEndian ); // the amount of space after the header for meta-data + + // Make sure we have a compressed type. Not only reduces work, but probably better to let dev know they are not compressing. + if ( this.glType !== 0 ) { + + console.warn( 'only compressed formats currently supported' ); + return; + + } else { + + // value of zero is an indication to generate mipmaps @ runtime. Not usually allowed for compressed, so disregard. + this.numberOfMipmapLevels = Math.max( 1, this.numberOfMipmapLevels ); + + } + + if ( this.pixelHeight === 0 || this.pixelDepth !== 0 ) { + + console.warn( 'only 2D textures currently supported' ); + return; + + } + + if ( this.numberOfArrayElements !== 0 ) { + + console.warn( 'texture arrays not currently supported' ); + return; + + } + + if ( this.numberOfFaces !== facesExpected ) { + + console.warn( 'number of faces expected' + facesExpected + ', but found ' + this.numberOfFaces ); + return; + + } + + // we now have a completely validated file, so could use existence of loadType as success + // would need to make this more elaborate & adjust checks above to support more than one load type + this.loadType = COMPRESSED_2D; + + } + + mipmaps( loadMipmaps ) { + + const mipmaps = []; + + // initialize width & height for level 1 + let dataOffset = HEADER_LEN + this.bytesOfKeyValueData; + let width = this.pixelWidth; + let height = this.pixelHeight; + const mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1; + + for ( let level = 0; level < mipmapCount; level ++ ) { + + const imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps + dataOffset += 4; // size of the image + 4 for the imageSize field + + for ( let face = 0; face < this.numberOfFaces; face ++ ) { + + const byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize ); + + mipmaps.push( { 'data': byteArray, 'width': width, 'height': height } ); + + dataOffset += imageSize; + dataOffset += 3 - ( ( imageSize + 3 ) % 4 ); // add padding for odd sized image + + } + + width = Math.max( 1.0, width * 0.5 ); + height = Math.max( 1.0, height * 0.5 ); + + } + + return mipmaps; + + } + +} + +export { KTXLoader }; diff --git a/jsm/loaders/LDrawLoader.js b/jsm/loaders/LDrawLoader.js new file mode 100644 index 0000000..cb60c16 --- /dev/null +++ b/jsm/loaders/LDrawLoader.js @@ -0,0 +1,2403 @@ +import { + BufferAttribute, + BufferGeometry, + Color, + FileLoader, + Group, + LineBasicMaterial, + LineSegments, + Loader, + Matrix4, + Mesh, + MeshStandardMaterial, + ShaderMaterial, + UniformsLib, + UniformsUtils, + Vector3, + Ray +} from 'three'; + +// Special surface finish tag types. +// Note: "MATERIAL" tag (e.g. GLITTER, SPECKLE) is not implemented +const FINISH_TYPE_DEFAULT = 0; +const FINISH_TYPE_CHROME = 1; +const FINISH_TYPE_PEARLESCENT = 2; +const FINISH_TYPE_RUBBER = 3; +const FINISH_TYPE_MATTE_METALLIC = 4; +const FINISH_TYPE_METAL = 5; + +// State machine to search a subobject path. +// The LDraw standard establishes these various possible subfolders. +const FILE_LOCATION_AS_IS = 0; +const FILE_LOCATION_TRY_PARTS = 1; +const FILE_LOCATION_TRY_P = 2; +const FILE_LOCATION_TRY_MODELS = 3; +const FILE_LOCATION_TRY_RELATIVE = 4; +const FILE_LOCATION_TRY_ABSOLUTE = 5; +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(); + +class LDrawConditionalLineMaterial extends ShaderMaterial { + + constructor( parameters ) { + + super( { + + uniforms: UniformsUtils.merge( [ + UniformsLib.fog, + { + diffuse: { + value: new Color() + }, + opacity: { + value: 1.0 + } + } + ] ), + + vertexShader: /* glsl */` + attribute vec3 control0; + attribute vec3 control1; + attribute vec3 direction; + varying float discardFlag; + + #include + #include + #include + #include + #include + void main() { + #include + + vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 ); + gl_Position = projectionMatrix * mvPosition; + + // Transform the line segment ends and control points into camera clip space + vec4 c0 = projectionMatrix * modelViewMatrix * vec4( control0, 1.0 ); + vec4 c1 = projectionMatrix * modelViewMatrix * vec4( control1, 1.0 ); + vec4 p0 = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + vec4 p1 = projectionMatrix * modelViewMatrix * vec4( position + direction, 1.0 ); + + c0.xy /= c0.w; + c1.xy /= c1.w; + p0.xy /= p0.w; + p1.xy /= p1.w; + + // Get the direction of the segment and an orthogonal vector + vec2 dir = p1.xy - p0.xy; + vec2 norm = vec2( -dir.y, dir.x ); + + // Get control point directions from the line + vec2 c0dir = c0.xy - p1.xy; + vec2 c1dir = c1.xy - p1.xy; + + // If the vectors to the controls points are pointed in different directions away + // from the line segment then the line should not be drawn. + float d0 = dot( normalize( norm ), normalize( c0dir ) ); + float d1 = dot( normalize( norm ), normalize( c1dir ) ); + discardFlag = float( sign( d0 ) != sign( d1 ) ); + + #include + #include + #include + } + `, + + fragmentShader: /* glsl */` + uniform vec3 diffuse; + uniform float opacity; + varying float discardFlag; + + #include + #include + #include + #include + #include + void main() { + + if ( discardFlag > 0.5 ) discard; + + #include + vec3 outgoingLight = vec3( 0.0 ); + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + #include + outgoingLight = diffuseColor.rgb; // simple shader + gl_FragColor = vec4( outgoingLight, diffuseColor.a ); + #include + #include + #include + #include + } + `, + + } ); + + Object.defineProperties( this, { + + opacity: { + get: function () { + + return this.uniforms.opacity.value; + + }, + + set: function ( value ) { + + this.uniforms.opacity.value = value; + + } + }, + + color: { + get: function () { + + return this.uniforms.diffuse.value; + + } + } + + } ); + + this.setValues( parameters ); + this.isLDrawConditionalLineMaterial = true; + + } + +} + +class ConditionalLineSegments extends LineSegments { + + constructor( geometry, material ) { + + super( geometry, material ); + this.isConditionalLine = true; + + } + +} + +function generateFaceNormals( faces ) { + + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const face = faces[ i ]; + const vertices = face.vertices; + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + const v2 = vertices[ 2 ]; + + _tempVec0.subVectors( v1, v0 ); + _tempVec1.subVectors( v2, v1 ); + face.faceNormal = new Vector3() + .crossVectors( _tempVec0, _tempVec1 ) + .normalize(); + + } + +} + +const _ray = 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). + // -- + // And the vector values are initialize multiplied by 1 + 1e-10 to account for floating + // point errors on vertices along quantization boundaries. Ie after matrix multiplication + // vertices that should be merged might be set to "1.7" and "1.6999..." meaning they won't + // get merged. This added epsilon attempts to push these error values to the same quantized + // value for the sake of hashing. See "AT-ST mini" dishes. See mrdoob/three#23169. + + const hashMultiplier = ( 1 + 1e-10 ) * 1e2; + function hashVertex( v ) { + + const x = ~ ~ ( v.x * hashMultiplier ); + const y = ~ ~ ( v.y * hashMultiplier ); + const z = ~ ~ ( v.z * hashMultiplier ); + + return `${ x },${ y },${ z }`; + + } + + function hashEdge( v0, v1 ) { + + return `${ hashVertex( v0 ) }_${ hashVertex( v1 ) }`; + + } + + // converts the two vertices to a ray with a normalized direction and origin of 0, 0, 0 projected + // onto the original line. + function toNormalizedRay( v0, v1, targetRay ) { + + targetRay.direction.subVectors( v1, v0 ).normalize(); + + const scalar = v0.dot( targetRay.direction ); + targetRay.origin.copy( v0 ).addScaledVector( targetRay.direction, - scalar ); + + return targetRay; + + } + + function hashRay( ray ) { + + return hashEdge( ray.origin, ray.direction ); + + } + + const hardEdges = new Set(); + const hardEdgeRays = new Map(); + const halfEdgeList = {}; + const normals = []; + + // Save the list of hard edges by hash + for ( let i = 0, l = lineSegments.length; i < l; i ++ ) { + + const ls = lineSegments[ i ]; + const vertices = ls.vertices; + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + hardEdges.add( hashEdge( v0, v1 ) ); + hardEdges.add( hashEdge( v1, v0 ) ); + + // only generate the hard edge ray map if we're checking subsegments because it's more expensive to check + // and requires more memory. + if ( checkSubSegments ) { + + // add both ray directions to the map + const ray = toNormalizedRay( v0, v1, new Ray() ); + const rh1 = hashRay( ray ); + if ( ! hardEdgeRays.has( rh1 ) ) { + + toNormalizedRay( v1, v0, ray ); + const rh2 = hashRay( ray ); + + const info = { + ray, + distances: [], + }; + + hardEdgeRays.set( rh1, info ); + hardEdgeRays.set( rh2, info ); + + } + + // store both segments ends in min, max order in the distances array to check if a face edge is a + // subsegment later. + const info = hardEdgeRays.get( rh1 ); + let d0 = info.ray.direction.dot( v0 ); + let d1 = info.ray.direction.dot( v1 ); + if ( d0 > d1 ) { + + [ d0, d1 ] = [ d1, d0 ]; + + } + + info.distances.push( d0, d1 ); + + } + + } + + // track the half edges associated with each triangle + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const tri = faces[ i ]; + const vertices = tri.vertices; + const vertCount = vertices.length; + for ( let i2 = 0; i2 < vertCount; i2 ++ ) { + + const index = i2; + const next = ( i2 + 1 ) % vertCount; + const v0 = vertices[ index ]; + const v1 = vertices[ next ]; + const hash = hashEdge( v0, v1 ); + + // don't add the triangle if the edge is supposed to be hard + if ( hardEdges.has( hash ) ) { + + continue; + + } + + // if checking subsegments then check to see if this edge lies on a hard edge ray and whether its within any ray bounds + if ( checkSubSegments ) { + + toNormalizedRay( v0, v1, _ray ); + + const rayHash = hashRay( _ray ); + if ( hardEdgeRays.has( rayHash ) ) { + + const info = hardEdgeRays.get( rayHash ); + const { ray, distances } = info; + let d0 = ray.direction.dot( v0 ); + let d1 = ray.direction.dot( v1 ); + + if ( d0 > d1 ) { + + [ d0, d1 ] = [ d1, d0 ]; + + } + + // return early if the face edge is found to be a subsegment of a line edge meaning the edge will have "hard" normals + let found = false; + for ( let i = 0, l = distances.length; i < l; i += 2 ) { + + if ( d0 >= distances[ i ] && d1 <= distances[ i + 1 ] ) { + + found = true; + break; + + } + + } + + if ( found ) { + + continue; + + } + + } + + } + + const info = { + index: index, + tri: tri + }; + halfEdgeList[ hash ] = info; + + } + + } + + // Iterate until we've tried to connect all faces to share normals + while ( true ) { + + // Stop if there are no more faces left + let halfEdge = null; + for ( const key in halfEdgeList ) { + + halfEdge = halfEdgeList[ key ]; + break; + + } + + if ( halfEdge === null ) { + + break; + + } + + // Exhaustively find all connected faces + const queue = [ halfEdge ]; + while ( queue.length > 0 ) { + + // initialize all vertex normals in this triangle + const tri = queue.pop().tri; + const vertices = tri.vertices; + const vertNormals = tri.normals; + const faceNormal = tri.faceNormal; + + // Check if any edge is connected to another triangle edge + const vertCount = vertices.length; + for ( let i2 = 0; i2 < vertCount; i2 ++ ) { + + const index = i2; + const next = ( i2 + 1 ) % vertCount; + const v0 = vertices[ index ]; + const v1 = vertices[ next ]; + + // delete this triangle from the list so it won't be found again + const hash = hashEdge( v0, v1 ); + delete halfEdgeList[ hash ]; + + const reverseHash = hashEdge( v1, v0 ); + const otherInfo = halfEdgeList[ reverseHash ]; + if ( otherInfo ) { + + const otherTri = otherInfo.tri; + const otherIndex = otherInfo.index; + const otherNormals = otherTri.normals; + const otherVertCount = otherNormals.length; + const otherFaceNormal = otherTri.faceNormal; + + // NOTE: If the angle between faces is > 67.5 degrees then assume it's + // hard edge. There are some cases where the line segments do not line up exactly + // with or span multiple triangle edges (see Lunar Vehicle wheels). + if ( Math.abs( otherTri.faceNormal.dot( tri.faceNormal ) ) < 0.25 ) { + + continue; + + } + + // if this triangle has already been traversed then it won't be in + // the halfEdgeList. If it has not then add it to the queue and delete + // it so it won't be found again. + if ( reverseHash in halfEdgeList ) { + + queue.push( otherInfo ); + delete halfEdgeList[ reverseHash ]; + + } + + // share the first normal + const otherNext = ( otherIndex + 1 ) % otherVertCount; + if ( + vertNormals[ index ] && otherNormals[ otherNext ] && + vertNormals[ index ] !== otherNormals[ otherNext ] + ) { + + otherNormals[ otherNext ].norm.add( vertNormals[ index ].norm ); + vertNormals[ index ].norm = otherNormals[ otherNext ].norm; + + } + + let sharedNormal1 = vertNormals[ index ] || otherNormals[ otherNext ]; + if ( sharedNormal1 === null ) { + + // it's possible to encounter an edge of a triangle that has already been traversed meaning + // both edges already have different normals defined and shared. To work around this we create + // a wrapper object so when those edges are merged the normals can be updated everywhere. + sharedNormal1 = { norm: new Vector3() }; + normals.push( sharedNormal1.norm ); + + } + + if ( vertNormals[ index ] === null ) { + + vertNormals[ index ] = sharedNormal1; + sharedNormal1.norm.add( faceNormal ); + + } + + if ( otherNormals[ otherNext ] === null ) { + + otherNormals[ otherNext ] = sharedNormal1; + sharedNormal1.norm.add( otherFaceNormal ); + + } + + // share the second normal + if ( + vertNormals[ next ] && otherNormals[ otherIndex ] && + vertNormals[ next ] !== otherNormals[ otherIndex ] + ) { + + otherNormals[ otherIndex ].norm.add( vertNormals[ next ].norm ); + vertNormals[ next ].norm = otherNormals[ otherIndex ].norm; + + } + + let sharedNormal2 = vertNormals[ next ] || otherNormals[ otherIndex ]; + if ( sharedNormal2 === null ) { + + sharedNormal2 = { norm: new Vector3() }; + normals.push( sharedNormal2.norm ); + + } + + if ( vertNormals[ next ] === null ) { + + vertNormals[ next ] = sharedNormal2; + sharedNormal2.norm.add( faceNormal ); + + } + + if ( otherNormals[ otherIndex ] === null ) { + + otherNormals[ otherIndex ] = sharedNormal2; + sharedNormal2.norm.add( otherFaceNormal ); + + } + + } + + } + + } + + } + + // The normals of each face have been added up so now we average them by normalizing the vector. + for ( let i = 0, l = normals.length; i < l; i ++ ) { + + normals[ i ].normalize(); + + } + +} + +function isPartType( type ) { + + return type === 'Part' || type === 'Unofficial_Part'; + +} + +function isPrimitiveType( type ) { + + return /primitive/i.test( type ) || type === 'Subpart'; + +} + +class LineParser { + + constructor( line, lineNumber ) { + + this.line = line; + this.lineLength = line.length; + this.currentCharIndex = 0; + this.currentChar = ' '; + this.lineNumber = lineNumber; + + } + + seekNonSpace() { + + while ( this.currentCharIndex < this.lineLength ) { + + this.currentChar = this.line.charAt( this.currentCharIndex ); + + if ( this.currentChar !== ' ' && this.currentChar !== '\t' ) { + + return; + + } + + this.currentCharIndex ++; + + } + + } + + getToken() { + + const pos0 = this.currentCharIndex ++; + + // Seek space + while ( this.currentCharIndex < this.lineLength ) { + + this.currentChar = this.line.charAt( this.currentCharIndex ); + + if ( this.currentChar === ' ' || this.currentChar === '\t' ) { + + break; + + } + + this.currentCharIndex ++; + + } + + const pos1 = this.currentCharIndex; + + this.seekNonSpace(); + + return this.line.substring( pos0, pos1 ); + + } + + getVector() { + + return new Vector3( parseFloat( this.getToken() ), parseFloat( this.getToken() ), parseFloat( this.getToken() ) ); + + } + + getRemainingString() { + + return this.line.substring( this.currentCharIndex, this.lineLength ); + + } + + isAtTheEnd() { + + return this.currentCharIndex >= this.lineLength; + + } + + setToEnd() { + + this.currentCharIndex = this.lineLength; + + } + + getLineNumberString() { + + return this.lineNumber >= 0 ? ' at line ' + this.lineNumber : ''; + + } + +} + +// Fetches and parses an intermediate representation of LDraw parts files. +class LDrawParsedCache { + + constructor( loader ) { + + this.loader = loader; + this._cache = {}; + + } + + cloneResult( original ) { + + const result = {}; + + // vertices are transformed and normals computed before being converted to geometry + // so these pieces must be cloned. + result.faces = original.faces.map( face => { + + return { + colorCode: face.colorCode, + material: face.material, + vertices: face.vertices.map( v => v.clone() ), + normals: face.normals.map( () => null ), + faceNormal: null + }; + + } ); + + result.conditionalSegments = original.conditionalSegments.map( face => { + + return { + colorCode: face.colorCode, + material: face.material, + vertices: face.vertices.map( v => v.clone() ), + controlPoints: face.controlPoints.map( v => v.clone() ) + }; + + } ); + + result.lineSegments = original.lineSegments.map( face => { + + return { + colorCode: face.colorCode, + material: face.material, + vertices: face.vertices.map( v => v.clone() ) + }; + + } ); + + // none if this is subsequently modified + result.type = original.type; + result.category = original.category; + result.keywords = original.keywords; + result.subobjects = original.subobjects; + result.totalFaces = original.totalFaces; + result.startingConstructionStep = original.startingConstructionStep; + result.materials = original.materials; + result.group = null; + return result; + + } + + async fetchData( fileName ) { + + let triedLowerCase = false; + let locationState = FILE_LOCATION_AS_IS; + while ( locationState !== FILE_LOCATION_NOT_FOUND ) { + + let subobjectURL = fileName; + switch ( locationState ) { + + case FILE_LOCATION_AS_IS: + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_PARTS: + subobjectURL = 'parts/' + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_P: + subobjectURL = 'p/' + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_MODELS: + subobjectURL = 'models/' + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_RELATIVE: + subobjectURL = fileName.substring( 0, fileName.lastIndexOf( '/' ) + 1 ) + subobjectURL; + locationState = locationState + 1; + break; + + case FILE_LOCATION_TRY_ABSOLUTE: + + if ( triedLowerCase ) { + + // Try absolute path + locationState = FILE_LOCATION_NOT_FOUND; + + } else { + + // Next attempt is lower case + fileName = fileName.toLowerCase(); + subobjectURL = fileName; + triedLowerCase = true; + locationState = FILE_LOCATION_AS_IS; + + } + + break; + + } + + const loader = this.loader; + const fileLoader = new FileLoader( loader.manager ); + fileLoader.setPath( loader.partsLibraryPath ); + fileLoader.setRequestHeader( loader.requestHeader ); + fileLoader.setWithCredentials( loader.withCredentials ); + + try { + + const text = await fileLoader.loadAsync( subobjectURL ); + return text; + + } catch { + + continue; + + } + + } + + throw new Error( 'LDrawLoader: Subobject "' + fileName + '" could not be loaded.' ); + + } + + parse( text, fileName = null ) { + + const loader = this.loader; + + // final results + const faces = []; + const lineSegments = []; + const conditionalSegments = []; + const subobjects = []; + const materials = {}; + + const getLocalMaterial = colorCode => { + + return materials[ colorCode ] || null; + + }; + + let type = 'Model'; + let category = null; + let keywords = null; + let totalFaces = 0; + + // split into lines + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + const lines = text.split( '\n' ); + const numLines = lines.length; + + let parsingEmbeddedFiles = false; + let currentEmbeddedFileName = null; + let currentEmbeddedText = null; + + let bfcCertified = false; + let bfcCCW = true; + let bfcInverted = false; + let bfcCull = true; + + let startingConstructionStep = false; + + // Parse all line commands + for ( let lineIndex = 0; lineIndex < numLines; lineIndex ++ ) { + + const line = lines[ lineIndex ]; + + if ( line.length === 0 ) continue; + + if ( parsingEmbeddedFiles ) { + + if ( line.startsWith( '0 FILE ' ) ) { + + // Save previous embedded file in the cache + this.setData( currentEmbeddedFileName, currentEmbeddedText ); + + // New embedded text file + currentEmbeddedFileName = line.substring( 7 ); + currentEmbeddedText = ''; + + } else { + + currentEmbeddedText += line + '\n'; + + } + + continue; + + } + + const lp = new LineParser( line, lineIndex + 1 ); + lp.seekNonSpace(); + + if ( lp.isAtTheEnd() ) { + + // Empty line + continue; + + } + + // Parse the line type + const lineType = lp.getToken(); + + let material; + let colorCode; + let segment; + let ccw; + let doubleSided; + let v0, v1, v2, v3, c0, c1; + + switch ( lineType ) { + + // Line type 0: Comment or META + case '0': + + // Parse meta directive + const meta = lp.getToken(); + + if ( meta ) { + + switch ( meta ) { + + case '!LDRAW_ORG': + + type = lp.getToken(); + break; + + case '!COLOUR': + + material = loader.parseColorMetaDirective( lp ); + if ( material ) { + + materials[ material.userData.code ] = material; + + } else { + + console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() ); + + } + + break; + + case '!CATEGORY': + + category = lp.getToken(); + break; + + case '!KEYWORDS': + + const newKeywords = lp.getRemainingString().split( ',' ); + if ( newKeywords.length > 0 ) { + + if ( ! keywords ) { + + keywords = []; + + } + + newKeywords.forEach( function ( keyword ) { + + keywords.push( keyword.trim() ); + + } ); + + } + + break; + + case 'FILE': + + if ( lineIndex > 0 ) { + + // Start embedded text files parsing + parsingEmbeddedFiles = true; + currentEmbeddedFileName = lp.getRemainingString(); + currentEmbeddedText = ''; + + bfcCertified = false; + bfcCCW = true; + + } + + break; + + case 'BFC': + + // Changes to the backface culling state + while ( ! lp.isAtTheEnd() ) { + + const token = lp.getToken(); + + switch ( token ) { + + case 'CERTIFY': + case 'NOCERTIFY': + + bfcCertified = token === 'CERTIFY'; + bfcCCW = true; + + break; + + case 'CW': + case 'CCW': + + bfcCCW = token === 'CCW'; + + break; + + case 'INVERTNEXT': + + bfcInverted = true; + + break; + + case 'CLIP': + case 'NOCLIP': + + bfcCull = token === 'CLIP'; + + break; + + default: + + console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' ); + + break; + + } + + } + + break; + + case 'STEP': + + startingConstructionStep = true; + + break; + + default: + // Other meta directives are not implemented + break; + + } + + } + + break; + + // Line type 1: Sub-object file + case '1': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + + const posX = parseFloat( lp.getToken() ); + const posY = parseFloat( lp.getToken() ); + const posZ = parseFloat( lp.getToken() ); + const m0 = parseFloat( lp.getToken() ); + const m1 = parseFloat( lp.getToken() ); + const m2 = parseFloat( lp.getToken() ); + const m3 = parseFloat( lp.getToken() ); + const m4 = parseFloat( lp.getToken() ); + const m5 = parseFloat( lp.getToken() ); + const m6 = parseFloat( lp.getToken() ); + const m7 = parseFloat( lp.getToken() ); + const m8 = parseFloat( lp.getToken() ); + + const matrix = new Matrix4().set( + m0, m1, m2, posX, + m3, m4, m5, posY, + m6, m7, m8, posZ, + 0, 0, 0, 1 + ); + + let fileName = lp.getRemainingString().trim().replace( /\\/g, '/' ); + + if ( loader.fileMap[ fileName ] ) { + + // Found the subobject path in the preloaded file path map + fileName = loader.fileMap[ fileName ]; + + } else { + + // Standardized subfolders + if ( fileName.startsWith( 's/' ) ) { + + fileName = 'parts/' + fileName; + + } else if ( fileName.startsWith( '48/' ) ) { + + fileName = 'p/' + fileName; + + } + + } + + subobjects.push( { + material: material, + colorCode: colorCode, + matrix: matrix, + fileName: fileName, + inverted: bfcInverted, + startingConstructionStep: startingConstructionStep + } ); + + bfcInverted = false; + + break; + + // Line type 2: Line segment + case '2': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + v0 = lp.getVector(); + v1 = lp.getVector(); + + segment = { + material: material, + colorCode: colorCode, + vertices: [ v0, v1 ], + }; + + lineSegments.push( segment ); + + break; + + // Line type 5: Conditional Line segment + case '5': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + v0 = lp.getVector(); + v1 = lp.getVector(); + c0 = lp.getVector(); + c1 = lp.getVector(); + + segment = { + material: material, + colorCode: colorCode, + vertices: [ v0, v1 ], + controlPoints: [ c0, c1 ], + }; + + conditionalSegments.push( segment ); + + break; + + // Line type 3: Triangle + case '3': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + ccw = bfcCCW; + doubleSided = ! bfcCertified || ! bfcCull; + + if ( ccw === true ) { + + v0 = lp.getVector(); + v1 = lp.getVector(); + v2 = lp.getVector(); + + } else { + + v2 = lp.getVector(); + v1 = lp.getVector(); + v0 = lp.getVector(); + + } + + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v0, v1, v2 ], + normals: [ null, null, null ], + } ); + totalFaces ++; + + if ( doubleSided === true ) { + + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v2, v1, v0 ], + normals: [ null, null, null ], + } ); + totalFaces ++; + + } + + break; + + // Line type 4: Quadrilateral + case '4': + + colorCode = lp.getToken(); + material = getLocalMaterial( colorCode ); + ccw = bfcCCW; + doubleSided = ! bfcCertified || ! bfcCull; + + if ( ccw === true ) { + + v0 = lp.getVector(); + v1 = lp.getVector(); + v2 = lp.getVector(); + v3 = lp.getVector(); + + } else { + + v3 = lp.getVector(); + v2 = lp.getVector(); + v1 = lp.getVector(); + v0 = lp.getVector(); + + } + + // specifically place the triangle diagonal in the v0 and v1 slots so we can + // account for the doubling of vertices later when smoothing normals. + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v0, v1, v2, v3 ], + normals: [ null, null, null, null ], + } ); + totalFaces += 2; + + if ( doubleSided === true ) { + + faces.push( { + material: material, + colorCode: colorCode, + faceNormal: null, + vertices: [ v3, v2, v1, v0 ], + normals: [ null, null, null, null ], + } ); + totalFaces += 2; + + } + + break; + + default: + throw new Error( 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.' ); + + } + + } + + if ( parsingEmbeddedFiles ) { + + this.setData( currentEmbeddedFileName, currentEmbeddedText ); + + } + + return { + faces, + conditionalSegments, + lineSegments, + type, + category, + keywords, + subobjects, + totalFaces, + startingConstructionStep, + materials, + fileName, + group: null + }; + + } + + // returns an (optionally cloned) instance of the data + getData( fileName, clone = true ) { + + const key = fileName.toLowerCase(); + const result = this._cache[ key ]; + if ( result === null || result instanceof Promise ) { + + return null; + + } + + if ( clone ) { + + return this.cloneResult( result ); + + } else { + + return result; + + } + + } + + // kicks off a fetch and parse of the requested data if it hasn't already been loaded. Returns when + // the data is ready to use and can be retrieved synchronously with "getData". + async ensureDataLoaded( fileName ) { + + const key = fileName.toLowerCase(); + if ( ! ( key in this._cache ) ) { + + // replace the promise with a copy of the parsed data for immediate processing + this._cache[ key ] = this.fetchData( fileName ).then( text => { + + const info = this.parse( text, fileName ); + this._cache[ key ] = info; + return info; + + } ); + + } + + await this._cache[ key ]; + + } + + // sets the data in the cache from parsed data + setData( fileName, text ) { + + const key = fileName.toLowerCase(); + this._cache[ key ] = this.parse( text, fileName ); + + } + +} + +// returns the material for an associated color code. If the color code is 16 for a face or 24 for +// an edge then the passthroughColorCode is used. +function getMaterialFromCode( colorCode, parentColorCode, materialHierarchy, forEdge ) { + + const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE; + if ( isPassthrough ) { + + colorCode = parentColorCode; + + } + + return materialHierarchy[ colorCode ] || null; + +} + +// Class used to parse and build LDraw parts as three.js objects and cache them if they're a "Part" type. +class LDrawPartsGeometryCache { + + constructor( loader ) { + + this.loader = loader; + this.parseCache = new LDrawParsedCache( loader ); + this._cache = {}; + + } + + // Convert the given file information into a mesh by processing subobjects. + async processIntoMesh( info ) { + + const loader = this.loader; + const parseCache = this.parseCache; + const faceMaterials = new Set(); + + // Processes the part subobject information to load child parts and merge geometry onto part + // piece object. + const processInfoSubobjects = async ( info, subobject = null ) => { + + const subobjects = info.subobjects; + const promises = []; + + // Trigger load of all subobjects. If a subobject isn't a primitive then load it as a separate + // group which lets instruction steps apply correctly. + for ( let i = 0, l = subobjects.length; i < l; i ++ ) { + + const subobject = subobjects[ i ]; + const promise = parseCache.ensureDataLoaded( subobject.fileName ).then( () => { + + const subobjectInfo = parseCache.getData( subobject.fileName, false ); + if ( ! isPrimitiveType( subobjectInfo.type ) ) { + + return this.loadModel( subobject.fileName ).catch( error => { + + console.warn( error ); + return null; + + } ); + + } + + return processInfoSubobjects( parseCache.getData( subobject.fileName ), subobject ); + + } ); + + promises.push( promise ); + + } + + const group = new Group(); + group.userData.category = info.category; + group.userData.keywords = info.keywords; + info.group = group; + + const subobjectInfos = await Promise.all( promises ); + for ( let i = 0, l = subobjectInfos.length; i < l; i ++ ) { + + const subobject = info.subobjects[ i ]; + const subobjectInfo = subobjectInfos[ i ]; + + if ( subobjectInfo === null ) { + + // the subobject failed to load + continue; + + } + + // if the subobject was loaded as a separate group then apply the parent scopes materials + if ( subobjectInfo.isGroup ) { + + const subobjectGroup = subobjectInfo; + subobject.matrix.decompose( subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale ); + subobjectGroup.userData.startingConstructionStep = subobject.startingConstructionStep; + subobjectGroup.name = subobject.fileName; + + loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials ); + + group.add( subobjectGroup ); + continue; + + } + + // add the subobject group if it has children in case it has both children and primitives + if ( subobjectInfo.group.children.length ) { + + group.add( subobjectInfo.group ); + + } + + // transform the primitives into the local space of the parent piece and append them to + // to the parent primitives list. + const parentLineSegments = info.lineSegments; + const parentConditionalSegments = info.conditionalSegments; + const parentFaces = info.faces; + + const lineSegments = subobjectInfo.lineSegments; + const conditionalSegments = subobjectInfo.conditionalSegments; + + const faces = subobjectInfo.faces; + const matrix = subobject.matrix; + const inverted = subobject.inverted; + const matrixScaleInverted = matrix.determinant() < 0; + const colorCode = subobject.colorCode; + + const lineColorCode = colorCode === MAIN_COLOUR_CODE ? MAIN_EDGE_COLOUR_CODE : colorCode; + for ( let i = 0, l = lineSegments.length; i < l; i ++ ) { + + const ls = lineSegments[ i ]; + const vertices = ls.vertices; + vertices[ 0 ].applyMatrix4( matrix ); + vertices[ 1 ].applyMatrix4( matrix ); + ls.colorCode = ls.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : ls.colorCode; + ls.material = ls.material || getMaterialFromCode( ls.colorCode, ls.colorCode, info.materials, true ); + + parentLineSegments.push( ls ); + + } + + for ( let i = 0, l = conditionalSegments.length; i < l; i ++ ) { + + const os = conditionalSegments[ i ]; + const vertices = os.vertices; + const controlPoints = os.controlPoints; + vertices[ 0 ].applyMatrix4( matrix ); + vertices[ 1 ].applyMatrix4( matrix ); + controlPoints[ 0 ].applyMatrix4( matrix ); + controlPoints[ 1 ].applyMatrix4( matrix ); + os.colorCode = os.colorCode === MAIN_EDGE_COLOUR_CODE ? lineColorCode : os.colorCode; + os.material = os.material || getMaterialFromCode( os.colorCode, os.colorCode, info.materials, true ); + + parentConditionalSegments.push( os ); + + } + + for ( let i = 0, l = faces.length; i < l; i ++ ) { + + const tri = faces[ i ]; + const vertices = tri.vertices; + for ( let i = 0, l = vertices.length; i < l; i ++ ) { + + vertices[ i ].applyMatrix4( matrix ); + + } + + tri.colorCode = tri.colorCode === MAIN_COLOUR_CODE ? colorCode : tri.colorCode; + tri.material = tri.material || getMaterialFromCode( tri.colorCode, colorCode, info.materials, false ); + faceMaterials.add( tri.colorCode ); + + // If the scale of the object is negated then the triangle winding order + // needs to be flipped. + if ( matrixScaleInverted !== inverted ) { + + vertices.reverse(); + + } + + parentFaces.push( tri ); + + } + + info.totalFaces += subobjectInfo.totalFaces; + + } + + // Apply the parent subobjects pass through material code to this object. This is done several times due + // to material scoping. + if ( subobject ) { + + loader.applyMaterialsToMesh( group, subobject.colorCode, info.materials ); + + } + + return info; + + }; + + // Track material use to see if we need to use the normal smooth slow path for hard edges. + for ( let i = 0, l = info.faces; i < l; i ++ ) { + + faceMaterials.add( info.faces[ i ].colorCode ); + + } + + await processInfoSubobjects( info ); + + if ( loader.smoothNormals ) { + + const checkSubSegments = faceMaterials.size > 1; + generateFaceNormals( info.faces ); + smoothNormals( info.faces, info.lineSegments, checkSubSegments ); + + } + + // Add the primitive objects and metadata. + const group = info.group; + if ( info.faces.length > 0 ) { + + group.add( createObject( info.faces, 3, false, info.totalFaces ) ); + + } + + if ( info.lineSegments.length > 0 ) { + + group.add( createObject( info.lineSegments, 2 ) ); + + } + + if ( info.conditionalSegments.length > 0 ) { + + group.add( createObject( info.conditionalSegments, 2, true ) ); + + } + + return group; + + } + + hasCachedModel( fileName ) { + + return fileName !== null && fileName.toLowerCase() in this._cache; + + } + + async getCachedModel( fileName ) { + + if ( fileName !== null && this.hasCachedModel( fileName ) ) { + + const key = fileName.toLowerCase(); + const group = await this._cache[ key ]; + return group.clone(); + + } else { + + return null; + + } + + } + + // Loads and parses the model with the given file name. Returns a cached copy if available. + async loadModel( fileName ) { + + const parseCache = this.parseCache; + const key = fileName.toLowerCase(); + if ( this.hasCachedModel( fileName ) ) { + + // Return cached model if available. + return this.getCachedModel( fileName ); + + } else { + + // Otherwise parse a new model. + // Ensure the file data is loaded and pre parsed. + await parseCache.ensureDataLoaded( fileName ); + + const info = parseCache.getData( fileName ); + const promise = this.processIntoMesh( info ); + + // Now that the file has loaded it's possible that another part parse has been waiting in parallel + // so check the cache again to see if it's been added since the last async operation so we don't + // do unnecessary work. + if ( this.hasCachedModel( fileName ) ) { + + return this.getCachedModel( fileName ); + + } + + // Cache object if it's a part so it can be reused later. + if ( isPartType( info.type ) ) { + + this._cache[ key ] = promise; + + } + + // return a copy + const group = await promise; + return group.clone(); + + } + + } + + // parses the given model text into a renderable object. Returns cached copy if available. + async parseModel( text ) { + + const parseCache = this.parseCache; + const info = parseCache.parse( text ); + if ( isPartType( info.type ) && this.hasCachedModel( info.fileName ) ) { + + return this.getCachedModel( info.fileName ); + + } + + return this.processIntoMesh( info ); + + } + +} + +function sortByMaterial( a, b ) { + + if ( a.colorCode === b.colorCode ) { + + return 0; + + } + + if ( a.colorCode < b.colorCode ) { + + return - 1; + + } + + return 1; + +} + +function createObject( elements, elementSize, isConditionalSegments = false, totalElements = null ) { + + // Creates a LineSegments (elementSize = 2) or a Mesh (elementSize = 3 ) + // With per face / segment material, implemented with mesh groups and materials array + + // Sort the faces or line segments by color code to make later the mesh groups + elements.sort( sortByMaterial ); + + if ( totalElements === null ) { + + totalElements = elements.length; + + } + + const positions = new Float32Array( elementSize * totalElements * 3 ); + const normals = elementSize === 3 ? new Float32Array( elementSize * totalElements * 3 ) : null; + const materials = []; + + const quadArray = new Array( 6 ); + const bufferGeometry = new BufferGeometry(); + let prevMaterial = null; + let index0 = 0; + let numGroupVerts = 0; + let offset = 0; + + for ( let iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) { + + const elem = elements[ iElem ]; + let vertices = elem.vertices; + if ( vertices.length === 4 ) { + + quadArray[ 0 ] = vertices[ 0 ]; + quadArray[ 1 ] = vertices[ 1 ]; + quadArray[ 2 ] = vertices[ 2 ]; + quadArray[ 3 ] = vertices[ 0 ]; + quadArray[ 4 ] = vertices[ 2 ]; + quadArray[ 5 ] = vertices[ 3 ]; + vertices = quadArray; + + } + + for ( let j = 0, l = vertices.length; j < l; j ++ ) { + + const v = vertices[ j ]; + const index = offset + j * 3; + positions[ index + 0 ] = v.x; + positions[ index + 1 ] = v.y; + positions[ index + 2 ] = v.z; + + } + + // create the normals array if this is a set of faces + if ( elementSize === 3 ) { + + if ( ! elem.faceNormal ) { + + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + const v2 = vertices[ 2 ]; + _tempVec0.subVectors( v1, v0 ); + _tempVec1.subVectors( v2, v1 ); + elem.faceNormal = new Vector3() + .crossVectors( _tempVec0, _tempVec1 ) + .normalize(); + + } + + let elemNormals = elem.normals; + if ( elemNormals.length === 4 ) { + + quadArray[ 0 ] = elemNormals[ 0 ]; + quadArray[ 1 ] = elemNormals[ 1 ]; + quadArray[ 2 ] = elemNormals[ 2 ]; + quadArray[ 3 ] = elemNormals[ 0 ]; + quadArray[ 4 ] = elemNormals[ 2 ]; + quadArray[ 5 ] = elemNormals[ 3 ]; + elemNormals = quadArray; + + } + + for ( let j = 0, l = elemNormals.length; j < l; j ++ ) { + + // use face normal if a vertex normal is not provided + let n = elem.faceNormal; + if ( elemNormals[ j ] ) { + + n = elemNormals[ j ].norm; + + } + + const index = offset + j * 3; + normals[ index + 0 ] = n.x; + normals[ index + 1 ] = n.y; + normals[ index + 2 ] = n.z; + + } + + } + + if ( prevMaterial !== elem.colorCode ) { + + if ( prevMaterial !== null ) { + + bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 ); + + } + + const material = elem.material; + + if ( material !== null ) { + + if ( elementSize === 3 ) { + + materials.push( material ); + + } else if ( elementSize === 2 ) { + + if ( isConditionalSegments ) { + + materials.push( material.userData.edgeMaterial.userData.conditionalEdgeMaterial ); + + } else { + + materials.push( material.userData.edgeMaterial ); + + } + + } + + } else { + + // If a material has not been made available yet then keep the color code string in the material array + // to save the spot for the material once a parent scopes materials are being applied to the object. + materials.push( elem.colorCode ); + + } + + prevMaterial = elem.colorCode; + index0 = offset / 3; + numGroupVerts = vertices.length; + + } else { + + numGroupVerts += vertices.length; + + } + + offset += 3 * vertices.length; + + } + + if ( numGroupVerts > 0 ) { + + bufferGeometry.addGroup( index0, Infinity, materials.length - 1 ); + + } + + bufferGeometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) ); + + if ( normals !== null ) { + + bufferGeometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); + + } + + let object3d = null; + + if ( elementSize === 2 ) { + + if ( isConditionalSegments ) { + + object3d = new ConditionalLineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); + + } else { + + object3d = new LineSegments( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); + + } + + } else if ( elementSize === 3 ) { + + object3d = new Mesh( bufferGeometry, materials.length === 1 ? materials[ 0 ] : materials ); + + } + + if ( isConditionalSegments ) { + + object3d.isConditionalLine = true; + + const controlArray0 = new Float32Array( elements.length * 3 * 2 ); + const controlArray1 = new Float32Array( elements.length * 3 * 2 ); + const directionArray = new Float32Array( elements.length * 3 * 2 ); + for ( let i = 0, l = elements.length; i < l; i ++ ) { + + const os = elements[ i ]; + const vertices = os.vertices; + const controlPoints = os.controlPoints; + const c0 = controlPoints[ 0 ]; + const c1 = controlPoints[ 1 ]; + const v0 = vertices[ 0 ]; + const v1 = vertices[ 1 ]; + const index = i * 3 * 2; + controlArray0[ index + 0 ] = c0.x; + controlArray0[ index + 1 ] = c0.y; + controlArray0[ index + 2 ] = c0.z; + controlArray0[ index + 3 ] = c0.x; + controlArray0[ index + 4 ] = c0.y; + controlArray0[ index + 5 ] = c0.z; + + controlArray1[ index + 0 ] = c1.x; + controlArray1[ index + 1 ] = c1.y; + controlArray1[ index + 2 ] = c1.z; + controlArray1[ index + 3 ] = c1.x; + controlArray1[ index + 4 ] = c1.y; + controlArray1[ index + 5 ] = c1.z; + + directionArray[ index + 0 ] = v1.x - v0.x; + directionArray[ index + 1 ] = v1.y - v0.y; + directionArray[ index + 2 ] = v1.z - v0.z; + directionArray[ index + 3 ] = v1.x - v0.x; + directionArray[ index + 4 ] = v1.y - v0.y; + directionArray[ index + 5 ] = v1.z - v0.z; + + } + + bufferGeometry.setAttribute( 'control0', new BufferAttribute( controlArray0, 3, false ) ); + bufferGeometry.setAttribute( 'control1', new BufferAttribute( controlArray1, 3, false ) ); + bufferGeometry.setAttribute( 'direction', new BufferAttribute( directionArray, 3, false ) ); + + } + + return object3d; + +} + +// + +class LDrawLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + // Array of THREE.Material + this.materials = []; + this.materialLibrary = {}; + + // This also allows to handle the embedded text files ("0 FILE" lines) + this.partsCache = new LDrawPartsGeometryCache( this ); + + // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error. + this.fileMap = {}; + + // Initializes the materials library with default materials + this.setMaterials( [] ); + + // If this flag is set to true the vertex normals will be smoothed. + this.smoothNormals = true; + + // The path to load parts from the LDraw parts library from. + this.partsLibraryPath = ''; + + } + + setPartsLibraryPath( path ) { + + this.partsLibraryPath = path; + return this; + + } + + async preloadMaterials( url ) { + + const fileLoader = new FileLoader( this.manager ); + fileLoader.setPath( this.path ); + fileLoader.setRequestHeader( this.requestHeader ); + fileLoader.setWithCredentials( this.withCredentials ); + + const text = await fileLoader.loadAsync( url ); + const colorLineRegex = /^0 !COLOUR/; + const lines = text.split( /[\n\r]/g ); + const materials = []; + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + const line = lines[ i ]; + if ( colorLineRegex.test( line ) ) { + + const directive = line.replace( colorLineRegex, '' ); + const material = this.parseColorMetaDirective( new LineParser( directive ) ); + materials.push( material ); + + } + + } + + this.setMaterials( materials ); + + } + + load( url, onLoad, onProgress, onError ) { + + const fileLoader = new FileLoader( this.manager ); + fileLoader.setPath( this.path ); + fileLoader.setRequestHeader( this.requestHeader ); + fileLoader.setWithCredentials( this.withCredentials ); + fileLoader.load( url, text => { + + this.partsCache + .parseModel( text, this.materialLibrary ) + .then( group => { + + this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true ); + this.computeConstructionSteps( group ); + onLoad( group ); + + } ) + .catch( onError ); + + }, onProgress, onError ); + + } + + parse( text, onLoad ) { + + this.partsCache + .parseModel( text, this.materialLibrary ) + .then( group => { + + this.computeConstructionSteps( group ); + onLoad( group ); + + } ); + + } + + setMaterials( materials ) { + + this.materialLibrary = {}; + this.materials = []; + for ( let i = 0, l = materials.length; i < l; i ++ ) { + + this.addMaterial( materials[ i ] ); + + } + + // Add default main triangle and line edge materials (used in pieces that can be colored with a main color) + this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ) ); + this.addMaterial( this.parseColorMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ); + + return this; + + } + + setFileMap( fileMap ) { + + this.fileMap = fileMap; + + return this; + + } + + addMaterial( material ) { + + // Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array + + const matLib = this.materialLibrary; + if ( ! matLib[ material.userData.code ] ) { + + this.materials.push( material ); + matLib[ material.userData.code ] = material; + + } + + return this; + + } + + getMaterial( colorCode ) { + + if ( colorCode.startsWith( '0x2' ) ) { + + // Special 'direct' material value (RGB color) + const color = colorCode.substring( 3 ); + + return this.parseColorMetaDirective( new LineParser( 'Direct_Color_' + color + ' CODE -1 VALUE #' + color + ' EDGE #' + color + '' ) ); + + } + + return this.materialLibrary[ colorCode ] || null; + + } + + // Applies the appropriate materials to a prebuilt hierarchy of geometry. Assumes that color codes are present + // in the material array if they need to be filled in. + applyMaterialsToMesh( group, parentColorCode, materialHierarchy, finalMaterialPass = false ) { + + // find any missing materials as indicated by a color code string and replace it with a material from the current material lib + const loader = this; + const parentIsPassthrough = parentColorCode === MAIN_COLOUR_CODE; + group.traverse( c => { + + if ( c.isMesh || c.isLineSegments ) { + + if ( Array.isArray( c.material ) ) { + + for ( let i = 0, l = c.material.length; i < l; i ++ ) { + + if ( ! c.material[ i ].isMaterial ) { + + c.material[ i ] = getMaterial( c, c.material[ i ] ); + + } + + } + + } else if ( ! c.material.isMaterial ) { + + c.material = getMaterial( c, c.material ); + + } + + } + + } ); + + + // Returns the appropriate material for the object (line or face) given color code. If the code is "pass through" + // (24 for lines, 16 for edges) then the pass through color code is used. If that is also pass through then it's + // simply returned for the subsequent material application. + function getMaterial( c, colorCode ) { + + // if our parent is a passthrough color code and we don't have the current material color available then + // return early. + if ( parentIsPassthrough && ! ( colorCode in materialHierarchy ) && ! finalMaterialPass ) { + + return colorCode; + + } + + const forEdge = c.isLineSegments || c.isConditionalLine; + const isPassthrough = ! forEdge && colorCode === MAIN_COLOUR_CODE || forEdge && colorCode === MAIN_EDGE_COLOUR_CODE; + if ( isPassthrough ) { + + colorCode = parentColorCode; + + } + + let material = null; + if ( colorCode in materialHierarchy ) { + + material = materialHierarchy[ colorCode ]; + + } else if ( finalMaterialPass ) { + + // see if we can get the final material from from the "getMaterial" function which will attempt to + // parse the "direct" colors + material = loader.getMaterial( colorCode ); + if ( material === null ) { + + // otherwise throw an error if this is final opportunity to set the material + throw new Error( `LDrawLoader: Material properties for code ${ colorCode } not available.` ); + + } + + + } else { + + return colorCode; + + } + + if ( c.isLineSegments ) { + + material = material.userData.edgeMaterial; + + if ( c.isConditionalLine ) { + + material = material.userData.conditionalEdgeMaterial; + + } + + } + + return material; + + } + + } + + getMainMaterial() { + + return this.getMaterial( MAIN_COLOUR_CODE ); + + } + + getMainEdgeMaterial() { + + const mainMat = this.getMainMaterial(); + return mainMat && mainMat.userData ? mainMat.userData.edgeMaterial : null; + + } + + parseColorMetaDirective( lineParser ) { + + // Parses a color definition and returns a THREE.Material + + let code = null; + + // Triangle and line colors + let color = 0xFF00FF; + let edgeColor = 0xFF00FF; + + // Transparency + let alpha = 1; + let isTransparent = false; + // Self-illumination: + let luminance = 0; + + let finishType = FINISH_TYPE_DEFAULT; + + let edgeMaterial = null; + + const name = lineParser.getToken(); + if ( ! name ) { + + throw new Error( 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.' ); + + } + + // Parse tag tokens and their parameters + let token = null; + while ( true ) { + + token = lineParser.getToken(); + + if ( ! token ) { + + break; + + } + + switch ( token.toUpperCase() ) { + + case 'CODE': + + code = lineParser.getToken(); + break; + + case 'VALUE': + + color = lineParser.getToken(); + if ( color.startsWith( '0x' ) ) { + + color = '#' + color.substring( 2 ); + + } else if ( ! color.startsWith( '#' ) ) { + + throw new Error( 'LDrawLoader: Invalid color while parsing material' + lineParser.getLineNumberString() + '.' ); + + } + + break; + + case 'EDGE': + + edgeColor = lineParser.getToken(); + if ( edgeColor.startsWith( '0x' ) ) { + + edgeColor = '#' + edgeColor.substring( 2 ); + + } else if ( ! edgeColor.startsWith( '#' ) ) { + + // Try to see if edge color is a color code + edgeMaterial = this.getMaterial( edgeColor ); + if ( ! edgeMaterial ) { + + throw new Error( 'LDrawLoader: Invalid edge color while parsing material' + lineParser.getLineNumberString() + '.' ); + + } + + // Get the edge material for this triangle material + edgeMaterial = edgeMaterial.userData.edgeMaterial; + + } + + break; + + case 'ALPHA': + + alpha = parseInt( lineParser.getToken() ); + + if ( isNaN( alpha ) ) { + + throw new Error( 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.' ); + + } + + alpha = Math.max( 0, Math.min( 1, alpha / 255 ) ); + + if ( alpha < 1 ) { + + isTransparent = true; + + } + + break; + + case 'LUMINANCE': + + luminance = parseInt( lineParser.getToken() ); + + if ( isNaN( luminance ) ) { + + throw new Error( 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.' ); + + } + + luminance = Math.max( 0, Math.min( 1, luminance / 255 ) ); + + break; + + case 'CHROME': + finishType = FINISH_TYPE_CHROME; + break; + + case 'PEARLESCENT': + finishType = FINISH_TYPE_PEARLESCENT; + break; + + case 'RUBBER': + finishType = FINISH_TYPE_RUBBER; + break; + + case 'MATTE_METALLIC': + finishType = FINISH_TYPE_MATTE_METALLIC; + break; + + case 'METAL': + finishType = FINISH_TYPE_METAL; + break; + + case 'MATERIAL': + // Not implemented + lineParser.setToEnd(); + break; + + default: + throw new Error( 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.' ); + + } + + } + + let material = null; + + switch ( finishType ) { + + case FINISH_TYPE_DEFAULT: + + material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0 } ); + break; + + case FINISH_TYPE_PEARLESCENT: + + // Try to imitate pearlescency by making the surface glossy + material = new MeshStandardMaterial( { color: color, roughness: 0.3, metalness: 0.25 } ); + break; + + case FINISH_TYPE_CHROME: + + // Mirror finish surface + material = new MeshStandardMaterial( { color: color, roughness: 0, metalness: 1 } ); + break; + + case FINISH_TYPE_RUBBER: + + // Rubber finish + material = new MeshStandardMaterial( { color: color, roughness: 0.9, metalness: 0 } ); + break; + + case FINISH_TYPE_MATTE_METALLIC: + + // Brushed metal finish + material = new MeshStandardMaterial( { color: color, roughness: 0.8, metalness: 0.4 } ); + break; + + case FINISH_TYPE_METAL: + + // Average metal finish + material = new MeshStandardMaterial( { color: color, roughness: 0.2, metalness: 0.85 } ); + break; + + default: + // Should not happen + break; + + } + + material.transparent = isTransparent; + material.premultipliedAlpha = true; + material.opacity = alpha; + material.depthWrite = ! isTransparent; + material.color.convertSRGBToLinear(); + + material.polygonOffset = true; + material.polygonOffsetFactor = 1; + + if ( luminance !== 0 ) { + + material.emissive.set( material.color ).multiplyScalar( luminance ); + + } + + if ( ! edgeMaterial ) { + + // This is the material used for edges + edgeMaterial = new LineBasicMaterial( { + color: edgeColor, + transparent: isTransparent, + opacity: alpha, + depthWrite: ! isTransparent + } ); + edgeMaterial.userData.code = code; + edgeMaterial.name = name + ' - Edge'; + edgeMaterial.color.convertSRGBToLinear(); + + // This is the material used for conditional edges + edgeMaterial.userData.conditionalEdgeMaterial = new LDrawConditionalLineMaterial( { + + fog: true, + transparent: isTransparent, + depthWrite: ! isTransparent, + color: edgeColor, + opacity: alpha, + + } ); + edgeMaterial.userData.conditionalEdgeMaterial.color.convertSRGBToLinear(); + + } + + material.userData.code = code; + material.name = name; + + material.userData.edgeMaterial = edgeMaterial; + + this.addMaterial( material ); + + return material; + + } + + computeConstructionSteps( model ) { + + // Sets userdata.constructionStep number in Group objects and userData.numConstructionSteps number in the root Group object. + + let stepNumber = 0; + + model.traverse( c => { + + if ( c.isGroup ) { + + if ( c.userData.startingConstructionStep ) { + + stepNumber ++; + + } + + c.userData.constructionStep = stepNumber; + + } + + } ); + + model.userData.numConstructionSteps = stepNumber + 1; + + } + +} + +export { LDrawLoader }; diff --git a/jsm/loaders/LUT3dlLoader.js b/jsm/loaders/LUT3dlLoader.js new file mode 100644 index 0000000..bddeb25 --- /dev/null +++ b/jsm/loaders/LUT3dlLoader.js @@ -0,0 +1,151 @@ +// http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492 +// https://community.foundry.com/discuss/topic/103636/format-spec-for-3dl?mode=Post&postID=895258 +import { + Loader, + FileLoader, + DataTexture, + Data3DTexture, + RGBAFormat, + UnsignedByteType, + ClampToEdgeWrapping, + LinearFilter, +} from 'three'; + +export class LUT3dlLoader extends Loader { + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'text' ); + loader.load( url, text => { + + try { + + onLoad( this.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( str ) { + + // remove empty lines and comment lints + str = str + .replace( /^#.*?(\n|\r)/gm, '' ) + .replace( /^\s*?(\n|\r)/gm, '' ) + .trim(); + + const lines = str.split( /[\n\r]+/g ); + + // first line is the positions on the grid that are provided by the LUT + const gridLines = lines[ 0 ].trim().split( /\s+/g ).map( e => parseFloat( e ) ); + const gridStep = gridLines[ 1 ] - gridLines[ 0 ]; + const size = gridLines.length; + + for ( let i = 1, l = gridLines.length; i < l; i ++ ) { + + if ( gridStep !== ( gridLines[ i ] - gridLines[ i - 1 ] ) ) { + + throw new Error( 'LUT3dlLoader: Inconsistent grid size not supported.' ); + + } + + } + + const dataArray = new Array( size * size * size * 4 ); + let index = 0; + let maxOutputValue = 0.0; + for ( let i = 1, l = lines.length; i < l; i ++ ) { + + const line = lines[ i ].trim(); + const split = line.split( /\s/g ); + + const r = parseFloat( split[ 0 ] ); + const g = parseFloat( split[ 1 ] ); + const b = parseFloat( split[ 2 ] ); + maxOutputValue = Math.max( maxOutputValue, r, g, b ); + + const bLayer = index % size; + const gLayer = Math.floor( index / size ) % size; + const rLayer = Math.floor( index / ( size * size ) ) % size; + + // b grows first, then g, then r + const pixelIndex = bLayer * size * size + gLayer * size + rLayer; + dataArray[ 4 * pixelIndex + 0 ] = r; + dataArray[ 4 * pixelIndex + 1 ] = g; + dataArray[ 4 * pixelIndex + 2 ] = b; + dataArray[ 4 * pixelIndex + 3 ] = 1.0; + index += 1; + + } + + // Find the apparent bit depth of the stored RGB values and map the + // values to [ 0, 255 ]. + const bits = Math.ceil( Math.log2( maxOutputValue ) ); + const maxBitValue = Math.pow( 2.0, bits ); + for ( let i = 0, l = dataArray.length; i < l; i += 4 ) { + + const r = dataArray[ i + 0 ]; + const g = dataArray[ i + 1 ]; + const b = dataArray[ i + 2 ]; + dataArray[ i + 0 ] = 255 * r / maxBitValue; // r + dataArray[ i + 1 ] = 255 * g / maxBitValue; // g + dataArray[ i + 2 ] = 255 * b / maxBitValue; // b + + } + + const data = new Uint8Array( dataArray ); + const texture = new DataTexture(); + texture.image.data = data; + texture.image.width = size; + texture.image.height = size * size; + texture.format = RGBAFormat; + texture.type = UnsignedByteType; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.wrapS = ClampToEdgeWrapping; + texture.wrapT = ClampToEdgeWrapping; + texture.generateMipmaps = false; + texture.needsUpdate = true; + + const texture3D = new Data3DTexture(); + texture3D.image.data = data; + texture3D.image.width = size; + texture3D.image.height = size; + texture3D.image.depth = size; + texture3D.format = RGBAFormat; + texture3D.type = UnsignedByteType; + texture3D.magFilter = LinearFilter; + texture3D.minFilter = LinearFilter; + texture3D.wrapS = ClampToEdgeWrapping; + texture3D.wrapT = ClampToEdgeWrapping; + texture3D.wrapR = ClampToEdgeWrapping; + texture3D.generateMipmaps = false; + texture3D.needsUpdate = true; + + return { + size, + texture, + texture3D, + }; + + } + +} diff --git a/jsm/loaders/LUTCubeLoader.js b/jsm/loaders/LUTCubeLoader.js new file mode 100644 index 0000000..4a3dac7 --- /dev/null +++ b/jsm/loaders/LUTCubeLoader.js @@ -0,0 +1,153 @@ +// https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf + +import { + Loader, + FileLoader, + Vector3, + DataTexture, + Data3DTexture, + UnsignedByteType, + ClampToEdgeWrapping, + LinearFilter, +} from 'three'; + +export class LUTCubeLoader extends Loader { + + load( url, onLoad, onProgress, onError ) { + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'text' ); + loader.load( url, text => { + + try { + + onLoad( this.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + this.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( str ) { + + // Remove empty lines and comments + str = str + .replace( /^#.*?(\n|\r)/gm, '' ) + .replace( /^\s*?(\n|\r)/gm, '' ) + .trim(); + + let title = null; + let size = null; + const domainMin = new Vector3( 0, 0, 0 ); + const domainMax = new Vector3( 1, 1, 1 ); + + const lines = str.split( /[\n\r]+/g ); + let data = null; + + let currIndex = 0; + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + const line = lines[ i ].trim(); + const split = line.split( /\s/g ); + + switch ( split[ 0 ] ) { + + case 'TITLE': + title = line.substring( 7, line.length - 1 ); + break; + case 'LUT_3D_SIZE': + // TODO: A .CUBE LUT file specifies floating point values and could be represented with + // more precision than can be captured with Uint8Array. + const sizeToken = split[ 1 ]; + size = parseFloat( sizeToken ); + data = new Uint8Array( size * size * size * 4 ); + break; + case 'DOMAIN_MIN': + domainMin.x = parseFloat( split[ 1 ] ); + domainMin.y = parseFloat( split[ 2 ] ); + domainMin.z = parseFloat( split[ 3 ] ); + break; + case 'DOMAIN_MAX': + domainMax.x = parseFloat( split[ 1 ] ); + domainMax.y = parseFloat( split[ 2 ] ); + domainMax.z = parseFloat( split[ 3 ] ); + break; + default: + const r = parseFloat( split[ 0 ] ); + const g = parseFloat( split[ 1 ] ); + const b = parseFloat( split[ 2 ] ); + + if ( + r > 1.0 || r < 0.0 || + g > 1.0 || g < 0.0 || + b > 1.0 || b < 0.0 + ) { + + throw new Error( 'LUTCubeLoader : Non normalized values not supported.' ); + + } + + data[ currIndex + 0 ] = r * 255; + data[ currIndex + 1 ] = g * 255; + data[ currIndex + 2 ] = b * 255; + data[ currIndex + 3 ] = 255; + currIndex += 4; + + } + + } + + const texture = new DataTexture(); + texture.image.data = data; + texture.image.width = size; + texture.image.height = size * size; + texture.type = UnsignedByteType; + texture.magFilter = LinearFilter; + texture.minFilter = LinearFilter; + texture.wrapS = ClampToEdgeWrapping; + texture.wrapT = ClampToEdgeWrapping; + texture.generateMipmaps = false; + texture.needsUpdate = true; + + const texture3D = new Data3DTexture(); + texture3D.image.data = data; + texture3D.image.width = size; + texture3D.image.height = size; + texture3D.image.depth = size; + texture3D.type = UnsignedByteType; + texture3D.magFilter = LinearFilter; + texture3D.minFilter = LinearFilter; + texture3D.wrapS = ClampToEdgeWrapping; + texture3D.wrapT = ClampToEdgeWrapping; + texture3D.wrapR = ClampToEdgeWrapping; + texture3D.generateMipmaps = false; + texture3D.needsUpdate = true; + + return { + title, + size, + domainMin, + domainMax, + texture, + texture3D, + }; + + } + +} diff --git a/jsm/loaders/LWOLoader.js b/jsm/loaders/LWOLoader.js new file mode 100644 index 0000000..56a013d --- /dev/null +++ b/jsm/loaders/LWOLoader.js @@ -0,0 +1,1067 @@ +/** + * @version 1.1.1 + * + * @desc Load files in LWO3 and LWO2 format on Three.js + * + * LWO3 format specification: + * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo3.html + * + * LWO2 format specification: + * https://static.lightwave3d.com/sdk/2019/html/filefmts/lwo2.html + * + **/ + +import { + AddOperation, + BackSide, + BufferAttribute, + BufferGeometry, + ClampToEdgeWrapping, + Color, + DoubleSide, + EquirectangularReflectionMapping, + EquirectangularRefractionMapping, + FileLoader, + Float32BufferAttribute, + FrontSide, + LineBasicMaterial, + LineSegments, + Loader, + Mesh, + MeshPhongMaterial, + MeshPhysicalMaterial, + MeshStandardMaterial, + MirroredRepeatWrapping, + Points, + PointsMaterial, + RepeatWrapping, + TextureLoader, + Vector2 +} from 'three'; + +import { IFFParser } from './lwo/IFFParser.js'; + +let _lwoTree; + +class LWOLoader extends Loader { + + constructor( manager, parameters = {} ) { + + super( manager ); + + this.resourcePath = ( parameters.resourcePath !== undefined ) ? parameters.resourcePath : ''; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( scope.path === '' ) ? extractParentUrl( url, 'Objects' ) : scope.path; + + // give the mesh a default name based on the filename + const modelName = url.split( path ).pop().split( '.' )[ 0 ]; + + const loader = new FileLoader( this.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + + loader.load( url, function ( buffer ) { + + // console.time( 'Total parsing: ' ); + + try { + + onLoad( scope.parse( buffer, path, modelName ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + // console.timeEnd( 'Total parsing: ' ); + + }, onProgress, onError ); + + } + + parse( iffBuffer, path, modelName ) { + + _lwoTree = new IFFParser().parse( iffBuffer ); + + // console.log( 'lwoTree', lwoTree ); + + const textureLoader = new TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin ); + + return new LWOTreeParser( textureLoader ).parse( modelName ); + + } + +} + +// Parse the lwoTree object +class LWOTreeParser { + + constructor( textureLoader ) { + + this.textureLoader = textureLoader; + + } + + parse( modelName ) { + + this.materials = new MaterialParser( this.textureLoader ).parse(); + this.defaultLayerName = modelName; + + this.meshes = this.parseLayers(); + + return { + materials: this.materials, + meshes: this.meshes, + }; + + } + + parseLayers() { + + // array of all meshes for building hierarchy + const meshes = []; + + // final array containing meshes with scene graph hierarchy set up + const finalMeshes = []; + + const geometryParser = new GeometryParser(); + + const scope = this; + _lwoTree.layers.forEach( function ( layer ) { + + const geometry = geometryParser.parse( layer.geometry, layer ); + + const mesh = scope.parseMesh( geometry, layer ); + + meshes[ layer.number ] = mesh; + + if ( layer.parent === - 1 ) finalMeshes.push( mesh ); + else meshes[ layer.parent ].add( mesh ); + + + } ); + + this.applyPivots( finalMeshes ); + + return finalMeshes; + + } + + parseMesh( geometry, layer ) { + + let mesh; + + const materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type ); + + this.duplicateUVs( geometry, materials ); + + if ( layer.geometry.type === 'points' ) mesh = new Points( geometry, materials ); + else if ( layer.geometry.type === 'lines' ) mesh = new LineSegments( geometry, materials ); + else mesh = new Mesh( geometry, materials ); + + if ( layer.name ) mesh.name = layer.name; + else mesh.name = this.defaultLayerName + '_layer_' + layer.number; + + mesh.userData.pivot = layer.pivot; + + return mesh; + + } + + // TODO: may need to be reversed in z to convert LWO to three.js coordinates + applyPivots( meshes ) { + + meshes.forEach( function ( mesh ) { + + mesh.traverse( function ( child ) { + + const pivot = child.userData.pivot; + + child.position.x += pivot[ 0 ]; + child.position.y += pivot[ 1 ]; + child.position.z += pivot[ 2 ]; + + if ( child.parent ) { + + const parentPivot = child.parent.userData.pivot; + + child.position.x -= parentPivot[ 0 ]; + child.position.y -= parentPivot[ 1 ]; + child.position.z -= parentPivot[ 2 ]; + + } + + } ); + + } ); + + } + + getMaterials( namesArray, type ) { + + const materials = []; + + const scope = this; + + namesArray.forEach( function ( name, i ) { + + materials[ i ] = scope.getMaterialByName( name ); + + } ); + + // convert materials to line or point mats if required + if ( type === 'points' || type === 'lines' ) { + + materials.forEach( function ( mat, i ) { + + const spec = { + color: mat.color, + }; + + if ( type === 'points' ) { + + spec.size = 0.1; + spec.map = mat.map; + materials[ i ] = new PointsMaterial( spec ); + + } else if ( type === 'lines' ) { + + materials[ i ] = new LineBasicMaterial( spec ); + + } + + } ); + + } + + // if there is only one material, return that directly instead of array + const filtered = materials.filter( Boolean ); + if ( filtered.length === 1 ) return filtered[ 0 ]; + + return materials; + + } + + getMaterialByName( name ) { + + return this.materials.filter( function ( m ) { + + return m.name === name; + + } )[ 0 ]; + + } + + // If the material has an aoMap, duplicate UVs + duplicateUVs( geometry, materials ) { + + let duplicateUVs = false; + + if ( ! Array.isArray( materials ) ) { + + if ( materials.aoMap ) duplicateUVs = true; + + } else { + + materials.forEach( function ( material ) { + + if ( material.aoMap ) duplicateUVs = true; + + } ); + + } + + if ( ! duplicateUVs ) return; + + geometry.setAttribute( 'uv2', new BufferAttribute( geometry.attributes.uv.array, 2 ) ); + + } + +} + +class MaterialParser { + + constructor( textureLoader ) { + + this.textureLoader = textureLoader; + + } + + parse() { + + const materials = []; + this.textures = {}; + + for ( const name in _lwoTree.materials ) { + + if ( _lwoTree.format === 'LWO3' ) { + + materials.push( this.parseMaterial( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); + + } else if ( _lwoTree.format === 'LWO2' ) { + + materials.push( this.parseMaterialLwo2( _lwoTree.materials[ name ], name, _lwoTree.textures ) ); + + } + + } + + return materials; + + } + + parseMaterial( materialData, name, textures ) { + + let params = { + name: name, + side: this.getSide( materialData.attributes ), + flatShading: this.getSmooth( materialData.attributes ), + }; + + const connections = this.parseConnections( materialData.connections, materialData.nodes ); + + const maps = this.parseTextureNodes( connections.maps ); + + this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps ); + + const attributes = this.parseAttributes( connections.attributes, maps ); + + this.parseEnvMap( connections, maps, attributes ); + + params = Object.assign( maps, params ); + params = Object.assign( params, attributes ); + + const materialType = this.getMaterialType( connections.attributes ); + + return new materialType( params ); + + } + + parseMaterialLwo2( materialData, name/*, textures*/ ) { + + let params = { + name: name, + side: this.getSide( materialData.attributes ), + flatShading: this.getSmooth( materialData.attributes ), + }; + + const attributes = this.parseAttributes( materialData.attributes, {} ); + params = Object.assign( params, attributes ); + return new MeshPhongMaterial( params ); + + } + + // Note: converting from left to right handed coords by switching x -> -x in vertices, and + // then switching mat FrontSide -> BackSide + // NB: this means that FrontSide and BackSide have been switched! + getSide( attributes ) { + + if ( ! attributes.side ) return BackSide; + + switch ( attributes.side ) { + + case 0: + case 1: + return BackSide; + case 2: return FrontSide; + case 3: return DoubleSide; + + } + + } + + getSmooth( attributes ) { + + if ( ! attributes.smooth ) return true; + return ! attributes.smooth; + + } + + parseConnections( connections, nodes ) { + + const materialConnections = { + maps: {} + }; + + const inputName = connections.inputName; + const inputNodeName = connections.inputNodeName; + const nodeName = connections.nodeName; + + const scope = this; + inputName.forEach( function ( name, index ) { + + if ( name === 'Material' ) { + + const matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes ); + materialConnections.attributes = matNode.attributes; + materialConnections.envMap = matNode.fileName; + materialConnections.name = inputNodeName[ index ]; + + } + + } ); + + nodeName.forEach( function ( name, index ) { + + if ( name === materialConnections.name ) { + + materialConnections.maps[ inputName[ index ] ] = scope.getNodeByRefName( inputNodeName[ index ], nodes ); + + } + + } ); + + return materialConnections; + + } + + getNodeByRefName( refName, nodes ) { + + for ( const name in nodes ) { + + if ( nodes[ name ].refName === refName ) return nodes[ name ]; + + } + + } + + parseTextureNodes( textureNodes ) { + + const maps = {}; + + for ( const name in textureNodes ) { + + const node = textureNodes[ name ]; + const path = node.fileName; + + if ( ! path ) return; + + const texture = this.loadTexture( path ); + + if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode ); + if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode ); + + switch ( name ) { + + case 'Color': + maps.map = texture; + break; + case 'Roughness': + maps.roughnessMap = texture; + maps.roughness = 1; + break; + case 'Specular': + maps.specularMap = texture; + maps.specular = 0xffffff; + break; + case 'Luminous': + maps.emissiveMap = texture; + maps.emissive = 0x808080; + break; + case 'Luminous Color': + maps.emissive = 0x808080; + break; + case 'Metallic': + maps.metalnessMap = texture; + maps.metalness = 1; + break; + case 'Transparency': + case 'Alpha': + maps.alphaMap = texture; + maps.transparent = true; + break; + case 'Normal': + maps.normalMap = texture; + if ( node.amplitude !== undefined ) maps.normalScale = new Vector2( node.amplitude, node.amplitude ); + break; + case 'Bump': + maps.bumpMap = texture; + break; + + } + + } + + // LWO BSDF materials can have both spec and rough, but this is not valid in three + if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap; + + return maps; + + } + + // maps can also be defined on individual material attributes, parse those here + // This occurs on Standard (Phong) surfaces + parseAttributeImageMaps( attributes, textures, maps ) { + + for ( const name in attributes ) { + + const attribute = attributes[ name ]; + + if ( attribute.maps ) { + + const mapData = attribute.maps[ 0 ]; + + const path = this.getTexturePathByIndex( mapData.imageIndex, textures ); + if ( ! path ) return; + + const texture = this.loadTexture( path ); + + if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w ); + if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h ); + + switch ( name ) { + + case 'Color': + maps.map = texture; + break; + case 'Diffuse': + maps.aoMap = texture; + break; + case 'Roughness': + maps.roughnessMap = texture; + maps.roughness = 1; + break; + case 'Specular': + maps.specularMap = texture; + maps.specular = 0xffffff; + break; + case 'Luminosity': + maps.emissiveMap = texture; + maps.emissive = 0x808080; + break; + case 'Metallic': + maps.metalnessMap = texture; + maps.metalness = 1; + break; + case 'Transparency': + case 'Alpha': + maps.alphaMap = texture; + maps.transparent = true; + break; + case 'Normal': + maps.normalMap = texture; + break; + case 'Bump': + maps.bumpMap = texture; + break; + + } + + } + + } + + } + + parseAttributes( attributes, maps ) { + + const params = {}; + + // don't use color data if color map is present + if ( attributes.Color && ! maps.map ) { + + params.color = new Color().fromArray( attributes.Color.value ); + + } else params.color = new Color(); + + + if ( attributes.Transparency && attributes.Transparency.value !== 0 ) { + + params.opacity = 1 - attributes.Transparency.value; + params.transparent = true; + + } + + if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1; + + if ( attributes[ 'Refraction Index' ] ) params.refractionRatio = 0.98 / attributes[ 'Refraction Index' ].value; + + this.parsePhysicalAttributes( params, attributes, maps ); + this.parseStandardAttributes( params, attributes, maps ); + this.parsePhongAttributes( params, attributes, maps ); + + return params; + + } + + parsePhysicalAttributes( params, attributes/*, maps*/ ) { + + if ( attributes.Clearcoat && attributes.Clearcoat.value > 0 ) { + + params.clearcoat = attributes.Clearcoat.value; + + if ( attributes[ 'Clearcoat Gloss' ] ) { + + params.clearcoatRoughness = 0.5 * ( 1 - attributes[ 'Clearcoat Gloss' ].value ); + + } + + } + + } + + parseStandardAttributes( params, attributes, maps ) { + + + if ( attributes.Luminous ) { + + params.emissiveIntensity = attributes.Luminous.value; + + if ( attributes[ 'Luminous Color' ] && ! maps.emissive ) { + + params.emissive = new Color().fromArray( attributes[ 'Luminous Color' ].value ); + + } else { + + params.emissive = new Color( 0x808080 ); + + } + + } + + if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value; + if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value; + + } + + parsePhongAttributes( params, attributes, maps ) { + + if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value ); + + if ( attributes.Reflection ) { + + params.reflectivity = attributes.Reflection.value; + params.combine = AddOperation; + + } + + if ( attributes.Luminosity ) { + + params.emissiveIntensity = attributes.Luminosity.value; + + if ( ! maps.emissiveMap && ! maps.map ) { + + params.emissive = params.color; + + } else { + + params.emissive = new Color( 0x808080 ); + + } + + } + + // parse specular if there is no roughness - we will interpret the material as 'Phong' in this case + if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) { + + if ( attributes[ 'Color Highlight' ] ) { + + params.specular = new Color().setScalar( attributes.Specular.value ).lerp( params.color.clone().multiplyScalar( attributes.Specular.value ), attributes[ 'Color Highlight' ].value ); + + } else { + + params.specular = new Color().setScalar( attributes.Specular.value ); + + } + + } + + if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 ); + + } + + parseEnvMap( connections, maps, attributes ) { + + if ( connections.envMap ) { + + const envMap = this.loadTexture( connections.envMap ); + + if ( attributes.transparent && attributes.opacity < 0.999 ) { + + envMap.mapping = EquirectangularRefractionMapping; + + // Reflectivity and refraction mapping don't work well together in Phong materials + if ( attributes.reflectivity !== undefined ) { + + delete attributes.reflectivity; + delete attributes.combine; + + } + + if ( attributes.metalness !== undefined ) { + + attributes.metalness = 1; // For most transparent materials metalness should be set to 1 if not otherwise defined. If set to 0 no refraction will be visible + + } + + attributes.opacity = 1; // transparency fades out refraction, forcing opacity to 1 ensures a closer visual match to the material in Lightwave. + + } else envMap.mapping = EquirectangularReflectionMapping; + + maps.envMap = envMap; + + } + + } + + // get texture defined at top level by its index + getTexturePathByIndex( index ) { + + let fileName = ''; + + if ( ! _lwoTree.textures ) return fileName; + + _lwoTree.textures.forEach( function ( texture ) { + + if ( texture.index === index ) fileName = texture.fileName; + + } ); + + return fileName; + + } + + loadTexture( path ) { + + if ( ! path ) return null; + + const texture = this.textureLoader.load( + path, + undefined, + undefined, + function () { + + console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' ); + + } + ); + + return texture; + + } + + // 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge + getWrappingType( num ) { + + switch ( num ) { + + case 0: + console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' ); + return ClampToEdgeWrapping; + case 1: return RepeatWrapping; + case 2: return MirroredRepeatWrapping; + case 3: return ClampToEdgeWrapping; + + } + + } + + getMaterialType( nodeData ) { + + if ( nodeData.Clearcoat && nodeData.Clearcoat.value > 0 ) return MeshPhysicalMaterial; + if ( nodeData.Roughness ) return MeshStandardMaterial; + return MeshPhongMaterial; + + } + +} + +class GeometryParser { + + parse( geoData, layer ) { + + const geometry = new BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( geoData.points, 3 ) ); + + const indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions ); + geometry.setIndex( indices ); + + this.parseGroups( geometry, geoData ); + + geometry.computeVertexNormals(); + + this.parseUVs( geometry, layer, indices ); + this.parseMorphTargets( geometry, layer, indices ); + + // TODO: z may need to be reversed to account for coordinate system change + geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); + + // let userData = geometry.userData; + // geometry = geometry.toNonIndexed() + // geometry.userData = userData; + + return geometry; + + } + + // split quads into tris + splitIndices( indices, polygonDimensions ) { + + const remappedIndices = []; + + let i = 0; + polygonDimensions.forEach( function ( dim ) { + + if ( dim < 4 ) { + + for ( let k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] ); + + } else if ( dim === 4 ) { + + remappedIndices.push( + indices[ i ], + indices[ i + 1 ], + indices[ i + 2 ], + + indices[ i ], + indices[ i + 2 ], + indices[ i + 3 ] + + ); + + } else if ( dim > 4 ) { + + for ( let k = 1; k < dim - 1; k ++ ) { + + remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] ); + + } + + console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' ); + + } + + i += dim; + + } ); + + return remappedIndices; + + } + + // NOTE: currently ignoring poly indices and assuming that they are intelligently ordered + parseGroups( geometry, geoData ) { + + const tags = _lwoTree.tags; + const matNames = []; + + let elemSize = 3; + if ( geoData.type === 'lines' ) elemSize = 2; + if ( geoData.type === 'points' ) elemSize = 1; + + const remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices ); + + let indexNum = 0; // create new indices in numerical order + const indexPairs = {}; // original indices mapped to numerical indices + + let prevMaterialIndex; + let materialIndex; + + let prevStart = 0; + let currentCount = 0; + + for ( let i = 0; i < remappedIndices.length; i += 2 ) { + + materialIndex = remappedIndices[ i + 1 ]; + + if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ]; + + if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex; + + if ( materialIndex !== prevMaterialIndex ) { + + let currentIndex; + if ( indexPairs[ tags[ prevMaterialIndex ] ] ) { + + currentIndex = indexPairs[ tags[ prevMaterialIndex ] ]; + + } else { + + currentIndex = indexNum; + indexPairs[ tags[ prevMaterialIndex ] ] = indexNum; + matNames[ indexNum ] = tags[ prevMaterialIndex ]; + indexNum ++; + + } + + geometry.addGroup( prevStart, currentCount, currentIndex ); + + prevStart += currentCount; + + prevMaterialIndex = materialIndex; + currentCount = 0; + + } + + currentCount += elemSize; + + } + + // the loop above doesn't add the last group, do that here. + if ( geometry.groups.length > 0 ) { + + let currentIndex; + if ( indexPairs[ tags[ materialIndex ] ] ) { + + currentIndex = indexPairs[ tags[ materialIndex ] ]; + + } else { + + currentIndex = indexNum; + indexPairs[ tags[ materialIndex ] ] = indexNum; + matNames[ indexNum ] = tags[ materialIndex ]; + + } + + geometry.addGroup( prevStart, currentCount, currentIndex ); + + } + + // Mat names from TAGS chunk, used to build up an array of materials for this geometry + geometry.userData.matNames = matNames; + + } + + splitMaterialIndices( polygonDimensions, indices ) { + + const remappedIndices = []; + + polygonDimensions.forEach( function ( dim, i ) { + + if ( dim <= 3 ) { + + remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] ); + + } else if ( dim === 4 ) { + + remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] ); + + } else { + + // ignore > 4 for now + for ( let k = 0; k < dim - 2; k ++ ) { + + remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] ); + + } + + } + + } ); + + return remappedIndices; + + } + + // UV maps: + // 1: are defined via index into an array of points, not into a geometry + // - the geometry is also defined by an index into this array, but the indexes may not match + // 2: there can be any number of UV maps for a single geometry. Here these are combined, + // with preference given to the first map encountered + // 3: UV maps can be partial - that is, defined for only a part of the geometry + // 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most + // UV maps are defined as partially VMAP and partially VMAD + // VMADs are currently not supported + parseUVs( geometry, layer ) { + + // start by creating a UV map set to zero for the whole geometry + const remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () { + + return 0; + + } ); + + for ( const name in layer.uvs ) { + + const uvs = layer.uvs[ name ].uvs; + const uvIndices = layer.uvs[ name ].uvIndices; + + uvIndices.forEach( function ( i, j ) { + + remappedUVs[ i * 2 ] = uvs[ j * 2 ]; + remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ]; + + } ); + + } + + geometry.setAttribute( 'uv', new Float32BufferAttribute( remappedUVs, 2 ) ); + + } + + parseMorphTargets( geometry, layer ) { + + let num = 0; + for ( const name in layer.morphTargets ) { + + const remappedPoints = geometry.attributes.position.array.slice(); + + if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = []; + + const morphPoints = layer.morphTargets[ name ].points; + const morphIndices = layer.morphTargets[ name ].indices; + const type = layer.morphTargets[ name ].type; + + morphIndices.forEach( function ( i, j ) { + + if ( type === 'relative' ) { + + remappedPoints[ i * 3 ] += morphPoints[ j * 3 ]; + remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ]; + remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ]; + + } else { + + remappedPoints[ i * 3 ] = morphPoints[ j * 3 ]; + remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ]; + remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ]; + + } + + } ); + + geometry.morphAttributes.position[ num ] = new Float32BufferAttribute( remappedPoints, 3 ); + geometry.morphAttributes.position[ num ].name = name; + + num ++; + + } + + geometry.morphTargetsRelative = false; + + } + +} + + +// ************** UTILITY FUNCTIONS ************** + +function extractParentUrl( url, dir ) { + + const index = url.indexOf( dir ); + + if ( index === - 1 ) return './'; + + return url.slice( 0, index ); + +} + +export { LWOLoader }; diff --git a/jsm/loaders/LogLuvLoader.js b/jsm/loaders/LogLuvLoader.js new file mode 100644 index 0000000..297ea0a --- /dev/null +++ b/jsm/loaders/LogLuvLoader.js @@ -0,0 +1,606 @@ +import { + DataUtils, + DataTextureLoader, + FloatType, + HalfFloatType, + RGBAFormat +} from 'three'; + +class LogLuvLoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + + } + + parse( buffer ) { + + const ifds = UTIF.decode( buffer ); + UTIF.decodeImage( buffer, ifds[ 0 ] ); + const rgba = UTIF.toRGBA( ifds[ 0 ], this.type ); + + return { + width: ifds[ 0 ].width, + height: ifds[ 0 ].height, + data: rgba, + format: RGBAFormat, + type: this.type, + flipY: true + }; + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + +} + +// from https://github.com/photopea/UTIF.js (MIT License) + +const UTIF = {}; + +UTIF.decode = function ( buff, prm ) { + + if ( prm == null ) prm = { parseMN: true, debug: false }; // read MakerNote, debug + var data = new Uint8Array( buff ), offset = 0; + + var id = UTIF._binBE.readASCII( data, offset, 2 ); offset += 2; + var bin = id == 'II' ? UTIF._binLE : UTIF._binBE; + bin.readUshort( data, offset ); offset += 2; + + var ifdo = bin.readUint( data, offset ); + var ifds = []; + while ( true ) { + + var cnt = bin.readUshort( data, ifdo ), typ = bin.readUshort( data, ifdo + 4 ); if ( cnt != 0 ) if ( typ < 1 || 13 < typ ) { + + console.log( 'error in TIFF' ); break; + + } + + + UTIF._readIFD( bin, data, ifdo, ifds, 0, prm ); + + ifdo = bin.readUint( data, ifdo + 2 + cnt * 12 ); + if ( ifdo == 0 ) break; + + } + + return ifds; + +}; + +UTIF.decodeImage = function ( buff, img, ifds ) { + + if ( img.data ) return; + var data = new Uint8Array( buff ); + var id = UTIF._binBE.readASCII( data, 0, 2 ); + + if ( img[ 't256' ] == null ) return; // No width => probably not an image + img.isLE = id == 'II'; + img.width = img[ 't256' ][ 0 ]; //delete img["t256"]; + img.height = img[ 't257' ][ 0 ]; //delete img["t257"]; + + var cmpr = img[ 't259' ] ? img[ 't259' ][ 0 ] : 1; //delete img["t259"]; + var fo = img[ 't266' ] ? img[ 't266' ][ 0 ] : 1; //delete img["t266"]; + if ( img[ 't284' ] && img[ 't284' ][ 0 ] == 2 ) console.log( 'PlanarConfiguration 2 should not be used!' ); + if ( cmpr == 7 && img[ 't258' ] && img[ 't258' ].length > 3 ) img[ 't258' ] = img[ 't258' ].slice( 0, 3 ); + + var bipp; // bits per pixel + if ( img[ 't258' ] ) bipp = Math.min( 32, img[ 't258' ][ 0 ] ) * img[ 't258' ].length; + else bipp = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ); + // Some .NEF files have t258==14, even though they use 16 bits per pixel + if ( cmpr == 1 && img[ 't279' ] != null && img[ 't278' ] && img[ 't262' ][ 0 ] == 32803 ) { + + bipp = Math.round( ( img[ 't279' ][ 0 ] * 8 ) / ( img.width * img[ 't278' ][ 0 ] ) ); + + } + + var bipl = Math.ceil( img.width * bipp / 8 ) * 8; + var soff = img[ 't273' ]; if ( soff == null ) soff = img[ 't324' ]; + var bcnt = img[ 't279' ]; if ( cmpr == 1 && soff.length == 1 ) bcnt = [ img.height * ( bipl >>> 3 ) ]; if ( bcnt == null ) bcnt = img[ 't325' ]; + //bcnt[0] = Math.min(bcnt[0], data.length); // Hasselblad, "RAW_HASSELBLAD_H3D39II.3FR" + var bytes = new Uint8Array( img.height * ( bipl >>> 3 ) ), bilen = 0; + + if ( img[ 't322' ] != null ) { + + var tw = img[ 't322' ][ 0 ], th = img[ 't323' ][ 0 ]; + var tx = Math.floor( ( img.width + tw - 1 ) / tw ); + var ty = Math.floor( ( img.height + th - 1 ) / th ); + var tbuff = new Uint8Array( Math.ceil( tw * th * bipp / 8 ) | 0 ); + for ( var y = 0; y < ty; y ++ ) + for ( var x = 0; x < tx; x ++ ) { + + var i = y * tx + x; for ( var j = 0; j < tbuff.length; j ++ ) tbuff[ j ] = 0; + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, tbuff, 0, fo ); + // Might be required for 7 too. Need to check + if ( cmpr == 6 ) bytes = tbuff; + else UTIF._copyTile( tbuff, Math.ceil( tw * bipp / 8 ) | 0, th, bytes, Math.ceil( img.width * bipp / 8 ) | 0, img.height, Math.ceil( x * tw * bipp / 8 ) | 0, y * th ); + + } + + bilen = bytes.length * 8; + + } else { + + var rps = img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height; rps = Math.min( rps, img.height ); + for ( var i = 0; i < soff.length; i ++ ) { + + UTIF.decode._decompress( img, ifds, data, soff[ i ], bcnt[ i ], cmpr, bytes, Math.ceil( bilen / 8 ) | 0, fo ); + bilen += bipl * rps; + + } + + bilen = Math.min( bilen, bytes.length * 8 ); + + } + + img.data = new Uint8Array( bytes.buffer, 0, Math.ceil( bilen / 8 ) | 0 ); + +}; + +UTIF.decode._decompress = function ( img, ifds, data, off, len, cmpr, tgt, toff ) { + + //console.log("compression", cmpr); + //var time = Date.now(); + if ( cmpr == 34676 ) UTIF.decode._decodeLogLuv32( img, data, off, len, tgt, toff ); + else console.log( 'Unsupported compression', cmpr ); + + //console.log(Date.now()-time); + + var bps = ( img[ 't258' ] ? Math.min( 32, img[ 't258' ][ 0 ] ) : 1 ); + var noc = ( img[ 't277' ] ? img[ 't277' ][ 0 ] : 1 ), bpp = ( bps * noc ) >>> 3, h = ( img[ 't278' ] ? img[ 't278' ][ 0 ] : img.height ), bpl = Math.ceil( bps * noc * img.width / 8 ); + + // convert to Little Endian /* + if ( bps == 16 && ! img.isLE && img[ 't33422' ] == null ) // not DNG + for ( var y = 0; y < h; y ++ ) { + + //console.log("fixing endianity"); + var roff = toff + y * bpl; + for ( var x = 1; x < bpl; x += 2 ) { + + var t = tgt[ roff + x ]; tgt[ roff + x ] = tgt[ roff + x - 1 ]; tgt[ roff + x - 1 ] = t; + + } + + } //*/ + + if ( img[ 't317' ] && img[ 't317' ][ 0 ] == 2 ) { + + for ( var y = 0; y < h; y ++ ) { + + var ntoff = toff + y * bpl; + if ( bps == 16 ) for ( var j = bpp; j < bpl; j += 2 ) { + + var nv = ( ( tgt[ ntoff + j + 1 ] << 8 ) | tgt[ ntoff + j ] ) + ( ( tgt[ ntoff + j - bpp + 1 ] << 8 ) | tgt[ ntoff + j - bpp ] ); + tgt[ ntoff + j ] = nv & 255; tgt[ ntoff + j + 1 ] = ( nv >>> 8 ) & 255; + + } + else if ( noc == 3 ) for ( var j = 3; j < bpl; j += 3 ) { + + tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - 3 ] ) & 255; + tgt[ ntoff + j + 1 ] = ( tgt[ ntoff + j + 1 ] + tgt[ ntoff + j - 2 ] ) & 255; + tgt[ ntoff + j + 2 ] = ( tgt[ ntoff + j + 2 ] + tgt[ ntoff + j - 1 ] ) & 255; + + } + else for ( var j = bpp; j < bpl; j ++ ) tgt[ ntoff + j ] = ( tgt[ ntoff + j ] + tgt[ ntoff + j - bpp ] ) & 255; + + } + + } + +}; + +UTIF.decode._decodeLogLuv32 = function ( img, data, off, len, tgt, toff ) { + + var w = img.width, qw = w * 4; + var io = 0, out = new Uint8Array( qw ); + + while ( io < len ) { + + var oo = 0; + while ( oo < qw ) { + + var c = data[ off + io ]; io ++; + if ( c < 128 ) { + + for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io + j ]; oo += c; io += c; + + } else { + + c = c - 126; for ( var j = 0; j < c; j ++ ) out[ oo + j ] = data[ off + io ]; oo += c; io ++; + + } + + } + + for ( var x = 0; x < w; x ++ ) { + + tgt[ toff + 0 ] = out[ x ]; + tgt[ toff + 1 ] = out[ x + w ]; + tgt[ toff + 2 ] = out[ x + w * 2 ]; + tgt[ toff + 4 ] = out[ x + w * 3 ]; + toff += 6; + + } + + } + +}; + +UTIF.tags = {}; +//UTIF.ttypes = { 256:3,257:3,258:3, 259:3, 262:3, 273:4, 274:3, 277:3,278:4,279:4, 282:5, 283:5, 284:3, 286:5,287:5, 296:3, 305:2, 306:2, 338:3, 513:4, 514:4, 34665:4 }; +// start at tag 250 +UTIF._types = function () { + + var main = new Array( 250 ); main.fill( 0 ); + main = main.concat( [ 0, 0, 0, 0, 4, 3, 3, 3, 3, 3, 0, 0, 3, 0, 0, 0, 3, 0, 0, 2, 2, 2, 2, 4, 3, 0, 0, 3, 4, 4, 3, 3, 5, 5, 3, 2, 5, 5, 0, 0, 0, 0, 4, 4, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 3, 5, 5, 3, 0, 3, 3, 4, 4, 4, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ); + var rest = { 33432: 2, 33434: 5, 33437: 5, 34665: 4, 34850: 3, 34853: 4, 34855: 3, 34864: 3, 34866: 4, 36864: 7, 36867: 2, 36868: 2, 37121: 7, 37377: 10, 37378: 5, 37380: 10, 37381: 5, 37383: 3, 37384: 3, 37385: 3, 37386: 5, 37510: 7, 37520: 2, 37521: 2, 37522: 2, 40960: 7, 40961: 3, 40962: 4, 40963: 4, 40965: 4, 41486: 5, 41487: 5, 41488: 3, 41985: 3, 41986: 3, 41987: 3, 41988: 5, 41989: 3, 41990: 3, 41993: 3, 41994: 3, 41995: 7, 41996: 3, 42032: 2, 42033: 2, 42034: 5, 42036: 2, 42037: 2, 59932: 7 }; + return { + basic: { + main: main, + rest: rest + }, + gps: { + main: [ 1, 2, 5, 2, 5, 1, 5, 5, 0, 9 ], + rest: { 18: 2, 29: 2 } + } + }; + +}(); + +UTIF._readIFD = function ( bin, data, offset, ifds, depth, prm ) { + + var cnt = bin.readUshort( data, offset ); offset += 2; + var ifd = {}; + + if ( prm.debug ) console.log( ' '.repeat( depth ), ifds.length - 1, '>>>----------------' ); + for ( var i = 0; i < cnt; i ++ ) { + + var tag = bin.readUshort( data, offset ); offset += 2; + var type = bin.readUshort( data, offset ); offset += 2; + var num = bin.readUint( data, offset ); offset += 4; + var voff = bin.readUint( data, offset ); offset += 4; + + var arr = []; + //ifd["t"+tag+"-"+UTIF.tags[tag]] = arr; + if ( type == 1 || type == 7 ) { + + arr = new Uint8Array( data.buffer, ( num < 5 ? offset - 4 : voff ), num ); + + } + + if ( type == 2 ) { + + var o0 = ( num < 5 ? offset - 4 : voff ), c = data[ o0 ], len = Math.max( 0, Math.min( num - 1, data.length - o0 ) ); + if ( c < 128 || len == 0 ) arr.push( bin.readASCII( data, o0, len ) ); + else arr = new Uint8Array( data.buffer, o0, len ); + + } + + if ( type == 3 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUshort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + + } + + if ( type == 4 + || type == 13 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readUint( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + + } + + if ( type == 5 || type == 10 ) { + + var ri = type == 5 ? bin.readUint : bin.readInt; + for ( var j = 0; j < num; j ++ ) arr.push( [ ri( data, voff + j * 8 ), ri( data, voff + j * 8 + 4 ) ] ); + + } + + if ( type == 8 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readShort( data, ( num < 3 ? offset - 4 : voff ) + 2 * j ) ); + + } + + if ( type == 9 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readInt( data, ( num < 2 ? offset - 4 : voff ) + 4 * j ) ); + + } + + if ( type == 11 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readFloat( data, voff + j * 4 ) ); + + } + + if ( type == 12 ) { + + for ( var j = 0; j < num; j ++ ) arr.push( bin.readDouble( data, voff + j * 8 ) ); + + } + + if ( num != 0 && arr.length == 0 ) { + + console.log( tag, 'unknown TIFF tag type: ', type, 'num:', num ); if ( i == 0 ) return; continue; + + } + + if ( prm.debug ) console.log( ' '.repeat( depth ), tag, type, UTIF.tags[ tag ], arr ); + + ifd[ 't' + tag ] = arr; + + if ( tag == 330 || tag == 34665 || tag == 34853 || ( tag == 50740 && bin.readUshort( data, bin.readUint( arr, 0 ) ) < 300 ) || tag == 61440 ) { + + var oarr = tag == 50740 ? [ bin.readUint( arr, 0 ) ] : arr; + var subfd = []; + for ( var j = 0; j < oarr.length; j ++ ) UTIF._readIFD( bin, data, oarr[ j ], subfd, depth + 1, prm ); + if ( tag == 330 ) ifd.subIFD = subfd; + if ( tag == 34665 ) ifd.exifIFD = subfd[ 0 ]; + if ( tag == 34853 ) ifd.gpsiIFD = subfd[ 0 ]; //console.log("gps", subfd[0]); } + if ( tag == 50740 ) ifd.dngPrvt = subfd[ 0 ]; + if ( tag == 61440 ) ifd.fujiIFD = subfd[ 0 ]; + + } + + if ( tag == 37500 && prm.parseMN ) { + + var mn = arr; + //console.log(bin.readASCII(mn,0,mn.length), mn); + if ( bin.readASCII( mn, 0, 5 ) == 'Nikon' ) ifd.makerNote = UTIF[ 'decode' ]( mn.slice( 10 ).buffer )[ 0 ]; + else if ( bin.readUshort( data, voff ) < 300 && bin.readUshort( data, voff + 4 ) <= 12 ) { + + var subsub = []; UTIF._readIFD( bin, data, voff, subsub, depth + 1, prm ); + ifd.makerNote = subsub[ 0 ]; + + } + + } + + } + + ifds.push( ifd ); + if ( prm.debug ) console.log( ' '.repeat( depth ), '<<<---------------' ); + return offset; + +}; + +UTIF.toRGBA = function ( out, type ) { + + const w = out.width, h = out.height, area = w * h, data = out.data; + + let img; + + switch ( type ) { + + case HalfFloatType: + + img = new Uint16Array( area * 4 ); + break; + + case FloatType: + + img = new Float32Array( area * 4 ); + break; + + default: + console.error( 'THREE.LogLuvLoader: Unsupported texture data type:', type ); + + } + + let intp = out[ 't262' ] ? out[ 't262' ][ 0 ] : 2; + const bps = out[ 't258' ] ? Math.min( 32, out[ 't258' ][ 0 ] ) : 1; + + if ( out[ 't262' ] == null && bps == 1 ) intp = 0; + + if ( intp == 32845 ) { + + for ( let y = 0; y < h; y ++ ) { + + for ( let x = 0; x < w; x ++ ) { + + const si = ( y * w + x ) * 6, qi = ( y * w + x ) * 4; + let L = ( data[ si + 1 ] << 8 ) | data[ si ]; + + L = Math.pow( 2, ( L + 0.5 ) / 256 - 64 ); + const u = ( data[ si + 3 ] + 0.5 ) / 410; + const v = ( data[ si + 5 ] + 0.5 ) / 410; + + // Luv to xyY + const sX = ( 9 * u ) / ( 6 * u - 16 * v + 12 ); + const sY = ( 4 * v ) / ( 6 * u - 16 * v + 12 ); + const bY = L; + + // xyY to XYZ + const X = ( sX * bY ) / sY, Y = bY, Z = ( 1 - sX - sY ) * bY / sY; + + // XYZ to linear RGB + const r = 2.690 * X - 1.276 * Y - 0.414 * Z; + const g = - 1.022 * X + 1.978 * Y + 0.044 * Z; + const b = 0.061 * X - 0.224 * Y + 1.163 * Z; + + if ( type === HalfFloatType ) { + + img[ qi ] = DataUtils.toHalfFloat( Math.min( r, 65504 ) ); + img[ qi + 1 ] = DataUtils.toHalfFloat( Math.min( g, 65504 ) ); + img[ qi + 2 ] = DataUtils.toHalfFloat( Math.min( b, 65504 ) ); + img[ qi + 3 ] = DataUtils.toHalfFloat( 1 ); + + + } else { + + img[ qi ] = r; + img[ qi + 1 ] = g; + img[ qi + 2 ] = b; + img[ qi + 3 ] = 1; + + } + + } + + } + + } else { + + console.log( 'Unsupported Photometric interpretation: ' + intp ); + + } + + return img; + +}; + +UTIF._binBE = +{ + nextZero: function ( data, o ) { + + while ( data[ o ] != 0 ) o ++; return o; + + }, + readUshort: function ( buff, p ) { + + return ( buff[ p ] << 8 ) | buff[ p + 1 ]; + + }, + readShort: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 1 ]; a[ 1 ] = buff[ p + 0 ]; return UTIF._binBE.i16[ 0 ]; + + }, + readInt: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.i32[ 0 ]; + + }, + readUint: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 3 ]; a[ 1 ] = buff[ p + 2 ]; a[ 2 ] = buff[ p + 1 ]; a[ 3 ] = buff[ p + 0 ]; return UTIF._binBE.ui32[ 0 ]; + + }, + readASCII: function ( buff, p, l ) { + + var s = ''; for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] ); return s; + + }, + readFloat: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + 3 - i ]; return UTIF._binBE.fl32[ 0 ]; + + }, + readDouble: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + 7 - i ]; return UTIF._binBE.fl64[ 0 ]; + + }, + + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n >> 8 ) & 255; buff[ p + 1 ] = n & 255; + + }, + writeInt: function ( buff, p, n ) { + + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 3 ] = a[ 0 ]; buff[ p + 2 ] = a[ 1 ]; buff[ p + 1 ] = a[ 2 ]; buff[ p + 0 ] = a[ 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 >> 0 ) & 255; + + }, + writeASCII: function ( buff, p, s ) { + + for ( var i = 0; i < s.length; i ++ ) buff[ p + i ] = s.charCodeAt( i ); + + }, + writeDouble: function ( buff, p, n ) { + + UTIF._binBE.fl64[ 0 ] = n; + for ( var i = 0; i < 8; i ++ ) buff[ p + i ] = UTIF._binBE.ui8[ 7 - i ]; + + } +}; +UTIF._binBE.ui8 = new Uint8Array( 8 ); +UTIF._binBE.i16 = new Int16Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.i32 = new Int32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.ui32 = new Uint32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl32 = new Float32Array( UTIF._binBE.ui8.buffer ); +UTIF._binBE.fl64 = new Float64Array( UTIF._binBE.ui8.buffer ); + +UTIF._binLE = +{ + nextZero: UTIF._binBE.nextZero, + readUshort: function ( buff, p ) { + + return ( buff[ p + 1 ] << 8 ) | buff[ p ]; + + }, + readShort: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; return UTIF._binBE.i16[ 0 ]; + + }, + readInt: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.i32[ 0 ]; + + }, + readUint: function ( buff, p ) { + + var a = UTIF._binBE.ui8; a[ 0 ] = buff[ p + 0 ]; a[ 1 ] = buff[ p + 1 ]; a[ 2 ] = buff[ p + 2 ]; a[ 3 ] = buff[ p + 3 ]; return UTIF._binBE.ui32[ 0 ]; + + }, + readASCII: UTIF._binBE.readASCII, + readFloat: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 4; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl32[ 0 ]; + + }, + readDouble: function ( buff, p ) { + + var a = UTIF._binBE.ui8; for ( var i = 0; i < 8; i ++ ) a[ i ] = buff[ p + i ]; return UTIF._binBE.fl64[ 0 ]; + + }, + + writeUshort: function ( buff, p, n ) { + + buff[ p ] = ( n ) & 255; buff[ p + 1 ] = ( n >> 8 ) & 255; + + }, + writeInt: function ( buff, p, n ) { + + var a = UTIF._binBE.ui8; UTIF._binBE.i32[ 0 ] = n; buff[ p + 0 ] = a[ 0 ]; buff[ p + 1 ] = a[ 1 ]; buff[ p + 2 ] = a[ 2 ]; buff[ p + 3 ] = a[ 3 ]; + + }, + writeUint: function ( buff, p, n ) { + + buff[ p ] = ( n >>> 0 ) & 255; buff[ p + 1 ] = ( n >>> 8 ) & 255; buff[ p + 2 ] = ( n >>> 16 ) & 255; buff[ p + 3 ] = ( n >>> 24 ) & 255; + + }, + writeASCII: UTIF._binBE.writeASCII +}; +UTIF._copyTile = function ( tb, tw, th, b, w, h, xoff, yoff ) { + + //log("copyTile", tw, th, w, h, xoff, yoff); + var xlim = Math.min( tw, w - xoff ); + var ylim = Math.min( th, h - yoff ); + for ( var y = 0; y < ylim; y ++ ) { + + var tof = ( yoff + y ) * w + xoff; + var sof = y * tw; + for ( var x = 0; x < xlim; x ++ ) b[ tof + x ] = tb[ sof + x ]; + + } + +}; + +export { LogLuvLoader }; diff --git a/jsm/loaders/LottieLoader.js b/jsm/loaders/LottieLoader.js new file mode 100644 index 0000000..d323bf4 --- /dev/null +++ b/jsm/loaders/LottieLoader.js @@ -0,0 +1,73 @@ +import { + FileLoader, + Loader, + CanvasTexture, + NearestFilter +} from 'three'; + +class LottieLoader extends Loader { + + setQuality( value ) { + + this._quality = value; + + } + + load( url, onLoad, onProgress, onError ) { + + const quality = this._quality || 1; + + const texture = new CanvasTexture(); + texture.minFilter = NearestFilter; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, function ( text ) { + + const data = JSON.parse( text ); + + // bodymoving uses container.offetWidth and offsetHeight + // to define width/height + + const container = document.createElement( 'div' ); + container.style.width = data.w + 'px'; + container.style.height = data.h + 'px'; + document.body.appendChild( container ); + + const animation = bodymovin.loadAnimation( { + container: container, + animType: 'canvas', + loop: true, + autoplay: true, + animationData: data, + rendererSettings: { dpr: quality } + } ); + + texture.animation = animation; + texture.image = animation.container; + + animation.addEventListener( 'enterFrame', function () { + + texture.needsUpdate = true; + + } ); + + container.style.display = 'none'; + + if ( onLoad !== undefined ) { + + onLoad( texture ); + + } + + }, onProgress, onError ); + + return texture; + + } + +} + +export { LottieLoader }; diff --git a/jsm/loaders/MD2Loader.js b/jsm/loaders/MD2Loader.js new file mode 100644 index 0000000..2d88be4 --- /dev/null +++ b/jsm/loaders/MD2Loader.js @@ -0,0 +1,399 @@ +import { + AnimationClip, + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + Vector3 +} from 'three'; + +const _normalData = [ + [ - 0.525731, 0.000000, 0.850651 ], [ - 0.442863, 0.238856, 0.864188 ], + [ - 0.295242, 0.000000, 0.955423 ], [ - 0.309017, 0.500000, 0.809017 ], + [ - 0.162460, 0.262866, 0.951056 ], [ 0.000000, 0.000000, 1.000000 ], + [ 0.000000, 0.850651, 0.525731 ], [ - 0.147621, 0.716567, 0.681718 ], + [ 0.147621, 0.716567, 0.681718 ], [ 0.000000, 0.525731, 0.850651 ], + [ 0.309017, 0.500000, 0.809017 ], [ 0.525731, 0.000000, 0.850651 ], + [ 0.295242, 0.000000, 0.955423 ], [ 0.442863, 0.238856, 0.864188 ], + [ 0.162460, 0.262866, 0.951056 ], [ - 0.681718, 0.147621, 0.716567 ], + [ - 0.809017, 0.309017, 0.500000 ], [ - 0.587785, 0.425325, 0.688191 ], + [ - 0.850651, 0.525731, 0.000000 ], [ - 0.864188, 0.442863, 0.238856 ], + [ - 0.716567, 0.681718, 0.147621 ], [ - 0.688191, 0.587785, 0.425325 ], + [ - 0.500000, 0.809017, 0.309017 ], [ - 0.238856, 0.864188, 0.442863 ], + [ - 0.425325, 0.688191, 0.587785 ], [ - 0.716567, 0.681718, - 0.147621 ], + [ - 0.500000, 0.809017, - 0.309017 ], [ - 0.525731, 0.850651, 0.000000 ], + [ 0.000000, 0.850651, - 0.525731 ], [ - 0.238856, 0.864188, - 0.442863 ], + [ 0.000000, 0.955423, - 0.295242 ], [ - 0.262866, 0.951056, - 0.162460 ], + [ 0.000000, 1.000000, 0.000000 ], [ 0.000000, 0.955423, 0.295242 ], + [ - 0.262866, 0.951056, 0.162460 ], [ 0.238856, 0.864188, 0.442863 ], + [ 0.262866, 0.951056, 0.162460 ], [ 0.500000, 0.809017, 0.309017 ], + [ 0.238856, 0.864188, - 0.442863 ], [ 0.262866, 0.951056, - 0.162460 ], + [ 0.500000, 0.809017, - 0.309017 ], [ 0.850651, 0.525731, 0.000000 ], + [ 0.716567, 0.681718, 0.147621 ], [ 0.716567, 0.681718, - 0.147621 ], + [ 0.525731, 0.850651, 0.000000 ], [ 0.425325, 0.688191, 0.587785 ], + [ 0.864188, 0.442863, 0.238856 ], [ 0.688191, 0.587785, 0.425325 ], + [ 0.809017, 0.309017, 0.500000 ], [ 0.681718, 0.147621, 0.716567 ], + [ 0.587785, 0.425325, 0.688191 ], [ 0.955423, 0.295242, 0.000000 ], + [ 1.000000, 0.000000, 0.000000 ], [ 0.951056, 0.162460, 0.262866 ], + [ 0.850651, - 0.525731, 0.000000 ], [ 0.955423, - 0.295242, 0.000000 ], + [ 0.864188, - 0.442863, 0.238856 ], [ 0.951056, - 0.162460, 0.262866 ], + [ 0.809017, - 0.309017, 0.500000 ], [ 0.681718, - 0.147621, 0.716567 ], + [ 0.850651, 0.000000, 0.525731 ], [ 0.864188, 0.442863, - 0.238856 ], + [ 0.809017, 0.309017, - 0.500000 ], [ 0.951056, 0.162460, - 0.262866 ], + [ 0.525731, 0.000000, - 0.850651 ], [ 0.681718, 0.147621, - 0.716567 ], + [ 0.681718, - 0.147621, - 0.716567 ], [ 0.850651, 0.000000, - 0.525731 ], + [ 0.809017, - 0.309017, - 0.500000 ], [ 0.864188, - 0.442863, - 0.238856 ], + [ 0.951056, - 0.162460, - 0.262866 ], [ 0.147621, 0.716567, - 0.681718 ], + [ 0.309017, 0.500000, - 0.809017 ], [ 0.425325, 0.688191, - 0.587785 ], + [ 0.442863, 0.238856, - 0.864188 ], [ 0.587785, 0.425325, - 0.688191 ], + [ 0.688191, 0.587785, - 0.425325 ], [ - 0.147621, 0.716567, - 0.681718 ], + [ - 0.309017, 0.500000, - 0.809017 ], [ 0.000000, 0.525731, - 0.850651 ], + [ - 0.525731, 0.000000, - 0.850651 ], [ - 0.442863, 0.238856, - 0.864188 ], + [ - 0.295242, 0.000000, - 0.955423 ], [ - 0.162460, 0.262866, - 0.951056 ], + [ 0.000000, 0.000000, - 1.000000 ], [ 0.295242, 0.000000, - 0.955423 ], + [ 0.162460, 0.262866, - 0.951056 ], [ - 0.442863, - 0.238856, - 0.864188 ], + [ - 0.309017, - 0.500000, - 0.809017 ], [ - 0.162460, - 0.262866, - 0.951056 ], + [ 0.000000, - 0.850651, - 0.525731 ], [ - 0.147621, - 0.716567, - 0.681718 ], + [ 0.147621, - 0.716567, - 0.681718 ], [ 0.000000, - 0.525731, - 0.850651 ], + [ 0.309017, - 0.500000, - 0.809017 ], [ 0.442863, - 0.238856, - 0.864188 ], + [ 0.162460, - 0.262866, - 0.951056 ], [ 0.238856, - 0.864188, - 0.442863 ], + [ 0.500000, - 0.809017, - 0.309017 ], [ 0.425325, - 0.688191, - 0.587785 ], + [ 0.716567, - 0.681718, - 0.147621 ], [ 0.688191, - 0.587785, - 0.425325 ], + [ 0.587785, - 0.425325, - 0.688191 ], [ 0.000000, - 0.955423, - 0.295242 ], + [ 0.000000, - 1.000000, 0.000000 ], [ 0.262866, - 0.951056, - 0.162460 ], + [ 0.000000, - 0.850651, 0.525731 ], [ 0.000000, - 0.955423, 0.295242 ], + [ 0.238856, - 0.864188, 0.442863 ], [ 0.262866, - 0.951056, 0.162460 ], + [ 0.500000, - 0.809017, 0.309017 ], [ 0.716567, - 0.681718, 0.147621 ], + [ 0.525731, - 0.850651, 0.000000 ], [ - 0.238856, - 0.864188, - 0.442863 ], + [ - 0.500000, - 0.809017, - 0.309017 ], [ - 0.262866, - 0.951056, - 0.162460 ], + [ - 0.850651, - 0.525731, 0.000000 ], [ - 0.716567, - 0.681718, - 0.147621 ], + [ - 0.716567, - 0.681718, 0.147621 ], [ - 0.525731, - 0.850651, 0.000000 ], + [ - 0.500000, - 0.809017, 0.309017 ], [ - 0.238856, - 0.864188, 0.442863 ], + [ - 0.262866, - 0.951056, 0.162460 ], [ - 0.864188, - 0.442863, 0.238856 ], + [ - 0.809017, - 0.309017, 0.500000 ], [ - 0.688191, - 0.587785, 0.425325 ], + [ - 0.681718, - 0.147621, 0.716567 ], [ - 0.442863, - 0.238856, 0.864188 ], + [ - 0.587785, - 0.425325, 0.688191 ], [ - 0.309017, - 0.500000, 0.809017 ], + [ - 0.147621, - 0.716567, 0.681718 ], [ - 0.425325, - 0.688191, 0.587785 ], + [ - 0.162460, - 0.262866, 0.951056 ], [ 0.442863, - 0.238856, 0.864188 ], + [ 0.162460, - 0.262866, 0.951056 ], [ 0.309017, - 0.500000, 0.809017 ], + [ 0.147621, - 0.716567, 0.681718 ], [ 0.000000, - 0.525731, 0.850651 ], + [ 0.425325, - 0.688191, 0.587785 ], [ 0.587785, - 0.425325, 0.688191 ], + [ 0.688191, - 0.587785, 0.425325 ], [ - 0.955423, 0.295242, 0.000000 ], + [ - 0.951056, 0.162460, 0.262866 ], [ - 1.000000, 0.000000, 0.000000 ], + [ - 0.850651, 0.000000, 0.525731 ], [ - 0.955423, - 0.295242, 0.000000 ], + [ - 0.951056, - 0.162460, 0.262866 ], [ - 0.864188, 0.442863, - 0.238856 ], + [ - 0.951056, 0.162460, - 0.262866 ], [ - 0.809017, 0.309017, - 0.500000 ], + [ - 0.864188, - 0.442863, - 0.238856 ], [ - 0.951056, - 0.162460, - 0.262866 ], + [ - 0.809017, - 0.309017, - 0.500000 ], [ - 0.681718, 0.147621, - 0.716567 ], + [ - 0.681718, - 0.147621, - 0.716567 ], [ - 0.850651, 0.000000, - 0.525731 ], + [ - 0.688191, 0.587785, - 0.425325 ], [ - 0.587785, 0.425325, - 0.688191 ], + [ - 0.425325, 0.688191, - 0.587785 ], [ - 0.425325, - 0.688191, - 0.587785 ], + [ - 0.587785, - 0.425325, - 0.688191 ], [ - 0.688191, - 0.587785, - 0.425325 ] +]; + +class MD2Loader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( buffer ) { + + try { + + onLoad( scope.parse( buffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( buffer ) { + + const data = new DataView( buffer ); + + // http://tfc.duke.free.fr/coding/md2-specs-en.html + + const header = {}; + const headerNames = [ + 'ident', 'version', + 'skinwidth', 'skinheight', + 'framesize', + 'num_skins', 'num_vertices', 'num_st', 'num_tris', 'num_glcmds', 'num_frames', + 'offset_skins', 'offset_st', 'offset_tris', 'offset_frames', 'offset_glcmds', 'offset_end' + ]; + + for ( let i = 0; i < headerNames.length; i ++ ) { + + header[ headerNames[ i ] ] = data.getInt32( i * 4, true ); + + } + + if ( header.ident !== 844121161 || header.version !== 8 ) { + + console.error( 'Not a valid MD2 file' ); + return; + + } + + if ( header.offset_end !== data.byteLength ) { + + console.error( 'Corrupted MD2 file' ); + return; + + } + + // + + const geometry = new BufferGeometry(); + + // uvs + + const uvsTemp = []; + let offset = header.offset_st; + + for ( let i = 0, l = header.num_st; i < l; i ++ ) { + + const u = data.getInt16( offset + 0, true ); + const v = data.getInt16( offset + 2, true ); + + uvsTemp.push( u / header.skinwidth, 1 - ( v / header.skinheight ) ); + + offset += 4; + + } + + // triangles + + offset = header.offset_tris; + + const vertexIndices = []; + const uvIndices = []; + + for ( let i = 0, l = header.num_tris; i < l; i ++ ) { + + vertexIndices.push( + data.getUint16( offset + 0, true ), + data.getUint16( offset + 2, true ), + data.getUint16( offset + 4, true ) + ); + + uvIndices.push( + data.getUint16( offset + 6, true ), + data.getUint16( offset + 8, true ), + data.getUint16( offset + 10, true ) + ); + + offset += 12; + + } + + // frames + + const translation = new Vector3(); + const scale = new Vector3(); + + const frames = []; + + offset = header.offset_frames; + + for ( let i = 0, l = header.num_frames; i < l; i ++ ) { + + scale.set( + data.getFloat32( offset + 0, true ), + data.getFloat32( offset + 4, true ), + data.getFloat32( offset + 8, true ) + ); + + translation.set( + data.getFloat32( offset + 12, true ), + data.getFloat32( offset + 16, true ), + data.getFloat32( offset + 20, true ) + ); + + offset += 24; + + const string = []; + + for ( let j = 0; j < 16; j ++ ) { + + const character = data.getUint8( offset + j ); + if ( character === 0 ) break; + + string[ j ] = character; + + } + + const frame = { + name: String.fromCharCode.apply( null, string ), + vertices: [], + normals: [] + }; + + offset += 16; + + for ( let j = 0; j < header.num_vertices; j ++ ) { + + let x = data.getUint8( offset ++ ); + let y = data.getUint8( offset ++ ); + let z = data.getUint8( offset ++ ); + const n = _normalData[ data.getUint8( offset ++ ) ]; + + x = x * scale.x + translation.x; + y = y * scale.y + translation.y; + z = z * scale.z + translation.z; + + frame.vertices.push( x, z, y ); // convert to Y-up + frame.normals.push( n[ 0 ], n[ 2 ], n[ 1 ] ); // convert to Y-up + + } + + frames.push( frame ); + + } + + // static + + const positions = []; + const normals = []; + const uvs = []; + + const verticesTemp = frames[ 0 ].vertices; + const normalsTemp = frames[ 0 ].normals; + + for ( let i = 0, l = vertexIndices.length; i < l; i ++ ) { + + const vertexIndex = vertexIndices[ i ]; + let stride = vertexIndex * 3; + + // + + const x = verticesTemp[ stride ]; + const y = verticesTemp[ stride + 1 ]; + const z = verticesTemp[ stride + 2 ]; + + positions.push( x, y, z ); + + // + + const nx = normalsTemp[ stride ]; + const ny = normalsTemp[ stride + 1 ]; + const nz = normalsTemp[ stride + 2 ]; + + normals.push( nx, ny, nz ); + + // + + const uvIndex = uvIndices[ i ]; + stride = uvIndex * 2; + + const u = uvsTemp[ stride ]; + const v = uvsTemp[ stride + 1 ]; + + uvs.push( u, v ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + + // animation + + const morphPositions = []; + const morphNormals = []; + + for ( let i = 0, l = frames.length; i < l; i ++ ) { + + const frame = frames[ i ]; + const attributeName = frame.name; + + if ( frame.vertices.length > 0 ) { + + const positions = []; + + for ( let j = 0, jl = vertexIndices.length; j < jl; j ++ ) { + + const vertexIndex = vertexIndices[ j ]; + const stride = vertexIndex * 3; + + const x = frame.vertices[ stride ]; + const y = frame.vertices[ stride + 1 ]; + const z = frame.vertices[ stride + 2 ]; + + positions.push( x, y, z ); + + } + + const positionAttribute = new Float32BufferAttribute( positions, 3 ); + positionAttribute.name = attributeName; + + morphPositions.push( positionAttribute ); + + } + + if ( frame.normals.length > 0 ) { + + const normals = []; + + for ( let j = 0, jl = vertexIndices.length; j < jl; j ++ ) { + + const vertexIndex = vertexIndices[ j ]; + const stride = vertexIndex * 3; + + const nx = frame.normals[ stride ]; + const ny = frame.normals[ stride + 1 ]; + const nz = frame.normals[ stride + 2 ]; + + normals.push( nx, ny, nz ); + + } + + const normalAttribute = new Float32BufferAttribute( normals, 3 ); + normalAttribute.name = attributeName; + + morphNormals.push( normalAttribute ); + + } + + } + + geometry.morphAttributes.position = morphPositions; + geometry.morphAttributes.normal = morphNormals; + geometry.morphTargetsRelative = false; + + geometry.animations = AnimationClip.CreateClipsFromMorphTargetSequences( frames, 10 ); + + return geometry; + + } + +} + +export { MD2Loader }; diff --git a/jsm/loaders/MDDLoader.js b/jsm/loaders/MDDLoader.js new file mode 100644 index 0000000..e70f8b0 --- /dev/null +++ b/jsm/loaders/MDDLoader.js @@ -0,0 +1,102 @@ +/** + * MDD is a special format that stores a position for every vertex in a model for every frame in an animation. + * Similar to BVH, it can be used to transfer animation data between different 3D applications or engines. + * + * MDD stores its data in binary format (big endian) in the following way: + * + * number of frames (a single uint32) + * number of vertices (a single uint32) + * time values for each frame (sequence of float32) + * vertex data for each frame (sequence of float32) + */ + +import { + AnimationClip, + BufferAttribute, + FileLoader, + Loader, + NumberKeyframeTrack +} from 'three'; + +class MDDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.load( url, function ( data ) { + + onLoad( scope.parse( data ) ); + + }, onProgress, onError ); + + } + + parse( data ) { + + const view = new DataView( data ); + + const totalFrames = view.getUint32( 0 ); + const totalPoints = view.getUint32( 4 ); + + let offset = 8; + + // animation clip + + const times = new Float32Array( totalFrames ); + const values = new Float32Array( totalFrames * totalFrames ).fill( 0 ); + + for ( let i = 0; i < totalFrames; i ++ ) { + + times[ i ] = view.getFloat32( offset ); offset += 4; + values[ ( totalFrames * i ) + i ] = 1; + + } + + const track = new NumberKeyframeTrack( '.morphTargetInfluences', times, values ); + const clip = new AnimationClip( 'default', times[ times.length - 1 ], [ track ] ); + + // morph targets + + const morphTargets = []; + + for ( let i = 0; i < totalFrames; i ++ ) { + + const morphTarget = new Float32Array( totalPoints * 3 ); + + for ( let j = 0; j < totalPoints; j ++ ) { + + const stride = ( j * 3 ); + + morphTarget[ stride + 0 ] = view.getFloat32( offset ); offset += 4; // x + morphTarget[ stride + 1 ] = view.getFloat32( offset ); offset += 4; // y + morphTarget[ stride + 2 ] = view.getFloat32( offset ); offset += 4; // z + + } + + const attribute = new BufferAttribute( morphTarget, 3 ); + attribute.name = 'morph_' + i; + + morphTargets.push( attribute ); + + } + + return { + morphTargets: morphTargets, + clip: clip + }; + + } + +} + +export { MDDLoader }; diff --git a/jsm/loaders/MMDLoader.js b/jsm/loaders/MMDLoader.js new file mode 100644 index 0000000..eb83354 --- /dev/null +++ b/jsm/loaders/MMDLoader.js @@ -0,0 +1,2224 @@ +import { + AddOperation, + AnimationClip, + Bone, + BufferGeometry, + Color, + CustomBlending, + TangentSpaceNormalMap, + DoubleSide, + DstAlphaFactor, + Euler, + FileLoader, + Float32BufferAttribute, + FrontSide, + Interpolant, + Loader, + LoaderUtils, + UniformsUtils, + ShaderMaterial, + MultiplyOperation, + NearestFilter, + NumberKeyframeTrack, + OneMinusSrcAlphaFactor, + Quaternion, + QuaternionKeyframeTrack, + RepeatWrapping, + Skeleton, + SkinnedMesh, + SrcAlphaFactor, + TextureLoader, + Uint16BufferAttribute, + Vector3, + VectorKeyframeTrack, + RGB_S3TC_DXT1_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_PVRTC_2BPPV1_Format, + RGB_ETC1_Format, + RGB_ETC2_Format +} from 'three'; +import { MMDToonShader } from '../shaders/MMDToonShader.js'; +import { TGALoader } from '../loaders/TGALoader.js'; +import { MMDParser } from '../libs/mmdparser.module.js'; + +/** + * Dependencies + * - mmd-parser https://github.com/takahirox/mmd-parser + * - TGALoader + * - OutlineEffect + * + * MMDLoader creates Three.js Objects from MMD resources as + * PMD, PMX, VMD, and VPD files. + * + * PMD/PMX is a model data format, VMD is a motion data format + * VPD is a posing data format used in MMD(Miku Miku Dance). + * + * MMD official site + * - https://sites.google.com/view/evpvp/ + * + * PMD, VMD format (in Japanese) + * - http://blog.goo.ne.jp/torisu_tetosuki/e/209ad341d3ece2b1b4df24abf619d6e4 + * + * PMX format + * - https://gist.github.com/felixjones/f8a06bd48f9da9a4539f + * + * TODO + * - light motion in vmd support. + * - SDEF support. + * - uv/material/bone morphing support. + * - more precise grant skinning support. + * - shadow support. + */ + +/** + * @param {THREE.LoadingManager} manager + */ +class MMDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.loader = new FileLoader( this.manager ); + + this.parser = null; // lazy generation + this.meshBuilder = new MeshBuilder( this.manager ); + this.animationBuilder = new AnimationBuilder(); + + } + + /** + * @param {string} animationPath + * @return {MMDLoader} + */ + setAnimationPath( animationPath ) { + + this.animationPath = animationPath; + return this; + + } + + // Load MMD assets as Three.js Object + + /** + * Loads Model file (.pmd or .pmx) as a SkinnedMesh. + * + * @param {string} url - url to Model(.pmd or .pmx) file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + load( url, onLoad, onProgress, onError ) { + + const builder = this.meshBuilder.setCrossOrigin( this.crossOrigin ); + + // resource path + + let resourcePath; + + if ( this.resourcePath !== '' ) { + + resourcePath = this.resourcePath; + + } else if ( this.path !== '' ) { + + resourcePath = this.path; + + } else { + + resourcePath = LoaderUtils.extractUrlBase( url ); + + } + + const modelExtension = this._extractExtension( url ).toLowerCase(); + + // Should I detect by seeing header? + if ( modelExtension !== 'pmd' && modelExtension !== 'pmx' ) { + + if ( onError ) onError( new Error( 'THREE.MMDLoader: Unknown model file extension .' + modelExtension + '.' ) ); + + return; + + } + + this[ modelExtension === 'pmd' ? 'loadPMD' : 'loadPMX' ]( url, function ( data ) { + + onLoad( builder.build( data, resourcePath, onProgress, onError ) ); + + }, onProgress, onError ); + + } + + /** + * Loads Motion file(s) (.vmd) as a AnimationClip. + * If two or more files are specified, they'll be merged. + * + * @param {string|Array} url - url(s) to animation(.vmd) file(s) + * @param {SkinnedMesh|THREE.Camera} object - tracks will be fitting to this object + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadAnimation( url, object, onLoad, onProgress, onError ) { + + const builder = this.animationBuilder; + + this.loadVMD( url, function ( vmd ) { + + onLoad( object.isCamera + ? builder.buildCameraAnimation( vmd ) + : builder.build( vmd, object ) ); + + }, onProgress, onError ); + + } + + /** + * Loads mode file and motion file(s) as an object containing + * a SkinnedMesh and a AnimationClip. + * Tracks of AnimationClip are fitting to the model. + * + * @param {string} modelUrl - url to Model(.pmd or .pmx) file + * @param {string|Array{string}} vmdUrl - url(s) to animation(.vmd) file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadWithAnimation( modelUrl, vmdUrl, onLoad, onProgress, onError ) { + + const scope = this; + + this.load( modelUrl, function ( mesh ) { + + scope.loadAnimation( vmdUrl, mesh, function ( animation ) { + + onLoad( { + mesh: mesh, + animation: animation + } ); + + }, onProgress, onError ); + + }, onProgress, onError ); + + } + + // Load MMD assets as Object data parsed by MMDParser + + /** + * Loads .pmd file as an Object. + * + * @param {string} url - url to .pmd file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadPMD( url, onLoad, onProgress, onError ) { + + const parser = this._getParser(); + + this.loader + .setMimeType( undefined ) + .setPath( this.path ) + .setResponseType( 'arraybuffer' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ) + .load( url, function ( buffer ) { + + onLoad( parser.parsePmd( buffer, true ) ); + + }, onProgress, onError ); + + } + + /** + * Loads .pmx file as an Object. + * + * @param {string} url - url to .pmx file + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadPMX( url, onLoad, onProgress, onError ) { + + const parser = this._getParser(); + + this.loader + .setMimeType( undefined ) + .setPath( this.path ) + .setResponseType( 'arraybuffer' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ) + .load( url, function ( buffer ) { + + onLoad( parser.parsePmx( buffer, true ) ); + + }, onProgress, onError ); + + } + + /** + * Loads .vmd file as an Object. If two or more files are specified + * they'll be merged. + * + * @param {string|Array} url - url(s) to .vmd file(s) + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadVMD( url, onLoad, onProgress, onError ) { + + const urls = Array.isArray( url ) ? url : [ url ]; + + const vmds = []; + const vmdNum = urls.length; + + const parser = this._getParser(); + + this.loader + .setMimeType( undefined ) + .setPath( this.animationPath ) + .setResponseType( 'arraybuffer' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ); + + for ( let i = 0, il = urls.length; i < il; i ++ ) { + + this.loader.load( urls[ i ], function ( buffer ) { + + vmds.push( parser.parseVmd( buffer, true ) ); + + if ( vmds.length === vmdNum ) onLoad( parser.mergeVmds( vmds ) ); + + }, onProgress, onError ); + + } + + } + + /** + * Loads .vpd file as an Object. + * + * @param {string} url - url to .vpd file + * @param {boolean} isUnicode + * @param {function} onLoad + * @param {function} onProgress + * @param {function} onError + */ + loadVPD( url, isUnicode, onLoad, onProgress, onError ) { + + const parser = this._getParser(); + + this.loader + .setMimeType( isUnicode ? undefined : 'text/plain; charset=shift_jis' ) + .setPath( this.animationPath ) + .setResponseType( 'text' ) + .setRequestHeader( this.requestHeader ) + .setWithCredentials( this.withCredentials ) + .load( url, function ( text ) { + + onLoad( parser.parseVpd( text, true ) ); + + }, onProgress, onError ); + + } + + // private methods + + _extractExtension( url ) { + + const index = url.lastIndexOf( '.' ); + return index < 0 ? '' : url.slice( index + 1 ); + + } + + _getParser() { + + if ( this.parser === null ) { + + if ( typeof MMDParser === 'undefined' ) { + + throw new Error( 'THREE.MMDLoader: Import MMDParser https://github.com/takahirox/mmd-parser' ); + + } + + this.parser = new MMDParser.Parser(); // eslint-disable-line no-undef + + } + + return this.parser; + + } + +} + +// Utilities + +/* + * base64 encoded defalut toon textures toon00.bmp - toon10.bmp. + * We don't need to request external toon image files. + */ +const DEFAULT_TOON_TEXTURES = [ + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/bWiiMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh8aBHZBl14e8wAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOUlEQVRYR+3WMREAMAwDsYY/yoDI7MLwIiP40+RJklfcCCBAgAABAgTqArfb/QMCCBAgQIAAgbbAB3z/e0F3js2cAAAAAElFTkSuQmCC', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAN0lEQVRYR+3WQREAMBACsZ5/B5ilMvgEBTt5cW37hjsBBAgQIECAwFwgyfYPCCBAgAABAgTWAh81dWyx0gFwKAAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAOklEQVRYR+3WoREAMAwDsWb/UQtCy9wxTOQJ/oQ8SXKKGwEECBAgQIBAXeDt7f4BAQQIECBAgEBb4AOz8Hzx7WLY4wAAAABJRU5ErkJggg==', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABPUlEQVRYR+1XwW7CMAy1+f9fZOMysSEOEweEOPRNdm3HbdOyIhAcklPrOs/PLy9RygBALxzcCDQFmgJNgaZAU6Ap0BR4PwX8gsRMVLssMRH5HcpzJEaWL7EVg9F1IHRlyqQohgVr4FGUlUcMJSjcUlDw0zvjeun70cLWmneoyf7NgBTQSniBTQQSuJAZsOnnaczjIMb5hCiuHKxokCrJfVnrctyZL0PkJAJe1HMil4nxeyi3Ypfn1kX51jpPvo/JeCNC4PhVdHdJw2XjBR8brF8PEIhNVn12AgP7uHsTBguBn53MUZCqv7Lp07Pn5k1Ro+uWmUNn7D+M57rtk7aG0Vo73xyF/fbFf0bPJjDXngnGocDTdFhygZjwUQrMNrDcmZlQT50VJ/g/UwNyHpu778+yW+/ksOz/BFo54P4AsUXMfRq7XWsAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACMElEQVRYR+2Xv4pTQRTGf2dubhLdICiii2KnYKHVolhauKWPoGAnNr6BD6CvIVaihYuI2i1ia0BY0MZGRHQXjZj/mSPnnskfNWiWZUlzJ5k7M2cm833nO5Mziej2DWWJRUoCpQKlAntSQCqgw39/iUWAGmh37jrRnVsKlgpiqmkoGVABA7E57fvY+pJDdgKqF6HzFCSADkDq+F6AHABtQ+UMVE5D7zXod7fFNhTEckTbj5XQgHzNN+5tQvc5NG7C6BNkp6D3EmpXHDR+dQAjFLchW3VS9rlw3JBh+B7ys5Cf9z0GW1C/7P32AyBAOAz1q4jGliIH3YPuBnSfQX4OGreTIgEYQb/pBDtPnEQ4CivXYPAWBk13oHrB54yA9QuSn2H4AcKRpEILDt0BUzj+RLR1V5EqjD66NPRBVpLcQwjHoHYJOhsQv6U4mnzmrIXJCFr4LDwm/xBUoboG9XX4cc9VKdYoSA2yk5NQLJaKDUjTBoveG3Z2TElTxwjNK4M3LEZgUdDdruvcXzKBpStgp2NPiWi3ks9ZXxIoFVi+AvHLdc9TqtjL3/aYjpPlrzOcEnK62Szhimdd7xX232zFDTgtxezOu3WNMRLjiKgjtOhHVMd1loynVHvOgjuIIJMaELEqhJAV/RCSLbWTcfPFakFgFlALTRRvx+ok6Hlp/Q+v3fmx90bMyUzaEAhmM3KvHlXTL5DxnbGf/1M8RNNACLL5MNtPxP/mypJAqcDSFfgFhpYqWUzhTEAAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=', + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAL0lEQVRYR+3QQREAAAzCsOFfNJPBJ1XQS9r2hsUAAQIECBAgQIAAAQIECBAgsBZ4MUx/ofm2I/kAAAAASUVORK5CYII=' +]; + +const NON_ALPHA_CHANNEL_FORMATS = [ + RGB_S3TC_DXT1_Format, + RGB_PVRTC_4BPPV1_Format, + RGB_PVRTC_2BPPV1_Format, + RGB_ETC1_Format, + RGB_ETC2_Format +]; + +// Builders. They build Three.js object from Object data parsed by MMDParser. + +/** + * @param {THREE.LoadingManager} manager + */ +class MeshBuilder { + + constructor( manager ) { + + this.crossOrigin = 'anonymous'; + this.geometryBuilder = new GeometryBuilder(); + this.materialBuilder = new MaterialBuilder( manager ); + + } + + /** + * @param {string} crossOrigin + * @return {MeshBuilder} + */ + setCrossOrigin( crossOrigin ) { + + this.crossOrigin = crossOrigin; + return this; + + } + + /** + * @param {Object} data - parsed PMD/PMX data + * @param {string} resourcePath + * @param {function} onProgress + * @param {function} onError + * @return {SkinnedMesh} + */ + build( data, resourcePath, onProgress, onError ) { + + const geometry = this.geometryBuilder.build( data ); + const material = this.materialBuilder + .setCrossOrigin( this.crossOrigin ) + .setResourcePath( resourcePath ) + .build( data, geometry, onProgress, onError ); + + const mesh = new SkinnedMesh( geometry, material ); + + const skeleton = new Skeleton( initBones( mesh ) ); + mesh.bind( skeleton ); + + // console.log( mesh ); // for console debug + + return mesh; + + } + +} + +// TODO: Try to remove this function + +function initBones( mesh ) { + + const geometry = mesh.geometry; + + const bones = []; + + if ( geometry && geometry.bones !== undefined ) { + + // first, create array of 'Bone' objects from geometry data + + for ( let i = 0, il = geometry.bones.length; i < il; i ++ ) { + + const gbone = geometry.bones[ i ]; + + // create new 'Bone' object + + const bone = new Bone(); + bones.push( bone ); + + // apply values + + bone.name = gbone.name; + bone.position.fromArray( gbone.pos ); + bone.quaternion.fromArray( gbone.rotq ); + if ( gbone.scl !== undefined ) bone.scale.fromArray( gbone.scl ); + + } + + // second, create bone hierarchy + + for ( let i = 0, il = geometry.bones.length; i < il; i ++ ) { + + const gbone = geometry.bones[ i ]; + + if ( ( gbone.parent !== - 1 ) && ( gbone.parent !== null ) && ( bones[ gbone.parent ] !== undefined ) ) { + + // subsequent bones in the hierarchy + + bones[ gbone.parent ].add( bones[ i ] ); + + } else { + + // topmost bone, immediate child of the skinned mesh + + mesh.add( bones[ i ] ); + + } + + } + + } + + // now the bones are part of the scene graph and children of the skinned mesh. + // let's update the corresponding matrices + + mesh.updateMatrixWorld( true ); + + return bones; + +} + +// + +class GeometryBuilder { + + /** + * @param {Object} data - parsed PMD/PMX data + * @return {BufferGeometry} + */ + build( data ) { + + // for geometry + const positions = []; + const uvs = []; + const normals = []; + + const indices = []; + + const groups = []; + + const bones = []; + const skinIndices = []; + const skinWeights = []; + + const morphTargets = []; + const morphPositions = []; + + const iks = []; + const grants = []; + + const rigidBodies = []; + const constraints = []; + + // for work + let offset = 0; + const boneTypeTable = {}; + + // positions, normals, uvs, skinIndices, skinWeights + + for ( let i = 0; i < data.metadata.vertexCount; i ++ ) { + + const v = data.vertices[ i ]; + + for ( let j = 0, jl = v.position.length; j < jl; j ++ ) { + + positions.push( v.position[ j ] ); + + } + + for ( let j = 0, jl = v.normal.length; j < jl; j ++ ) { + + normals.push( v.normal[ j ] ); + + } + + for ( let j = 0, jl = v.uv.length; j < jl; j ++ ) { + + uvs.push( v.uv[ j ] ); + + } + + for ( let j = 0; j < 4; j ++ ) { + + skinIndices.push( v.skinIndices.length - 1 >= j ? v.skinIndices[ j ] : 0.0 ); + + } + + for ( let j = 0; j < 4; j ++ ) { + + skinWeights.push( v.skinWeights.length - 1 >= j ? v.skinWeights[ j ] : 0.0 ); + + } + + } + + // indices + + for ( let i = 0; i < data.metadata.faceCount; i ++ ) { + + const face = data.faces[ i ]; + + for ( let j = 0, jl = face.indices.length; j < jl; j ++ ) { + + indices.push( face.indices[ j ] ); + + } + + } + + // groups + + for ( let i = 0; i < data.metadata.materialCount; i ++ ) { + + const material = data.materials[ i ]; + + groups.push( { + offset: offset * 3, + count: material.faceCount * 3 + } ); + + offset += material.faceCount; + + } + + // bones + + for ( let i = 0; i < data.metadata.rigidBodyCount; i ++ ) { + + const body = data.rigidBodies[ i ]; + let value = boneTypeTable[ body.boneIndex ]; + + // keeps greater number if already value is set without any special reasons + value = value === undefined ? body.type : Math.max( body.type, value ); + + boneTypeTable[ body.boneIndex ] = value; + + } + + for ( let i = 0; i < data.metadata.boneCount; i ++ ) { + + const boneData = data.bones[ i ]; + + const bone = { + index: i, + transformationClass: boneData.transformationClass, + parent: boneData.parentIndex, + name: boneData.name, + pos: boneData.position.slice( 0, 3 ), + rotq: [ 0, 0, 0, 1 ], + scl: [ 1, 1, 1 ], + rigidBodyType: boneTypeTable[ i ] !== undefined ? boneTypeTable[ i ] : - 1 + }; + + if ( bone.parent !== - 1 ) { + + bone.pos[ 0 ] -= data.bones[ bone.parent ].position[ 0 ]; + bone.pos[ 1 ] -= data.bones[ bone.parent ].position[ 1 ]; + bone.pos[ 2 ] -= data.bones[ bone.parent ].position[ 2 ]; + + } + + bones.push( bone ); + + } + + // iks + + // TODO: remove duplicated codes between PMD and PMX + if ( data.metadata.format === 'pmd' ) { + + for ( let i = 0; i < data.metadata.ikCount; i ++ ) { + + const ik = data.iks[ i ]; + + const param = { + target: ik.target, + effector: ik.effector, + iteration: ik.iteration, + maxAngle: ik.maxAngle * 4, + links: [] + }; + + for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { + + const link = {}; + link.index = ik.links[ j ].index; + link.enabled = true; + + if ( data.bones[ link.index ].name.indexOf( 'ひざ' ) >= 0 ) { + + link.limitation = new Vector3( 1.0, 0.0, 0.0 ); + + } + + param.links.push( link ); + + } + + iks.push( param ); + + } + + } else { + + for ( let i = 0; i < data.metadata.boneCount; i ++ ) { + + const ik = data.bones[ i ].ik; + + if ( ik === undefined ) continue; + + const param = { + target: i, + effector: ik.effector, + iteration: ik.iteration, + maxAngle: ik.maxAngle, + links: [] + }; + + for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) { + + const link = {}; + link.index = ik.links[ j ].index; + link.enabled = true; + + if ( ik.links[ j ].angleLimitation === 1 ) { + + // Revert if rotationMin/Max doesn't work well + // link.limitation = new Vector3( 1.0, 0.0, 0.0 ); + + const rotationMin = ik.links[ j ].lowerLimitationAngle; + const rotationMax = ik.links[ j ].upperLimitationAngle; + + // Convert Left to Right coordinate by myself because + // MMDParser doesn't convert. It's a MMDParser's bug + + const tmp1 = - rotationMax[ 0 ]; + const tmp2 = - rotationMax[ 1 ]; + rotationMax[ 0 ] = - rotationMin[ 0 ]; + rotationMax[ 1 ] = - rotationMin[ 1 ]; + rotationMin[ 0 ] = tmp1; + rotationMin[ 1 ] = tmp2; + + link.rotationMin = new Vector3().fromArray( rotationMin ); + link.rotationMax = new Vector3().fromArray( rotationMax ); + + } + + param.links.push( link ); + + } + + iks.push( param ); + + // Save the reference even from bone data for efficiently + // simulating PMX animation system + bones[ i ].ik = param; + + } + + } + + // grants + + if ( data.metadata.format === 'pmx' ) { + + // bone index -> grant entry map + const grantEntryMap = {}; + + for ( let i = 0; i < data.metadata.boneCount; i ++ ) { + + const boneData = data.bones[ i ]; + const grant = boneData.grant; + + if ( grant === undefined ) continue; + + const param = { + index: i, + parentIndex: grant.parentIndex, + ratio: grant.ratio, + isLocal: grant.isLocal, + affectRotation: grant.affectRotation, + affectPosition: grant.affectPosition, + transformationClass: boneData.transformationClass + }; + + grantEntryMap[ i ] = { parent: null, children: [], param: param, visited: false }; + + } + + const rootEntry = { parent: null, children: [], param: null, visited: false }; + + // Build a tree representing grant hierarchy + + for ( const boneIndex in grantEntryMap ) { + + const grantEntry = grantEntryMap[ boneIndex ]; + const parentGrantEntry = grantEntryMap[ grantEntry.parentIndex ] || rootEntry; + + grantEntry.parent = parentGrantEntry; + parentGrantEntry.children.push( grantEntry ); + + } + + // Sort grant parameters from parents to children because + // grant uses parent's transform that parent's grant is already applied + // so grant should be applied in order from parents to children + + function traverse( entry ) { + + if ( entry.param ) { + + grants.push( entry.param ); + + // Save the reference even from bone data for efficiently + // simulating PMX animation system + bones[ entry.param.index ].grant = entry.param; + + } + + entry.visited = true; + + for ( let i = 0, il = entry.children.length; i < il; i ++ ) { + + const child = entry.children[ i ]; + + // Cut off a loop if exists. (Is a grant loop invalid?) + if ( ! child.visited ) traverse( child ); + + } + + } + + traverse( rootEntry ); + + } + + // morph + + function updateAttributes( attribute, morph, ratio ) { + + for ( let i = 0; i < morph.elementCount; i ++ ) { + + const element = morph.elements[ i ]; + + let index; + + if ( data.metadata.format === 'pmd' ) { + + index = data.morphs[ 0 ].elements[ element.index ].index; + + } else { + + index = element.index; + + } + + attribute.array[ index * 3 + 0 ] += element.position[ 0 ] * ratio; + attribute.array[ index * 3 + 1 ] += element.position[ 1 ] * ratio; + attribute.array[ index * 3 + 2 ] += element.position[ 2 ] * ratio; + + } + + } + + for ( let i = 0; i < data.metadata.morphCount; i ++ ) { + + const morph = data.morphs[ i ]; + const params = { name: morph.name }; + + const attribute = new Float32BufferAttribute( data.metadata.vertexCount * 3, 3 ); + attribute.name = morph.name; + + for ( let j = 0; j < data.metadata.vertexCount * 3; j ++ ) { + + attribute.array[ j ] = positions[ j ]; + + } + + if ( data.metadata.format === 'pmd' ) { + + if ( i !== 0 ) { + + updateAttributes( attribute, morph, 1.0 ); + + } + + } else { + + if ( morph.type === 0 ) { // group + + for ( let j = 0; j < morph.elementCount; j ++ ) { + + const morph2 = data.morphs[ morph.elements[ j ].index ]; + const ratio = morph.elements[ j ].ratio; + + if ( morph2.type === 1 ) { + + updateAttributes( attribute, morph2, ratio ); + + } else { + + // TODO: implement + + } + + } + + } else if ( morph.type === 1 ) { // vertex + + updateAttributes( attribute, morph, 1.0 ); + + } else if ( morph.type === 2 ) { // bone + + // TODO: implement + + } else if ( morph.type === 3 ) { // uv + + // TODO: implement + + } else if ( morph.type === 4 ) { // additional uv1 + + // TODO: implement + + } else if ( morph.type === 5 ) { // additional uv2 + + // TODO: implement + + } else if ( morph.type === 6 ) { // additional uv3 + + // TODO: implement + + } else if ( morph.type === 7 ) { // additional uv4 + + // TODO: implement + + } else if ( morph.type === 8 ) { // material + + // TODO: implement + + } + + } + + morphTargets.push( params ); + morphPositions.push( attribute ); + + } + + // rigid bodies from rigidBodies field. + + for ( let i = 0; i < data.metadata.rigidBodyCount; i ++ ) { + + const rigidBody = data.rigidBodies[ i ]; + const params = {}; + + for ( const key in rigidBody ) { + + params[ key ] = rigidBody[ key ]; + + } + + /* + * RigidBody position parameter in PMX seems global position + * while the one in PMD seems offset from corresponding bone. + * So unify being offset. + */ + if ( data.metadata.format === 'pmx' ) { + + if ( params.boneIndex !== - 1 ) { + + const bone = data.bones[ params.boneIndex ]; + params.position[ 0 ] -= bone.position[ 0 ]; + params.position[ 1 ] -= bone.position[ 1 ]; + params.position[ 2 ] -= bone.position[ 2 ]; + + } + + } + + rigidBodies.push( params ); + + } + + // constraints from constraints field. + + for ( let i = 0; i < data.metadata.constraintCount; i ++ ) { + + const constraint = data.constraints[ i ]; + const params = {}; + + for ( const key in constraint ) { + + params[ key ] = constraint[ key ]; + + } + + const bodyA = rigidBodies[ params.rigidBodyIndex1 ]; + const bodyB = rigidBodies[ params.rigidBodyIndex2 ]; + + // Refer to http://www20.atpages.jp/katwat/wp/?p=4135 + if ( bodyA.type !== 0 && bodyB.type === 2 ) { + + if ( bodyA.boneIndex !== - 1 && bodyB.boneIndex !== - 1 && + data.bones[ bodyB.boneIndex ].parentIndex === bodyA.boneIndex ) { + + bodyB.type = 1; + + } + + } + + constraints.push( params ); + + } + + // build BufferGeometry. + + const geometry = new BufferGeometry(); + + geometry.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) ); + geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + geometry.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) ); + geometry.setAttribute( 'skinIndex', new Uint16BufferAttribute( skinIndices, 4 ) ); + geometry.setAttribute( 'skinWeight', new Float32BufferAttribute( skinWeights, 4 ) ); + geometry.setIndex( indices ); + + for ( let i = 0, il = groups.length; i < il; i ++ ) { + + geometry.addGroup( groups[ i ].offset, groups[ i ].count, i ); + + } + + geometry.bones = bones; + + geometry.morphTargets = morphTargets; + geometry.morphAttributes.position = morphPositions; + geometry.morphTargetsRelative = false; + + geometry.userData.MMD = { + bones: bones, + iks: iks, + grants: grants, + rigidBodies: rigidBodies, + constraints: constraints, + format: data.metadata.format + }; + + geometry.computeBoundingSphere(); + + return geometry; + + } + +} + +// + +/** + * @param {THREE.LoadingManager} manager + */ +class MaterialBuilder { + + constructor( manager ) { + + this.manager = manager; + + this.textureLoader = new TextureLoader( this.manager ); + this.tgaLoader = null; // lazy generation + + this.crossOrigin = 'anonymous'; + this.resourcePath = undefined; + + } + + /** + * @param {string} crossOrigin + * @return {MaterialBuilder} + */ + setCrossOrigin( crossOrigin ) { + + this.crossOrigin = crossOrigin; + return this; + + } + + /** + * @param {string} resourcePath + * @return {MaterialBuilder} + */ + setResourcePath( resourcePath ) { + + this.resourcePath = resourcePath; + return this; + + } + + /** + * @param {Object} data - parsed PMD/PMX data + * @param {BufferGeometry} geometry - some properties are dependend on geometry + * @param {function} onProgress + * @param {function} onError + * @return {Array} + */ + build( data, geometry /*, onProgress, onError */ ) { + + const materials = []; + + const textures = {}; + + this.textureLoader.setCrossOrigin( this.crossOrigin ); + + // materials + + for ( let i = 0; i < data.metadata.materialCount; i ++ ) { + + const material = data.materials[ i ]; + + const params = { userData: { MMD: {} } }; + + if ( material.name !== undefined ) params.name = material.name; + + /* + * Color + * + * MMD MMDToonMaterial + * ambient - emissive * a + * (a = 1.0 without map texture or 0.2 with map texture) + * + * MMDToonMaterial doesn't have ambient. Set it to emissive instead. + * It'll be too bright if material has map texture so using coef 0.2. + */ + params.diffuse = new Color().fromArray( material.diffuse ); + params.opacity = material.diffuse[ 3 ]; + params.specular = new Color().fromArray( material.specular ); + params.shininess = material.shininess; + params.emissive = new Color().fromArray( material.ambient ); + params.transparent = params.opacity !== 1.0; + + // + + params.fog = true; + + // blend + + params.blending = CustomBlending; + params.blendSrc = SrcAlphaFactor; + params.blendDst = OneMinusSrcAlphaFactor; + params.blendSrcAlpha = SrcAlphaFactor; + params.blendDstAlpha = DstAlphaFactor; + + // side + + if ( data.metadata.format === 'pmx' && ( material.flag & 0x1 ) === 1 ) { + + params.side = DoubleSide; + + } else { + + params.side = params.opacity === 1.0 ? FrontSide : DoubleSide; + + } + + if ( data.metadata.format === 'pmd' ) { + + // map, envMap + + if ( material.fileName ) { + + const fileName = material.fileName; + const fileNames = fileName.split( '*' ); + + // fileNames[ 0 ]: mapFileName + // fileNames[ 1 ]: envMapFileName( optional ) + + params.map = this._loadTexture( fileNames[ 0 ], textures ); + + if ( fileNames.length > 1 ) { + + const extension = fileNames[ 1 ].slice( - 4 ).toLowerCase(); + + params.envMap = this._loadTexture( + fileNames[ 1 ], + textures + ); + + params.combine = extension === '.sph' + ? MultiplyOperation + : AddOperation; + + } + + } + + // gradientMap + + const toonFileName = ( material.toonIndex === - 1 ) + ? 'toon00.bmp' + : data.toonTextures[ material.toonIndex ].fileName; + + params.gradientMap = this._loadTexture( + toonFileName, + textures, + { + isToonTexture: true, + isDefaultToonTexture: this._isDefaultToonTexture( toonFileName ) + } + ); + + // parameters for OutlineEffect + + params.userData.outlineParameters = { + thickness: material.edgeFlag === 1 ? 0.003 : 0.0, + color: [ 0, 0, 0 ], + alpha: 1.0, + visible: material.edgeFlag === 1 + }; + + } else { + + // map + + if ( material.textureIndex !== - 1 ) { + + params.map = this._loadTexture( data.textures[ material.textureIndex ], textures ); + + // Since PMX spec don't have standard to list map files except color map and env map, + // we need to save file name for further mapping, like matching normal map file names after model loaded. + // ref: https://gist.github.com/felixjones/f8a06bd48f9da9a4539f#texture + params.userData.MMD.mapFileName = data.textures[ material.textureIndex ]; + + } + + // envMap TODO: support m.envFlag === 3 + + if ( material.envTextureIndex !== - 1 && ( material.envFlag === 1 || material.envFlag == 2 ) ) { + + params.matcap = this._loadTexture( + data.textures[ material.envTextureIndex ], + textures + ); + + // Same as color map above, keep file name in userData for further usage. + params.userData.MMD.matcapFileName = data.textures[ material.envTextureIndex ]; + + params.matcapCombine = material.envFlag === 1 + ? MultiplyOperation + : AddOperation; + + } + + // gradientMap + + let toonFileName, isDefaultToon; + + if ( material.toonIndex === - 1 || material.toonFlag !== 0 ) { + + toonFileName = 'toon' + ( '0' + ( material.toonIndex + 1 ) ).slice( - 2 ) + '.bmp'; + isDefaultToon = true; + + } else { + + toonFileName = data.textures[ material.toonIndex ]; + isDefaultToon = false; + + } + + params.gradientMap = this._loadTexture( + toonFileName, + textures, + { + isToonTexture: true, + isDefaultToonTexture: isDefaultToon + } + ); + + // parameters for OutlineEffect + params.userData.outlineParameters = { + thickness: material.edgeSize / 300, // TODO: better calculation? + color: material.edgeColor.slice( 0, 3 ), + alpha: material.edgeColor[ 3 ], + visible: ( material.flag & 0x10 ) !== 0 && material.edgeSize > 0.0 + }; + + } + + if ( params.map !== undefined ) { + + if ( ! params.transparent ) { + + this._checkImageTransparency( params.map, geometry, i ); + + } + + params.emissive.multiplyScalar( 0.2 ); + + } + + materials.push( new MMDToonMaterial( params ) ); + + } + + if ( data.metadata.format === 'pmx' ) { + + // set transparent true if alpha morph is defined. + + function checkAlphaMorph( elements, materials ) { + + for ( let i = 0, il = elements.length; i < il; i ++ ) { + + const element = elements[ i ]; + + if ( element.index === - 1 ) continue; + + const material = materials[ element.index ]; + + if ( material.opacity !== element.diffuse[ 3 ] ) { + + material.transparent = true; + + } + + } + + } + + for ( let i = 0, il = data.morphs.length; i < il; i ++ ) { + + const morph = data.morphs[ i ]; + const elements = morph.elements; + + if ( morph.type === 0 ) { + + for ( let j = 0, jl = elements.length; j < jl; j ++ ) { + + const morph2 = data.morphs[ elements[ j ].index ]; + + if ( morph2.type !== 8 ) continue; + + checkAlphaMorph( morph2.elements, materials ); + + } + + } else if ( morph.type === 8 ) { + + checkAlphaMorph( elements, materials ); + + } + + } + + } + + return materials; + + } + + // private methods + + _getTGALoader() { + + if ( this.tgaLoader === null ) { + + if ( TGALoader === undefined ) { + + throw new Error( 'THREE.MMDLoader: Import TGALoader' ); + + } + + this.tgaLoader = new TGALoader( this.manager ); + + } + + return this.tgaLoader; + + } + + _isDefaultToonTexture( name ) { + + if ( name.length !== 10 ) return false; + + return /toon(10|0[0-9])\.bmp/.test( name ); + + } + + _loadTexture( filePath, textures, params, onProgress, onError ) { + + params = params || {}; + + const scope = this; + + let fullPath; + + if ( params.isDefaultToonTexture === true ) { + + let index; + + try { + + index = parseInt( filePath.match( /toon([0-9]{2})\.bmp$/ )[ 1 ] ); + + } catch ( e ) { + + console.warn( 'THREE.MMDLoader: ' + filePath + ' seems like a ' + + 'not right default texture path. Using toon00.bmp instead.' ); + + index = 0; + + } + + fullPath = DEFAULT_TOON_TEXTURES[ index ]; + + } else { + + fullPath = this.resourcePath + filePath; + + } + + if ( textures[ fullPath ] !== undefined ) return textures[ fullPath ]; + + let loader = this.manager.getHandler( fullPath ); + + if ( loader === null ) { + + loader = ( filePath.slice( - 4 ).toLowerCase() === '.tga' ) + ? this._getTGALoader() + : this.textureLoader; + + } + + const texture = loader.load( fullPath, function ( t ) { + + // MMD toon texture is Axis-Y oriented + // but Three.js gradient map is Axis-X oriented. + // So here replaces the toon texture image with the rotated one. + if ( params.isToonTexture === true ) { + + t.image = scope._getRotatedImage( t.image ); + + t.magFilter = NearestFilter; + t.minFilter = NearestFilter; + + } + + t.flipY = false; + t.wrapS = RepeatWrapping; + t.wrapT = RepeatWrapping; + + for ( let i = 0; i < texture.readyCallbacks.length; i ++ ) { + + texture.readyCallbacks[ i ]( texture ); + + } + + delete texture.readyCallbacks; + + }, onProgress, onError ); + + texture.readyCallbacks = []; + + textures[ fullPath ] = texture; + + return texture; + + } + + _getRotatedImage( image ) { + + const canvas = document.createElement( 'canvas' ); + const context = canvas.getContext( '2d' ); + + const width = image.width; + const height = image.height; + + canvas.width = width; + canvas.height = height; + + context.clearRect( 0, 0, width, height ); + context.translate( width / 2.0, height / 2.0 ); + context.rotate( 0.5 * Math.PI ); // 90.0 * Math.PI / 180.0 + context.translate( - width / 2.0, - height / 2.0 ); + context.drawImage( image, 0, 0 ); + + return context.getImageData( 0, 0, width, height ); + + } + + // Check if the partial image area used by the texture is transparent. + _checkImageTransparency( map, geometry, groupIndex ) { + + map.readyCallbacks.push( function ( texture ) { + + // Is there any efficient ways? + function createImageData( image ) { + + const canvas = document.createElement( 'canvas' ); + canvas.width = image.width; + canvas.height = image.height; + + const context = canvas.getContext( '2d' ); + context.drawImage( image, 0, 0 ); + + return context.getImageData( 0, 0, canvas.width, canvas.height ); + + } + + function detectImageTransparency( image, uvs, indices ) { + + const width = image.width; + const height = image.height; + const data = image.data; + const threshold = 253; + + if ( data.length / ( width * height ) !== 4 ) return false; + + for ( let i = 0; i < indices.length; i += 3 ) { + + const centerUV = { x: 0.0, y: 0.0 }; + + for ( let j = 0; j < 3; j ++ ) { + + const index = indices[ i * 3 + j ]; + const uv = { x: uvs[ index * 2 + 0 ], y: uvs[ index * 2 + 1 ] }; + + if ( getAlphaByUv( image, uv ) < threshold ) return true; + + centerUV.x += uv.x; + centerUV.y += uv.y; + + } + + centerUV.x /= 3; + centerUV.y /= 3; + + if ( getAlphaByUv( image, centerUV ) < threshold ) return true; + + } + + return false; + + } + + /* + * This method expects + * texture.flipY = false + * texture.wrapS = RepeatWrapping + * texture.wrapT = RepeatWrapping + * TODO: more precise + */ + function getAlphaByUv( image, uv ) { + + const width = image.width; + const height = image.height; + + let x = Math.round( uv.x * width ) % width; + let y = Math.round( uv.y * height ) % height; + + if ( x < 0 ) x += width; + if ( y < 0 ) y += height; + + const index = y * width + x; + + return image.data[ index * 4 + 3 ]; + + } + + if ( texture.isCompressedTexture === true ) { + + if ( NON_ALPHA_CHANNEL_FORMATS.includes( texture.format ) ) { + + map.transparent = false; + + } else { + + // any other way to check transparency of CompressedTexture? + map.transparent = true; + + } + + return; + + } + + const imageData = texture.image.data !== undefined + ? texture.image + : createImageData( texture.image ); + + const group = geometry.groups[ groupIndex ]; + + if ( detectImageTransparency( + imageData, + geometry.attributes.uv.array, + geometry.index.array.slice( group.start, group.start + group.count ) ) ) { + + map.transparent = true; + + } + + } ); + + } + +} + +// + +class AnimationBuilder { + + /** + * @param {Object} vmd - parsed VMD data + * @param {SkinnedMesh} mesh - tracks will be fitting to mesh + * @return {AnimationClip} + */ + build( vmd, mesh ) { + + // combine skeletal and morph animations + + const tracks = this.buildSkeletalAnimation( vmd, mesh ).tracks; + const tracks2 = this.buildMorphAnimation( vmd, mesh ).tracks; + + for ( let i = 0, il = tracks2.length; i < il; i ++ ) { + + tracks.push( tracks2[ i ] ); + + } + + return new AnimationClip( '', - 1, tracks ); + + } + + /** + * @param {Object} vmd - parsed VMD data + * @param {SkinnedMesh} mesh - tracks will be fitting to mesh + * @return {AnimationClip} + */ + buildSkeletalAnimation( vmd, mesh ) { + + function pushInterpolation( array, interpolation, index ) { + + array.push( interpolation[ index + 0 ] / 127 ); // x1 + array.push( interpolation[ index + 8 ] / 127 ); // x2 + array.push( interpolation[ index + 4 ] / 127 ); // y1 + array.push( interpolation[ index + 12 ] / 127 ); // y2 + + } + + const tracks = []; + + const motions = {}; + const bones = mesh.skeleton.bones; + const boneNameDictionary = {}; + + for ( let i = 0, il = bones.length; i < il; i ++ ) { + + boneNameDictionary[ bones[ i ].name ] = true; + + } + + for ( let i = 0; i < vmd.metadata.motionCount; i ++ ) { + + const motion = vmd.motions[ i ]; + const boneName = motion.boneName; + + if ( boneNameDictionary[ boneName ] === undefined ) continue; + + motions[ boneName ] = motions[ boneName ] || []; + motions[ boneName ].push( motion ); + + } + + for ( const key in motions ) { + + const array = motions[ key ]; + + array.sort( function ( a, b ) { + + return a.frameNum - b.frameNum; + + } ); + + const times = []; + const positions = []; + const rotations = []; + const pInterpolations = []; + const rInterpolations = []; + + const basePosition = mesh.skeleton.getBoneByName( key ).position.toArray(); + + for ( let i = 0, il = array.length; i < il; i ++ ) { + + const time = array[ i ].frameNum / 30; + const position = array[ i ].position; + const rotation = array[ i ].rotation; + const interpolation = array[ i ].interpolation; + + times.push( time ); + + for ( let j = 0; j < 3; j ++ ) positions.push( basePosition[ j ] + position[ j ] ); + for ( let j = 0; j < 4; j ++ ) rotations.push( rotation[ j ] ); + for ( let j = 0; j < 3; j ++ ) pushInterpolation( pInterpolations, interpolation, j ); + + pushInterpolation( rInterpolations, interpolation, 3 ); + + } + + const targetName = '.bones[' + key + ']'; + + tracks.push( this._createTrack( targetName + '.position', VectorKeyframeTrack, times, positions, pInterpolations ) ); + tracks.push( this._createTrack( targetName + '.quaternion', QuaternionKeyframeTrack, times, rotations, rInterpolations ) ); + + } + + return new AnimationClip( '', - 1, tracks ); + + } + + /** + * @param {Object} vmd - parsed VMD data + * @param {SkinnedMesh} mesh - tracks will be fitting to mesh + * @return {AnimationClip} + */ + buildMorphAnimation( vmd, mesh ) { + + const tracks = []; + + const morphs = {}; + const morphTargetDictionary = mesh.morphTargetDictionary; + + for ( let i = 0; i < vmd.metadata.morphCount; i ++ ) { + + const morph = vmd.morphs[ i ]; + const morphName = morph.morphName; + + if ( morphTargetDictionary[ morphName ] === undefined ) continue; + + morphs[ morphName ] = morphs[ morphName ] || []; + morphs[ morphName ].push( morph ); + + } + + for ( const key in morphs ) { + + const array = morphs[ key ]; + + array.sort( function ( a, b ) { + + return a.frameNum - b.frameNum; + + } ); + + const times = []; + const values = []; + + for ( let i = 0, il = array.length; i < il; i ++ ) { + + times.push( array[ i ].frameNum / 30 ); + values.push( array[ i ].weight ); + + } + + tracks.push( new NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetDictionary[ key ] + ']', times, values ) ); + + } + + return new AnimationClip( '', - 1, tracks ); + + } + + /** + * @param {Object} vmd - parsed VMD data + * @return {AnimationClip} + */ + buildCameraAnimation( vmd ) { + + function pushVector3( array, vec ) { + + array.push( vec.x ); + array.push( vec.y ); + array.push( vec.z ); + + } + + function pushQuaternion( array, q ) { + + array.push( q.x ); + array.push( q.y ); + array.push( q.z ); + array.push( q.w ); + + } + + function pushInterpolation( array, interpolation, index ) { + + array.push( interpolation[ index * 4 + 0 ] / 127 ); // x1 + array.push( interpolation[ index * 4 + 1 ] / 127 ); // x2 + array.push( interpolation[ index * 4 + 2 ] / 127 ); // y1 + array.push( interpolation[ index * 4 + 3 ] / 127 ); // y2 + + } + + const cameras = vmd.cameras === undefined ? [] : vmd.cameras.slice(); + + cameras.sort( function ( a, b ) { + + return a.frameNum - b.frameNum; + + } ); + + const times = []; + const centers = []; + const quaternions = []; + const positions = []; + const fovs = []; + + const cInterpolations = []; + const qInterpolations = []; + const pInterpolations = []; + const fInterpolations = []; + + const quaternion = new Quaternion(); + const euler = new Euler(); + const position = new Vector3(); + const center = new Vector3(); + + for ( let i = 0, il = cameras.length; i < il; i ++ ) { + + const motion = cameras[ i ]; + + const time = motion.frameNum / 30; + const pos = motion.position; + const rot = motion.rotation; + const distance = motion.distance; + const fov = motion.fov; + const interpolation = motion.interpolation; + + times.push( time ); + + position.set( 0, 0, - distance ); + center.set( pos[ 0 ], pos[ 1 ], pos[ 2 ] ); + + euler.set( - rot[ 0 ], - rot[ 1 ], - rot[ 2 ] ); + quaternion.setFromEuler( euler ); + + position.add( center ); + position.applyQuaternion( quaternion ); + + pushVector3( centers, center ); + pushQuaternion( quaternions, quaternion ); + pushVector3( positions, position ); + + fovs.push( fov ); + + for ( let j = 0; j < 3; j ++ ) { + + pushInterpolation( cInterpolations, interpolation, j ); + + } + + pushInterpolation( qInterpolations, interpolation, 3 ); + + // use the same parameter for x, y, z axis. + for ( let j = 0; j < 3; j ++ ) { + + pushInterpolation( pInterpolations, interpolation, 4 ); + + } + + pushInterpolation( fInterpolations, interpolation, 5 ); + + } + + const tracks = []; + + // I expect an object whose name 'target' exists under THREE.Camera + tracks.push( this._createTrack( 'target.position', VectorKeyframeTrack, times, centers, cInterpolations ) ); + + tracks.push( this._createTrack( '.quaternion', QuaternionKeyframeTrack, times, quaternions, qInterpolations ) ); + tracks.push( this._createTrack( '.position', VectorKeyframeTrack, times, positions, pInterpolations ) ); + tracks.push( this._createTrack( '.fov', NumberKeyframeTrack, times, fovs, fInterpolations ) ); + + return new AnimationClip( '', - 1, tracks ); + + } + + // private method + + _createTrack( node, typedKeyframeTrack, times, values, interpolations ) { + + /* + * optimizes here not to let KeyframeTrackPrototype optimize + * because KeyframeTrackPrototype optimizes times and values but + * doesn't optimize interpolations. + */ + if ( times.length > 2 ) { + + times = times.slice(); + values = values.slice(); + interpolations = interpolations.slice(); + + const stride = values.length / times.length; + const interpolateStride = interpolations.length / times.length; + + let index = 1; + + for ( let aheadIndex = 2, endIndex = times.length; aheadIndex < endIndex; aheadIndex ++ ) { + + for ( let i = 0; i < stride; i ++ ) { + + if ( values[ index * stride + i ] !== values[ ( index - 1 ) * stride + i ] || + values[ index * stride + i ] !== values[ aheadIndex * stride + i ] ) { + + index ++; + break; + + } + + } + + if ( aheadIndex > index ) { + + times[ index ] = times[ aheadIndex ]; + + for ( let i = 0; i < stride; i ++ ) { + + values[ index * stride + i ] = values[ aheadIndex * stride + i ]; + + } + + for ( let i = 0; i < interpolateStride; i ++ ) { + + interpolations[ index * interpolateStride + i ] = interpolations[ aheadIndex * interpolateStride + i ]; + + } + + } + + } + + times.length = index + 1; + values.length = ( index + 1 ) * stride; + interpolations.length = ( index + 1 ) * interpolateStride; + + } + + const track = new typedKeyframeTrack( node, times, values ); + + track.createInterpolant = function InterpolantFactoryMethodCubicBezier( result ) { + + return new CubicBezierInterpolation( this.times, this.values, this.getValueSize(), result, new Float32Array( interpolations ) ); + + }; + + return track; + + } + +} + +// interpolation + +class CubicBezierInterpolation extends Interpolant { + + constructor( parameterPositions, sampleValues, sampleSize, resultBuffer, params ) { + + super( parameterPositions, sampleValues, sampleSize, resultBuffer ); + + this.interpolationParams = params; + + } + + interpolate_( i1, t0, t, t1 ) { + + const result = this.resultBuffer; + const values = this.sampleValues; + const stride = this.valueSize; + const params = this.interpolationParams; + + const offset1 = i1 * stride; + const offset0 = offset1 - stride; + + // No interpolation if next key frame is in one frame in 30fps. + // This is from MMD animation spec. + // '1.5' is for precision loss. times are Float32 in Three.js Animation system. + const weight1 = ( ( t1 - t0 ) < 1 / 30 * 1.5 ) ? 0.0 : ( t - t0 ) / ( t1 - t0 ); + + if ( stride === 4 ) { // Quaternion + + const x1 = params[ i1 * 4 + 0 ]; + const x2 = params[ i1 * 4 + 1 ]; + const y1 = params[ i1 * 4 + 2 ]; + const y2 = params[ i1 * 4 + 3 ]; + + const ratio = this._calculate( x1, x2, y1, y2, weight1 ); + + Quaternion.slerpFlat( result, 0, values, offset0, values, offset1, ratio ); + + } else if ( stride === 3 ) { // Vector3 + + for ( let i = 0; i !== stride; ++ i ) { + + const x1 = params[ i1 * 12 + i * 4 + 0 ]; + const x2 = params[ i1 * 12 + i * 4 + 1 ]; + const y1 = params[ i1 * 12 + i * 4 + 2 ]; + const y2 = params[ i1 * 12 + i * 4 + 3 ]; + + const ratio = this._calculate( x1, x2, y1, y2, weight1 ); + + result[ i ] = values[ offset0 + i ] * ( 1 - ratio ) + values[ offset1 + i ] * ratio; + + } + + } else { // Number + + const x1 = params[ i1 * 4 + 0 ]; + const x2 = params[ i1 * 4 + 1 ]; + const y1 = params[ i1 * 4 + 2 ]; + const y2 = params[ i1 * 4 + 3 ]; + + const ratio = this._calculate( x1, x2, y1, y2, weight1 ); + + result[ 0 ] = values[ offset0 ] * ( 1 - ratio ) + values[ offset1 ] * ratio; + + } + + return result; + + } + + _calculate( x1, x2, y1, y2, x ) { + + /* + * Cubic Bezier curves + * https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Cubic_B.C3.A9zier_curves + * + * B(t) = ( 1 - t ) ^ 3 * P0 + * + 3 * ( 1 - t ) ^ 2 * t * P1 + * + 3 * ( 1 - t ) * t^2 * P2 + * + t ^ 3 * P3 + * ( 0 <= t <= 1 ) + * + * MMD uses Cubic Bezier curves for bone and camera animation interpolation. + * http://d.hatena.ne.jp/edvakf/20111016/1318716097 + * + * x = ( 1 - t ) ^ 3 * x0 + * + 3 * ( 1 - t ) ^ 2 * t * x1 + * + 3 * ( 1 - t ) * t^2 * x2 + * + t ^ 3 * x3 + * y = ( 1 - t ) ^ 3 * y0 + * + 3 * ( 1 - t ) ^ 2 * t * y1 + * + 3 * ( 1 - t ) * t^2 * y2 + * + t ^ 3 * y3 + * ( x0 = 0, y0 = 0 ) + * ( x3 = 1, y3 = 1 ) + * ( 0 <= t, x1, x2, y1, y2 <= 1 ) + * + * Here solves this equation with Bisection method, + * https://en.wikipedia.org/wiki/Bisection_method + * gets t, and then calculate y. + * + * f(t) = 3 * ( 1 - t ) ^ 2 * t * x1 + * + 3 * ( 1 - t ) * t^2 * x2 + * + t ^ 3 - x = 0 + * + * (Another option: Newton's method + * https://en.wikipedia.org/wiki/Newton%27s_method) + */ + + let c = 0.5; + let t = c; + let s = 1.0 - t; + const loop = 15; + const eps = 1e-5; + const math = Math; + + let sst3, stt3, ttt; + + for ( let i = 0; i < loop; i ++ ) { + + sst3 = 3.0 * s * s * t; + stt3 = 3.0 * s * t * t; + ttt = t * t * t; + + const ft = ( sst3 * x1 ) + ( stt3 * x2 ) + ( ttt ) - x; + + if ( math.abs( ft ) < eps ) break; + + c /= 2.0; + + t += ( ft < 0 ) ? c : - c; + s = 1.0 - t; + + } + + return ( sst3 * y1 ) + ( stt3 * y2 ) + ttt; + + } + +} + +class MMDToonMaterial extends ShaderMaterial { + + constructor( parameters ) { + + super(); + + this._matcapCombine = AddOperation; + this.emissiveIntensity = 1.0; + this.normalMapType = TangentSpaceNormalMap; + + this.combine = MultiplyOperation; + + this.wireframeLinecap = 'round'; + this.wireframeLinejoin = 'round'; + + this.flatShading = false; + + this.lights = true; + + this.vertexShader = MMDToonShader.vertexShader; + this.fragmentShader = MMDToonShader.fragmentShader; + + this.defines = Object.assign( {}, MMDToonShader.defines ); + Object.defineProperty( this, 'matcapCombine', { + + get: function () { + + return this._matcapCombine; + + }, + + set: function ( value ) { + + this._matcapCombine = value; + + switch ( value ) { + + case MultiplyOperation: + this.defines.MATCAP_BLENDING_MULTIPLY = true; + delete this.defines.MATCAP_BLENDING_ADD; + break; + + default: + case AddOperation: + this.defines.MATCAP_BLENDING_ADD = true; + delete this.defines.MATCAP_BLENDING_MULTIPLY; + break; + + } + + }, + + } ); + + this.uniforms = UniformsUtils.clone( MMDToonShader.uniforms ); + + // merged from MeshToon/Phong/MatcapMaterial + const exposePropertyNames = [ + 'specular', + 'shininess', + 'opacity', + 'diffuse', + + 'map', + 'matcap', + 'gradientMap', + + 'lightMap', + 'lightMapIntensity', + + 'aoMap', + 'aoMapIntensity', + + 'emissive', + 'emissiveMap', + + 'bumpMap', + 'bumpScale', + + 'normalMap', + 'normalScale', + + 'displacemantBias', + 'displacemantMap', + 'displacemantScale', + + 'specularMap', + + 'alphaMap', + + 'envMap', + 'reflectivity', + 'refractionRatio', + ]; + for ( const propertyName of exposePropertyNames ) { + + Object.defineProperty( this, propertyName, { + + get: function () { + + return this.uniforms[ propertyName ].value; + + }, + + set: function ( value ) { + + this.uniforms[ propertyName ].value = value; + + }, + + } ); + + } + + Object.defineProperty( + this, + 'color', + Object.getOwnPropertyDescriptor( this, 'diffuse' ) + ); + + this.setValues( parameters ); + + } + + copy( source ) { + + super.copy( source ); + + this.matcapCombine = source.matcapCombine; + this.emissiveIntensity = source.emissiveIntensity; + this.normalMapType = source.normalMapType; + + this.combine = source.combine; + + this.wireframeLinecap = source.wireframeLinecap; + this.wireframeLinejoin = source.wireframeLinejoin; + + this.flatShading = source.flatShading; + + return this; + + } + +} + +MMDToonMaterial.prototype.isMMDToonMaterial = true; + +export { MMDLoader }; diff --git a/jsm/loaders/MTLLoader.js b/jsm/loaders/MTLLoader.js new file mode 100644 index 0000000..7289eb9 --- /dev/null +++ b/jsm/loaders/MTLLoader.js @@ -0,0 +1,567 @@ +import { + Color, + DefaultLoadingManager, + FileLoader, + FrontSide, + Loader, + LoaderUtils, + MeshPhongMaterial, + RepeatWrapping, + TextureLoader, + Vector2, + sRGBEncoding +} from 'three'; + +/** + * Loads a Wavefront .mtl file specifying materials + */ + +class MTLLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + /** + * Loads and parses a MTL asset from a URL. + * + * @param {String} url - URL to the MTL file. + * @param {Function} [onLoad] - Callback invoked with the loaded object. + * @param {Function} [onProgress] - Callback for download progress. + * @param {Function} [onError] - Callback for download errors. + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to load. + */ + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const path = ( this.path === '' ) ? LoaderUtils.extractUrlBase( url ) : this.path; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text, path ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + setMaterialOptions( value ) { + + this.materialOptions = value; + return this; + + } + + /** + * Parses a MTL file. + * + * @param {String} text - Content of MTL file + * @return {MaterialCreator} + * + * @see setPath setResourcePath + * + * @note In order for relative texture references to resolve correctly + * you must call setResourcePath() explicitly prior to parse. + */ + parse( text, path ) { + + const lines = text.split( '\n' ); + let info = {}; + const delimiter_pattern = /\s+/; + const materialsInfo = {}; + + for ( let i = 0; i < lines.length; i ++ ) { + + let line = lines[ i ]; + line = line.trim(); + + if ( line.length === 0 || line.charAt( 0 ) === '#' ) { + + // Blank line or comment ignore + continue; + + } + + const pos = line.indexOf( ' ' ); + + let key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; + key = key.toLowerCase(); + + let value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ''; + value = value.trim(); + + if ( key === 'newmtl' ) { + + // New material + + info = { name: value }; + materialsInfo[ value ] = info; + + } else { + + if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) { + + const ss = value.split( delimiter_pattern, 3 ); + info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; + + } else { + + info[ key ] = value; + + } + + } + + } + + const materialCreator = new MaterialCreator( this.resourcePath || path, this.materialOptions ); + materialCreator.setCrossOrigin( this.crossOrigin ); + materialCreator.setManager( this.manager ); + materialCreator.setMaterials( materialsInfo ); + return materialCreator; + + } + +} + +/** + * Create a new MTLLoader.MaterialCreator + * @param baseUrl - Url relative to which textures are loaded + * @param options - Set of options on how to construct the materials + * side: Which side to apply the material + * FrontSide (default), THREE.BackSide, THREE.DoubleSide + * wrap: What type of wrapping to apply for textures + * RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping + * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 + * Default: false, assumed to be already normalized + * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's + * Default: false + * @constructor + */ + +class MaterialCreator { + + constructor( baseUrl = '', options = {} ) { + + this.baseUrl = baseUrl; + this.options = options; + this.materialsInfo = {}; + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + this.crossOrigin = 'anonymous'; + + this.side = ( this.options.side !== undefined ) ? this.options.side : FrontSide; + this.wrap = ( this.options.wrap !== undefined ) ? this.options.wrap : RepeatWrapping; + + } + + setCrossOrigin( value ) { + + this.crossOrigin = value; + return this; + + } + + setManager( value ) { + + this.manager = value; + + } + + setMaterials( materialsInfo ) { + + this.materialsInfo = this.convert( materialsInfo ); + this.materials = {}; + this.materialsArray = []; + this.nameLookup = {}; + + } + + convert( materialsInfo ) { + + if ( ! this.options ) return materialsInfo; + + const converted = {}; + + for ( const mn in materialsInfo ) { + + // Convert materials info into normalized form based on options + + const mat = materialsInfo[ mn ]; + + const covmat = {}; + + converted[ mn ] = covmat; + + for ( const prop in mat ) { + + let save = true; + let value = mat[ prop ]; + const lprop = prop.toLowerCase(); + + switch ( lprop ) { + + case 'kd': + case 'ka': + case 'ks': + + // Diffuse color (color under white light) using RGB values + + if ( this.options && this.options.normalizeRGB ) { + + value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; + + } + + if ( this.options && this.options.ignoreZeroRGBs ) { + + if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) { + + // ignore + + save = false; + + } + + } + + break; + + default: + + break; + + } + + if ( save ) { + + covmat[ lprop ] = value; + + } + + } + + } + + return converted; + + } + + preload() { + + for ( const mn in this.materialsInfo ) { + + this.create( mn ); + + } + + } + + getIndex( materialName ) { + + return this.nameLookup[ materialName ]; + + } + + getAsArray() { + + let index = 0; + + for ( const mn in this.materialsInfo ) { + + this.materialsArray[ index ] = this.create( mn ); + this.nameLookup[ mn ] = index; + index ++; + + } + + return this.materialsArray; + + } + + create( materialName ) { + + if ( this.materials[ materialName ] === undefined ) { + + this.createMaterial_( materialName ); + + } + + return this.materials[ materialName ]; + + } + + createMaterial_( materialName ) { + + // Create material + + const scope = this; + const mat = this.materialsInfo[ materialName ]; + const params = { + + name: materialName, + side: this.side + + }; + + function resolveURL( baseUrl, url ) { + + if ( typeof url !== 'string' || url === '' ) + return ''; + + // Absolute URL + if ( /^https?:\/\//i.test( url ) ) return url; + + return baseUrl + url; + + } + + function setMapForType( mapType, value ) { + + if ( params[ mapType ] ) return; // Keep the first encountered texture + + const texParams = scope.getTextureParams( value, params ); + const map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) ); + + map.repeat.copy( texParams.scale ); + map.offset.copy( texParams.offset ); + + map.wrapS = scope.wrap; + map.wrapT = scope.wrap; + + if ( mapType === 'map' || mapType === 'emissiveMap' ) { + + map.encoding = sRGBEncoding; + + } + + params[ mapType ] = map; + + } + + for ( const prop in mat ) { + + const value = mat[ prop ]; + let n; + + if ( value === '' ) continue; + + switch ( prop.toLowerCase() ) { + + // Ns is material specular exponent + + case 'kd': + + // Diffuse color (color under white light) using RGB values + + params.color = new Color().fromArray( value ).convertSRGBToLinear(); + + break; + + case 'ks': + + // Specular color (color when light is reflected from shiny surface) using RGB values + params.specular = new Color().fromArray( value ).convertSRGBToLinear(); + + break; + + case 'ke': + + // Emissive using RGB values + params.emissive = new Color().fromArray( value ).convertSRGBToLinear(); + + break; + + case 'map_kd': + + // Diffuse texture map + + setMapForType( 'map', value ); + + break; + + case 'map_ks': + + // Specular map + + setMapForType( 'specularMap', value ); + + break; + + case 'map_ke': + + // Emissive map + + setMapForType( 'emissiveMap', value ); + + break; + + case 'norm': + + setMapForType( 'normalMap', value ); + + break; + + case 'map_bump': + case 'bump': + + // Bump texture map + + setMapForType( 'bumpMap', value ); + + break; + + case 'map_d': + + // Alpha map + + setMapForType( 'alphaMap', value ); + params.transparent = true; + + break; + + case 'ns': + + // The specular exponent (defines the focus of the specular highlight) + // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. + + params.shininess = parseFloat( value ); + + break; + + case 'd': + n = parseFloat( value ); + + if ( n < 1 ) { + + params.opacity = n; + params.transparent = true; + + } + + break; + + case 'tr': + n = parseFloat( value ); + + if ( this.options && this.options.invertTrProperty ) n = 1 - n; + + if ( n > 0 ) { + + params.opacity = 1 - n; + params.transparent = true; + + } + + break; + + default: + break; + + } + + } + + this.materials[ materialName ] = new MeshPhongMaterial( params ); + return this.materials[ materialName ]; + + } + + getTextureParams( value, matParams ) { + + const texParams = { + + scale: new Vector2( 1, 1 ), + offset: new Vector2( 0, 0 ) + + }; + + const items = value.split( /\s+/ ); + let pos; + + pos = items.indexOf( '-bm' ); + + if ( pos >= 0 ) { + + matParams.bumpScale = parseFloat( items[ pos + 1 ] ); + items.splice( pos, 2 ); + + } + + pos = items.indexOf( '-s' ); + + if ( pos >= 0 ) { + + texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + pos = items.indexOf( '-o' ); + + if ( pos >= 0 ) { + + texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) ); + items.splice( pos, 4 ); // we expect 3 parameters here! + + } + + texParams.url = items.join( ' ' ).trim(); + return texParams; + + } + + loadTexture( url, mapping, onLoad, onProgress, onError ) { + + const manager = ( this.manager !== undefined ) ? this.manager : DefaultLoadingManager; + let loader = manager.getHandler( url ); + + if ( loader === null ) { + + loader = new TextureLoader( manager ); + + } + + if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); + + const texture = loader.load( url, onLoad, onProgress, onError ); + + if ( mapping !== undefined ) texture.mapping = mapping; + + return texture; + + } + +} + +export { MTLLoader }; diff --git a/jsm/loaders/NRRDLoader.js b/jsm/loaders/NRRDLoader.js new file mode 100644 index 0000000..c595c85 --- /dev/null +++ b/jsm/loaders/NRRDLoader.js @@ -0,0 +1,672 @@ +import { + FileLoader, + Loader, + Matrix4, + Vector3 +} from 'three'; +import * as fflate from '../libs/fflate.module.js'; +import { Volume } from '../misc/Volume.js'; + +class NRRDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( data ) { + + try { + + onLoad( scope.parse( data ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + // this parser is largely inspired from the XTK NRRD parser : https://github.com/xtk/X + + let _data = data; + + let _dataPointer = 0; + + const _nativeLittleEndian = new Int8Array( new Int16Array( [ 1 ] ).buffer )[ 0 ] > 0; + + const _littleEndian = true; + + const headerObject = {}; + + function scan( type, chunks ) { + + if ( chunks === undefined || chunks === null ) { + + chunks = 1; + + } + + let _chunkSize = 1; + let _array_type = Uint8Array; + + switch ( type ) { + + // 1 byte data types + case 'uchar': + break; + case 'schar': + _array_type = Int8Array; + break; + // 2 byte data types + case 'ushort': + _array_type = Uint16Array; + _chunkSize = 2; + break; + case 'sshort': + _array_type = Int16Array; + _chunkSize = 2; + break; + // 4 byte data types + case 'uint': + _array_type = Uint32Array; + _chunkSize = 4; + break; + case 'sint': + _array_type = Int32Array; + _chunkSize = 4; + break; + case 'float': + _array_type = Float32Array; + _chunkSize = 4; + break; + case 'complex': + _array_type = Float64Array; + _chunkSize = 8; + break; + case 'double': + _array_type = Float64Array; + _chunkSize = 8; + break; + + } + + // increase the data pointer in-place + let _bytes = new _array_type( _data.slice( _dataPointer, + _dataPointer += chunks * _chunkSize ) ); + + // if required, flip the endianness of the bytes + if ( _nativeLittleEndian != _littleEndian ) { + + // we need to flip here since the format doesn't match the native endianness + _bytes = flipEndianness( _bytes, _chunkSize ); + + } + + if ( chunks == 1 ) { + + // if only one chunk was requested, just return one value + return _bytes[ 0 ]; + + } + + // return the byte array + return _bytes; + + } + + //Flips typed array endianness in-place. Based on https://github.com/kig/DataStream.js/blob/master/DataStream.js. + + function flipEndianness( array, chunkSize ) { + + const u8 = new Uint8Array( array.buffer, array.byteOffset, array.byteLength ); + for ( let i = 0; i < array.byteLength; i += chunkSize ) { + + for ( let j = i + chunkSize - 1, k = i; j > k; j --, k ++ ) { + + const tmp = u8[ k ]; + u8[ k ] = u8[ j ]; + u8[ j ] = tmp; + + } + + } + + return array; + + } + + //parse the header + function parseHeader( header ) { + + let data, field, fn, i, l, m, _i, _len; + const lines = header.split( /\r?\n/ ); + for ( _i = 0, _len = lines.length; _i < _len; _i ++ ) { + + l = lines[ _i ]; + if ( l.match( /NRRD\d+/ ) ) { + + headerObject.isNrrd = true; + + } else if ( l.match( /^#/ ) ) { + } else if ( m = l.match( /(.*):(.*)/ ) ) { + + field = m[ 1 ].trim(); + data = m[ 2 ].trim(); + fn = _fieldFunctions[ field ]; + if ( fn ) { + + fn.call( headerObject, data ); + + } else { + + headerObject[ field ] = data; + + } + + } + + } + + if ( ! headerObject.isNrrd ) { + + throw new Error( 'Not an NRRD file' ); + + } + + if ( headerObject.encoding === 'bz2' || headerObject.encoding === 'bzip2' ) { + + throw new Error( 'Bzip is not supported' ); + + } + + if ( ! headerObject.vectors ) { + + //if no space direction is set, let's use the identity + headerObject.vectors = [ ]; + headerObject.vectors.push( [ 1, 0, 0 ] ); + headerObject.vectors.push( [ 0, 1, 0 ] ); + headerObject.vectors.push( [ 0, 0, 1 ] ); + + //apply spacing if defined + if ( headerObject.spacings ) { + + for ( i = 0; i <= 2; i ++ ) { + + if ( ! isNaN( headerObject.spacings[ i ] ) ) { + + for ( let j = 0; j <= 2; j ++ ) { + + headerObject.vectors[ i ][ j ] *= headerObject.spacings[ i ]; + + } + + } + + } + + } + + } + + } + + //parse the data when registred as one of this type : 'text', 'ascii', 'txt' + function parseDataAsText( data, start, end ) { + + let number = ''; + start = start || 0; + end = end || data.length; + let value; + //length of the result is the product of the sizes + const lengthOfTheResult = headerObject.sizes.reduce( function ( previous, current ) { + + return previous * current; + + }, 1 ); + + let base = 10; + if ( headerObject.encoding === 'hex' ) { + + base = 16; + + } + + const result = new headerObject.__array( lengthOfTheResult ); + let resultIndex = 0; + let parsingFunction = parseInt; + if ( headerObject.__array === Float32Array || headerObject.__array === Float64Array ) { + + parsingFunction = parseFloat; + + } + + for ( let i = start; i < end; i ++ ) { + + value = data[ i ]; + //if value is not a space + if ( ( value < 9 || value > 13 ) && value !== 32 ) { + + number += String.fromCharCode( value ); + + } else { + + if ( number !== '' ) { + + result[ resultIndex ] = parsingFunction( number, base ); + resultIndex ++; + + } + + number = ''; + + } + + } + + if ( number !== '' ) { + + result[ resultIndex ] = parsingFunction( number, base ); + resultIndex ++; + + } + + return result; + + } + + const _bytes = scan( 'uchar', data.byteLength ); + const _length = _bytes.length; + let _header = null; + let _data_start = 0; + let i; + for ( i = 1; i < _length; i ++ ) { + + if ( _bytes[ i - 1 ] == 10 && _bytes[ i ] == 10 ) { + + // we found two line breaks in a row + // now we know what the header is + _header = this.parseChars( _bytes, 0, i - 2 ); + // this is were the data starts + _data_start = i + 1; + break; + + } + + } + + // parse the header + parseHeader( _header ); + + _data = _bytes.subarray( _data_start ); // the data without header + if ( headerObject.encoding.substring( 0, 2 ) === 'gz' ) { + + // we need to decompress the datastream + // here we start the unzipping and get a typed Uint8Array back + _data = fflate.gunzipSync( new Uint8Array( _data ) );// eslint-disable-line no-undef + + } else if ( headerObject.encoding === 'ascii' || headerObject.encoding === 'text' || headerObject.encoding === 'txt' || headerObject.encoding === 'hex' ) { + + _data = parseDataAsText( _data ); + + } else if ( headerObject.encoding === 'raw' ) { + + //we need to copy the array to create a new array buffer, else we retrieve the original arraybuffer with the header + const _copy = new Uint8Array( _data.length ); + + for ( let i = 0; i < _data.length; i ++ ) { + + _copy[ i ] = _data[ i ]; + + } + + _data = _copy; + + } + + // .. let's use the underlying array buffer + _data = _data.buffer; + + const volume = new Volume(); + volume.header = headerObject; + // + // parse the (unzipped) data to a datastream of the correct type + // + volume.data = new headerObject.__array( _data ); + // get the min and max intensities + const min_max = volume.computeMinMax(); + const min = min_max[ 0 ]; + const max = min_max[ 1 ]; + // attach the scalar range to the volume + volume.windowLow = min; + volume.windowHigh = max; + + // get the image dimensions + volume.dimensions = [ headerObject.sizes[ 0 ], headerObject.sizes[ 1 ], headerObject.sizes[ 2 ] ]; + volume.xLength = volume.dimensions[ 0 ]; + volume.yLength = volume.dimensions[ 1 ]; + volume.zLength = volume.dimensions[ 2 ]; + + // Identify axis order in the space-directions matrix from the header if possible. + if ( headerObject.vectors ) { + + const xIndex = headerObject.vectors.findIndex( vector => vector[ 0 ] !== 0 ); + const yIndex = headerObject.vectors.findIndex( vector => vector[ 1 ] !== 0 ); + const zIndex = headerObject.vectors.findIndex( vector => vector[ 2 ] !== 0 ); + + const axisOrder = []; + axisOrder[ xIndex ] = 'x'; + axisOrder[ yIndex ] = 'y'; + axisOrder[ zIndex ] = 'z'; + volume.axisOrder = axisOrder; + + } else { + + volume.axisOrder = [ 'x', 'y', 'z' ]; + + } + + // spacing + const spacingX = new Vector3().fromArray( headerObject.vectors[ 0 ] ).length(); + const spacingY = new Vector3().fromArray( headerObject.vectors[ 1 ] ).length(); + const spacingZ = new Vector3().fromArray( headerObject.vectors[ 2 ] ).length(); + volume.spacing = [ spacingX, spacingY, spacingZ ]; + + + // Create IJKtoRAS matrix + volume.matrix = new Matrix4(); + + const transitionMatrix = new Matrix4(); + + if ( headerObject.space === 'left-posterior-superior' ) { + + transitionMatrix.set( + - 1, 0, 0, 0, + 0, - 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + ); + + } else if ( headerObject.space === 'left-anterior-superior' ) { + + transitionMatrix.set( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, - 1, 0, + 0, 0, 0, 1 + ); + + } + + + if ( ! headerObject.vectors ) { + + volume.matrix.set( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 ); + + } else { + + const v = headerObject.vectors; + + const ijk_to_transition = new Matrix4().set( + v[ 0 ][ 0 ], v[ 1 ][ 0 ], v[ 2 ][ 0 ], 0, + v[ 0 ][ 1 ], v[ 1 ][ 1 ], v[ 2 ][ 1 ], 0, + v[ 0 ][ 2 ], v[ 1 ][ 2 ], v[ 2 ][ 2 ], 0, + 0, 0, 0, 1 + ); + + const transition_to_ras = new Matrix4().multiplyMatrices( ijk_to_transition, transitionMatrix ); + + volume.matrix = transition_to_ras; + + } + + volume.inverseMatrix = new Matrix4(); + volume.inverseMatrix.copy( volume.matrix ).invert(); + volume.RASDimensions = new Vector3( volume.xLength, volume.yLength, volume.zLength ).applyMatrix4( volume.matrix ).round().toArray().map( Math.abs ); + + // .. and set the default threshold + // only if the threshold was not already set + if ( volume.lowerThreshold === - Infinity ) { + + volume.lowerThreshold = min; + + } + + if ( volume.upperThreshold === Infinity ) { + + volume.upperThreshold = max; + + } + + return volume; + + } + + parseChars( array, start, end ) { + + // without borders, use the whole array + if ( start === undefined ) { + + start = 0; + + } + + if ( end === undefined ) { + + end = array.length; + + } + + let output = ''; + // create and append the chars + let i = 0; + for ( i = start; i < end; ++ i ) { + + output += String.fromCharCode( array[ i ] ); + + } + + return output; + + } + +} + +const _fieldFunctions = { + + type: function ( data ) { + + switch ( data ) { + + case 'uchar': + case 'unsigned char': + case 'uint8': + case 'uint8_t': + this.__array = Uint8Array; + break; + case 'signed char': + case 'int8': + case 'int8_t': + this.__array = Int8Array; + break; + case 'short': + case 'short int': + case 'signed short': + case 'signed short int': + case 'int16': + case 'int16_t': + this.__array = Int16Array; + break; + case 'ushort': + case 'unsigned short': + case 'unsigned short int': + case 'uint16': + case 'uint16_t': + this.__array = Uint16Array; + break; + case 'int': + case 'signed int': + case 'int32': + case 'int32_t': + this.__array = Int32Array; + break; + case 'uint': + case 'unsigned int': + case 'uint32': + case 'uint32_t': + this.__array = Uint32Array; + break; + case 'float': + this.__array = Float32Array; + break; + case 'double': + this.__array = Float64Array; + break; + default: + throw new Error( 'Unsupported NRRD data type: ' + data ); + + } + + return this.type = data; + + }, + + endian: function ( data ) { + + return this.endian = data; + + }, + + encoding: function ( data ) { + + return this.encoding = data; + + }, + + dimension: function ( data ) { + + return this.dim = parseInt( data, 10 ); + + }, + + sizes: function ( data ) { + + let i; + return this.sizes = ( function () { + + const _ref = data.split( /\s+/ ); + const _results = []; + + for ( let _i = 0, _len = _ref.length; _i < _len; _i ++ ) { + + i = _ref[ _i ]; + _results.push( parseInt( i, 10 ) ); + + } + + return _results; + + } )(); + + }, + + space: function ( data ) { + + return this.space = data; + + }, + + 'space origin': function ( data ) { + + return this.space_origin = data.split( '(' )[ 1 ].split( ')' )[ 0 ].split( ',' ); + + }, + + 'space directions': function ( data ) { + + let f, v; + const parts = data.match( /\(.*?\)/g ); + return this.vectors = ( function () { + + const _results = []; + + for ( let _i = 0, _len = parts.length; _i < _len; _i ++ ) { + + v = parts[ _i ]; + _results.push( ( function () { + + const _ref = v.slice( 1, - 1 ).split( /,/ ); + const _results2 = []; + + for ( let _j = 0, _len2 = _ref.length; _j < _len2; _j ++ ) { + + f = _ref[ _j ]; + _results2.push( parseFloat( f ) ); + + } + + return _results2; + + } )() ); + + } + + return _results; + + } )(); + + }, + + spacings: function ( data ) { + + let f; + const parts = data.split( /\s+/ ); + return this.spacings = ( function () { + + const _results = []; + + for ( let _i = 0, _len = parts.length; _i < _len; _i ++ ) { + + f = parts[ _i ]; + _results.push( parseFloat( f ) ); + + } + + return _results; + + } )(); + + } + +}; + +export { NRRDLoader }; diff --git a/jsm/loaders/OBJLoader.js b/jsm/loaders/OBJLoader.js new file mode 100644 index 0000000..1c7a9a8 --- /dev/null +++ b/jsm/loaders/OBJLoader.js @@ -0,0 +1,913 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Group, + LineBasicMaterial, + LineSegments, + Loader, + Material, + Mesh, + MeshPhongMaterial, + Points, + PointsMaterial, + Vector3, + Color +} from '../../src/Three.js'; + +// o object_name | g group_name +const _object_pattern = /^[og]\s*(.+)?/; +// mtllib file_reference +const _material_library_pattern = /^mtllib /; +// usemtl material_name +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 _ab = new Vector3(); +const _cb = new Vector3(); + +const _color = new Color(); + +function ParserState() { + + const state = { + objects: [], + object: {}, + + vertices: [], + normals: [], + colors: [], + uvs: [], + + materials: {}, + materialLibraries: [], + + startObject: function ( name, fromDeclaration ) { + + // If the current object (initial from reset) is not from a g/o declaration in the parsed + // file. We need to use it for the first parsed g/o to keep things in sync. + if ( this.object && this.object.fromDeclaration === false ) { + + this.object.name = name; + this.object.fromDeclaration = ( fromDeclaration !== false ); + return; + + } + + const previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined ); + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + this.object = { + name: name || '', + fromDeclaration: ( fromDeclaration !== false ), + + geometry: { + vertices: [], + normals: [], + colors: [], + uvs: [], + hasUVIndices: false + }, + materials: [], + smooth: true, + + startMaterial: function ( name, libraries ) { + + const previous = this._finalize( false ); + + // New usemtl declaration overwrites an inherited material, except if faces were declared + // after the material, then it must be preserved for proper MultiMaterial continuation. + if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) { + + this.materials.splice( previous.index, 1 ); + + } + + const material = { + index: this.materials.length, + name: name || '', + mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ), + smooth: ( previous !== undefined ? previous.smooth : this.smooth ), + groupStart: ( previous !== undefined ? previous.groupEnd : 0 ), + groupEnd: - 1, + groupCount: - 1, + inherited: false, + + clone: function ( index ) { + + const cloned = { + index: ( typeof index === 'number' ? index : this.index ), + name: this.name, + mtllib: this.mtllib, + smooth: this.smooth, + groupStart: 0, + groupEnd: - 1, + groupCount: - 1, + inherited: false + }; + cloned.clone = this.clone.bind( cloned ); + return cloned; + + } + }; + + this.materials.push( material ); + + return material; + + }, + + currentMaterial: function () { + + if ( this.materials.length > 0 ) { + + return this.materials[ this.materials.length - 1 ]; + + } + + return undefined; + + }, + + _finalize: function ( end ) { + + const lastMultiMaterial = this.currentMaterial(); + if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) { + + lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3; + lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart; + lastMultiMaterial.inherited = false; + + } + + // Ignore objects tail materials if no face declarations followed them before a new o/g started. + if ( end && this.materials.length > 1 ) { + + for ( let mi = this.materials.length - 1; mi >= 0; mi -- ) { + + if ( this.materials[ mi ].groupCount <= 0 ) { + + this.materials.splice( mi, 1 ); + + } + + } + + } + + // Guarantee at least one empty material, this makes the creation later more straight forward. + if ( end && this.materials.length === 0 ) { + + this.materials.push( { + name: '', + smooth: this.smooth + } ); + + } + + return lastMultiMaterial; + + } + }; + + // Inherit previous objects material. + // Spec tells us that a declared material must be set to all objects until a new material is declared. + // If a usemtl declaration is encountered while this new object is being parsed, it will + // overwrite the inherited material. Exception being that there was already face declarations + // to the inherited material, then it will be preserved for proper MultiMaterial continuation. + + if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) { + + const declared = previousMaterial.clone( 0 ); + declared.inherited = true; + this.object.materials.push( declared ); + + } + + this.objects.push( this.object ); + + }, + + finalize: function () { + + if ( this.object && typeof this.object._finalize === 'function' ) { + + this.object._finalize( true ); + + } + + }, + + parseVertexIndex: function ( value, len ) { + + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseNormalIndex: function ( value, len ) { + + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 3 ) * 3; + + }, + + parseUVIndex: function ( value, len ) { + + const index = parseInt( value, 10 ); + return ( index >= 0 ? index - 1 : index + len / 2 ) * 2; + + }, + + addVertex: function ( a, b, c ) { + + const src = this.vertices; + const dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addVertexPoint: function ( a ) { + + const src = this.vertices; + const dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addVertexLine: function ( a ) { + + const src = this.vertices; + const dst = this.object.geometry.vertices; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + + }, + + addNormal: function ( a, b, c ) { + + const src = this.normals; + const dst = this.object.geometry.normals; + + dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addFaceNormal: function ( a, b, c ) { + + const src = this.vertices; + const dst = this.object.geometry.normals; + + _vA.fromArray( src, a ); + _vB.fromArray( src, b ); + _vC.fromArray( src, c ); + + _cb.subVectors( _vC, _vB ); + _ab.subVectors( _vA, _vB ); + _cb.cross( _ab ); + + _cb.normalize(); + + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); + dst.push( _cb.x, _cb.y, _cb.z ); + + }, + + addColor: function ( a, b, c ) { + + const src = this.colors; + const dst = this.object.geometry.colors; + + if ( src[ a ] !== undefined ) dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] ); + if ( src[ b ] !== undefined ) dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] ); + if ( src[ c ] !== undefined ) dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] ); + + }, + + addUV: function ( a, b, c ) { + + const src = this.uvs; + const dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + dst.push( src[ b + 0 ], src[ b + 1 ] ); + dst.push( src[ c + 0 ], src[ c + 1 ] ); + + }, + + addDefaultUV: function () { + + const dst = this.object.geometry.uvs; + + dst.push( 0, 0 ); + dst.push( 0, 0 ); + dst.push( 0, 0 ); + + }, + + addUVLine: function ( a ) { + + const src = this.uvs; + const dst = this.object.geometry.uvs; + + dst.push( src[ a + 0 ], src[ a + 1 ] ); + + }, + + addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) { + + const vLen = this.vertices.length; + + let ia = this.parseVertexIndex( a, vLen ); + let ib = this.parseVertexIndex( b, vLen ); + let ic = this.parseVertexIndex( c, vLen ); + + this.addVertex( ia, ib, ic ); + this.addColor( ia, ib, ic ); + + // normals + + if ( na !== undefined && na !== '' ) { + + const nLen = this.normals.length; + + ia = this.parseNormalIndex( na, nLen ); + ib = this.parseNormalIndex( nb, nLen ); + ic = this.parseNormalIndex( nc, nLen ); + + this.addNormal( ia, ib, ic ); + + } else { + + this.addFaceNormal( ia, ib, ic ); + + } + + // uvs + + if ( ua !== undefined && ua !== '' ) { + + const uvLen = this.uvs.length; + + ia = this.parseUVIndex( ua, uvLen ); + ib = this.parseUVIndex( ub, uvLen ); + ic = this.parseUVIndex( uc, uvLen ); + + this.addUV( ia, ib, ic ); + + this.object.geometry.hasUVIndices = true; + + } else { + + // add placeholder values (for inconsistent face definitions) + + this.addDefaultUV(); + + } + + }, + + addPointGeometry: function ( vertices ) { + + this.object.geometry.type = 'Points'; + + const vLen = this.vertices.length; + + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { + + const index = this.parseVertexIndex( vertices[ vi ], vLen ); + + this.addVertexPoint( index ); + this.addColor( index ); + + } + + }, + + addLineGeometry: function ( vertices, uvs ) { + + this.object.geometry.type = 'Line'; + + const vLen = this.vertices.length; + const uvLen = this.uvs.length; + + for ( let vi = 0, l = vertices.length; vi < l; vi ++ ) { + + this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) ); + + } + + for ( let uvi = 0, l = uvs.length; uvi < l; uvi ++ ) { + + this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) ); + + } + + } + + }; + + state.startObject( '', false ); + + return state; + +} + +// + +class OBJLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.materials = null; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + setMaterials( materials ) { + + this.materials = materials; + + return this; + + } + + parse( text ) { + + const state = new ParserState(); + + if ( text.indexOf( '\r\n' ) !== - 1 ) { + + // This is faster than String.split with regex that splits on both + text = text.replace( /\r\n/g, '\n' ); + + } + + if ( text.indexOf( '\\\n' ) !== - 1 ) { + + // join lines separated by a line continuation character (\) + text = text.replace( /\\\n/g, '' ); + + } + + const lines = text.split( '\n' ); + let line = '', lineFirstChar = ''; + let lineLength = 0; + let result = []; + + // Faster to just trim left side of the line. Use if available. + const trimLeft = ( typeof ''.trimLeft === 'function' ); + + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + line = lines[ i ]; + + line = trimLeft ? line.trimLeft() : line.trim(); + + lineLength = line.length; + + if ( lineLength === 0 ) continue; + + lineFirstChar = line.charAt( 0 ); + + // @todo invoke passed in handler if any + if ( lineFirstChar === '#' ) continue; + + if ( lineFirstChar === 'v' ) { + + const data = line.split( /\s+/ ); + + switch ( data[ 0 ] ) { + + case 'v': + state.vertices.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + if ( data.length >= 7 ) { + + _color.setRGB( + parseFloat( data[ 4 ] ), + parseFloat( data[ 5 ] ), + parseFloat( data[ 6 ] ) + ).convertSRGBToLinear(); + + state.colors.push( _color.r, _color.g, _color.b ); + + } else { + + // if no colors are defined, add placeholders so color and vertex indices match + + state.colors.push( undefined, undefined, undefined ); + + } + + break; + case 'vn': + state.normals.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ), + parseFloat( data[ 3 ] ) + ); + break; + case 'vt': + state.uvs.push( + parseFloat( data[ 1 ] ), + parseFloat( data[ 2 ] ) + ); + break; + + } + + } else if ( lineFirstChar === 'f' ) { + + const lineData = line.slice( 1 ).trim(); + const vertexData = lineData.split( /\s+/ ); + const faceVertices = []; + + // Parse the face vertex data into an easy to work with format + + for ( let j = 0, jl = vertexData.length; j < jl; j ++ ) { + + const vertex = vertexData[ j ]; + + if ( vertex.length > 0 ) { + + const vertexParts = vertex.split( '/' ); + faceVertices.push( vertexParts ); + + } + + } + + // Draw an edge between the first vertex and all subsequent vertices to form an n-gon + + const v1 = faceVertices[ 0 ]; + + for ( let j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) { + + const v2 = faceVertices[ j ]; + const v3 = faceVertices[ j + 1 ]; + + state.addFace( + v1[ 0 ], v2[ 0 ], v3[ 0 ], + v1[ 1 ], v2[ 1 ], v3[ 1 ], + v1[ 2 ], v2[ 2 ], v3[ 2 ] + ); + + } + + } else if ( lineFirstChar === 'l' ) { + + const lineParts = line.substring( 1 ).trim().split( ' ' ); + let lineVertices = []; + const lineUVs = []; + + if ( line.indexOf( '/' ) === - 1 ) { + + lineVertices = lineParts; + + } else { + + for ( let li = 0, llen = lineParts.length; li < llen; li ++ ) { + + const parts = lineParts[ li ].split( '/' ); + + if ( parts[ 0 ] !== '' ) lineVertices.push( parts[ 0 ] ); + if ( parts[ 1 ] !== '' ) lineUVs.push( parts[ 1 ] ); + + } + + } + + state.addLineGeometry( lineVertices, lineUVs ); + + } else if ( lineFirstChar === 'p' ) { + + const lineData = line.slice( 1 ).trim(); + const pointData = lineData.split( ' ' ); + + state.addPointGeometry( pointData ); + + } else if ( ( result = _object_pattern.exec( line ) ) !== null ) { + + // o object_name + // or + // g group_name + + // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869 + // let name = result[ 0 ].slice( 1 ).trim(); + const name = ( ' ' + result[ 0 ].slice( 1 ).trim() ).slice( 1 ); + + state.startObject( name ); + + } else if ( _material_use_pattern.test( line ) ) { + + // material + + state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries ); + + } else if ( _material_library_pattern.test( line ) ) { + + // mtl file + + state.materialLibraries.push( line.substring( 7 ).trim() ); + + } else if ( _map_use_pattern.test( line ) ) { + + // the line is parsed but ignored since the loader assumes textures are defined MTL files + // (according to https://www.okino.com/conv/imp_wave.htm, 'usemap' is the old-style Wavefront texture reference method) + + console.warn( 'THREE.OBJLoader: Rendering identifier "usemap" not supported. Textures must be defined in MTL files.' ); + + } else if ( lineFirstChar === 's' ) { + + result = line.split( ' ' ); + + // smooth shading + + // @todo Handle files that have varying smooth values for a set of faces inside one geometry, + // but does not define a usemtl for each face set. + // This should be detected and a dummy material created (later MultiMaterial and geometry groups). + // This requires some care to not create extra material on each smooth value for "normal" obj files. + // where explicit usemtl defines geometry groups. + // Example asset: examples/models/obj/cerberus/Cerberus.obj + + /* + * http://paulbourke.net/dataformats/obj/ + * + * From chapter "Grouping" Syntax explanation "s group_number": + * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off. + * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form + * surfaces, smoothing groups are either turned on or off; there is no difference between values greater + * than 0." + */ + if ( result.length > 1 ) { + + const value = result[ 1 ].trim().toLowerCase(); + state.object.smooth = ( value !== '0' && value !== 'off' ); + + } else { + + // ZBrush can produce "s" lines #11707 + state.object.smooth = true; + + } + + const material = state.object.currentMaterial(); + if ( material ) material.smooth = state.object.smooth; + + } else { + + // Handle null terminated files without exception + if ( line === '\0' ) continue; + + console.warn( 'THREE.OBJLoader: Unexpected line: "' + line + '"' ); + + } + + } + + state.finalize(); + + const container = new Group(); + container.materialLibraries = [].concat( state.materialLibraries ); + + const hasPrimitives = ! ( state.objects.length === 1 && state.objects[ 0 ].geometry.vertices.length === 0 ); + + if ( hasPrimitives === true ) { + + for ( let i = 0, l = state.objects.length; i < l; i ++ ) { + + const object = state.objects[ i ]; + const geometry = object.geometry; + const materials = object.materials; + const isLine = ( geometry.type === 'Line' ); + const isPoints = ( geometry.type === 'Points' ); + let hasVertexColors = false; + + // Skip o/g line declarations that did not follow with any faces + if ( geometry.vertices.length === 0 ) continue; + + const buffergeometry = new BufferGeometry(); + + buffergeometry.setAttribute( 'position', new Float32BufferAttribute( geometry.vertices, 3 ) ); + + if ( geometry.normals.length > 0 ) { + + buffergeometry.setAttribute( 'normal', new Float32BufferAttribute( geometry.normals, 3 ) ); + + } + + if ( geometry.colors.length > 0 ) { + + hasVertexColors = true; + buffergeometry.setAttribute( 'color', new Float32BufferAttribute( geometry.colors, 3 ) ); + + } + + if ( geometry.hasUVIndices === true ) { + + buffergeometry.setAttribute( 'uv', new Float32BufferAttribute( geometry.uvs, 2 ) ); + + } + + // Create materials + + const createdMaterials = []; + + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + const sourceMaterial = materials[ mi ]; + const materialHash = sourceMaterial.name + '_' + sourceMaterial.smooth + '_' + hasVertexColors; + let material = state.materials[ materialHash ]; + + if ( this.materials !== null ) { + + material = this.materials.create( sourceMaterial.name ); + + // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material. + if ( isLine && material && ! ( material instanceof LineBasicMaterial ) ) { + + const materialLine = new LineBasicMaterial(); + Material.prototype.copy.call( materialLine, material ); + materialLine.color.copy( material.color ); + material = materialLine; + + } else if ( isPoints && material && ! ( material instanceof PointsMaterial ) ) { + + const materialPoints = new PointsMaterial( { size: 10, sizeAttenuation: false } ); + Material.prototype.copy.call( materialPoints, material ); + materialPoints.color.copy( material.color ); + materialPoints.map = material.map; + material = materialPoints; + + } + + } + + if ( material === undefined ) { + + if ( isLine ) { + + material = new LineBasicMaterial(); + + } else if ( isPoints ) { + + material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); + + } else { + + material = new MeshPhongMaterial(); + + } + + material.name = sourceMaterial.name; + material.flatShading = sourceMaterial.smooth ? false : true; + material.vertexColors = hasVertexColors; + + state.materials[ materialHash ] = material; + + } + + createdMaterials.push( material ); + + } + + // Create mesh + + let mesh; + + if ( createdMaterials.length > 1 ) { + + for ( let mi = 0, miLen = materials.length; mi < miLen; mi ++ ) { + + const sourceMaterial = materials[ mi ]; + buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi ); + + } + + if ( isLine ) { + + mesh = new LineSegments( buffergeometry, createdMaterials ); + + } else if ( isPoints ) { + + mesh = new Points( buffergeometry, createdMaterials ); + + } else { + + mesh = new Mesh( buffergeometry, createdMaterials ); + + } + + } else { + + if ( isLine ) { + + mesh = new LineSegments( buffergeometry, createdMaterials[ 0 ] ); + + } else if ( isPoints ) { + + mesh = new Points( buffergeometry, createdMaterials[ 0 ] ); + + } else { + + mesh = new Mesh( buffergeometry, createdMaterials[ 0 ] ); + + } + + } + + mesh.name = object.name; + + container.add( mesh ); + + } + + } else { + + // if there is only the default parser state object with no geometry data, interpret data as point cloud + + if ( state.vertices.length > 0 ) { + + const material = new PointsMaterial( { size: 1, sizeAttenuation: false } ); + + const buffergeometry = new BufferGeometry(); + + buffergeometry.setAttribute( 'position', new Float32BufferAttribute( state.vertices, 3 ) ); + + if ( state.colors.length > 0 && state.colors[ 0 ] !== undefined ) { + + buffergeometry.setAttribute( 'color', new Float32BufferAttribute( state.colors, 3 ) ); + material.vertexColors = true; + + } + + const points = new Points( buffergeometry, material ); + container.add( points ); + + } + + } + + return container; + + } + +} + +export { OBJLoader }; diff --git a/jsm/loaders/PCDLoader.js b/jsm/loaders/PCDLoader.js new file mode 100644 index 0000000..2a1784c --- /dev/null +++ b/jsm/loaders/PCDLoader.js @@ -0,0 +1,413 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + LoaderUtils, + Points, + PointsMaterial +} from 'three'; + +class PCDLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.littleEndian = true; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( data ) { + + try { + + onLoad( scope.parse( data, url ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data, url ) { + + // from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js + + function decompressLZF( inData, outLength ) { + + const inLength = inData.length; + const outData = new Uint8Array( outLength ); + let inPtr = 0; + let outPtr = 0; + let ctrl; + let len; + let ref; + do { + + ctrl = inData[ inPtr ++ ]; + if ( ctrl < ( 1 << 5 ) ) { + + ctrl ++; + if ( outPtr + ctrl > outLength ) throw new Error( 'Output buffer is not large enough' ); + if ( inPtr + ctrl > inLength ) throw new Error( 'Invalid compressed data' ); + do { + + outData[ outPtr ++ ] = inData[ inPtr ++ ]; + + } while ( -- ctrl ); + + } else { + + len = ctrl >> 5; + ref = outPtr - ( ( ctrl & 0x1f ) << 8 ) - 1; + if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' ); + if ( len === 7 ) { + + len += inData[ inPtr ++ ]; + if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' ); + + } + + ref -= inData[ inPtr ++ ]; + if ( outPtr + len + 2 > outLength ) throw new Error( 'Output buffer is not large enough' ); + if ( ref < 0 ) throw new Error( 'Invalid compressed data' ); + if ( ref >= outPtr ) throw new Error( 'Invalid compressed data' ); + do { + + outData[ outPtr ++ ] = outData[ ref ++ ]; + + } while ( -- len + 2 ); + + } + + } while ( inPtr < inLength ); + + return outData; + + } + + function parseHeader( data ) { + + const PCDheader = {}; + const result1 = data.search( /[\r\n]DATA\s(\S*)\s/i ); + const result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.slice( result1 - 1 ) ); + + PCDheader.data = result2[ 1 ]; + PCDheader.headerLen = result2[ 0 ].length + result1; + PCDheader.str = data.slice( 0, PCDheader.headerLen ); + + // remove comments + + PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' ); + + // parse + + PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str ); + PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str ); + PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str ); + PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str ); + PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str ); + PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str ); + PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str ); + PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str ); + PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str ); + + // evaluate + + if ( PCDheader.version !== null ) + PCDheader.version = parseFloat( PCDheader.version[ 1 ] ); + + PCDheader.fields = ( PCDheader.fields !== null ) ? PCDheader.fields[ 1 ].split( ' ' ) : []; + + if ( PCDheader.type !== null ) + PCDheader.type = PCDheader.type[ 1 ].split( ' ' ); + + if ( PCDheader.width !== null ) + PCDheader.width = parseInt( PCDheader.width[ 1 ] ); + + if ( PCDheader.height !== null ) + PCDheader.height = parseInt( PCDheader.height[ 1 ] ); + + if ( PCDheader.viewpoint !== null ) + PCDheader.viewpoint = PCDheader.viewpoint[ 1 ]; + + if ( PCDheader.points !== null ) + PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 ); + + if ( PCDheader.points === null ) + PCDheader.points = PCDheader.width * PCDheader.height; + + if ( PCDheader.size !== null ) { + + PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) { + + return parseInt( x, 10 ); + + } ); + + } + + if ( PCDheader.count !== null ) { + + PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) { + + return parseInt( x, 10 ); + + } ); + + } else { + + PCDheader.count = []; + + for ( let i = 0, l = PCDheader.fields.length; i < l; i ++ ) { + + PCDheader.count.push( 1 ); + + } + + } + + PCDheader.offset = {}; + + let sizeSum = 0; + + for ( let i = 0, l = PCDheader.fields.length; i < l; i ++ ) { + + if ( PCDheader.data === 'ascii' ) { + + PCDheader.offset[ PCDheader.fields[ i ] ] = i; + + } else { + + PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum; + sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ]; + + } + + } + + // for binary only + + PCDheader.rowSize = sizeSum; + + return PCDheader; + + } + + const textData = LoaderUtils.decodeText( new Uint8Array( data ) ); + + // parse header (always ascii format) + + const PCDheader = parseHeader( textData ); + + // parse data + + const position = []; + const normal = []; + const color = []; + + // ascii + + if ( PCDheader.data === 'ascii' ) { + + const offset = PCDheader.offset; + const pcdData = textData.slice( PCDheader.headerLen ); + const lines = pcdData.split( '\n' ); + + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + if ( lines[ i ] === '' ) continue; + + const line = lines[ i ].split( ' ' ); + + if ( offset.x !== undefined ) { + + position.push( parseFloat( line[ offset.x ] ) ); + position.push( parseFloat( line[ offset.y ] ) ); + position.push( parseFloat( line[ offset.z ] ) ); + + } + + if ( offset.rgb !== undefined ) { + + const rgb_field_index = PCDheader.fields.findIndex( ( field ) => field === 'rgb' ); + const rgb_type = PCDheader.type[ rgb_field_index ]; + + const float = parseFloat( line[ offset.rgb ] ); + let rgb = float; + + if ( rgb_type === 'F' ) { + + // treat float values as int + // https://github.com/daavoo/pyntcloud/pull/204/commits/7b4205e64d5ed09abe708b2e91b615690c24d518 + const farr = new Float32Array( 1 ); + farr[ 0 ] = float; + rgb = new Int32Array( farr.buffer )[ 0 ]; + + } + + const r = ( rgb >> 16 ) & 0x0000ff; + const g = ( rgb >> 8 ) & 0x0000ff; + const b = ( rgb >> 0 ) & 0x0000ff; + color.push( r / 255, g / 255, b / 255 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( parseFloat( line[ offset.normal_x ] ) ); + normal.push( parseFloat( line[ offset.normal_y ] ) ); + normal.push( parseFloat( line[ offset.normal_z ] ) ); + + } + + } + + } + + // binary-compressed + + // normally data in PCD files are organized as array of structures: XYZRGBXYZRGB + // binary compressed PCD files organize their data as structure of arrays: XXYYZZRGBRGB + // that requires a totally different parsing approach compared to non-compressed data + + if ( PCDheader.data === 'binary_compressed' ) { + + const sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) ); + const compressedSize = sizes[ 0 ]; + const decompressedSize = sizes[ 1 ]; + const decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize ); + const dataview = new DataView( decompressed.buffer ); + + const offset = PCDheader.offset; + + for ( let i = 0; i < PCDheader.points; i ++ ) { + + if ( offset.x !== undefined ) { + + position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ 0 ] * i, this.littleEndian ) ); + position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ 1 ] * i, this.littleEndian ) ); + position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ 2 ] * i, this.littleEndian ) ); + + } + + if ( offset.rgb !== undefined ) { + + color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 2 ) / 255.0 ); + color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 1 ) / 255.0 ); + color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 0 ) / 255.0 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ 4 ] * i, this.littleEndian ) ); + normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ 5 ] * i, this.littleEndian ) ); + normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ 6 ] * i, this.littleEndian ) ); + + } + + } + + } + + // binary + + if ( PCDheader.data === 'binary' ) { + + const dataview = new DataView( data, PCDheader.headerLen ); + const offset = PCDheader.offset; + + for ( let i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) { + + if ( offset.x !== undefined ) { + + position.push( dataview.getFloat32( row + offset.x, this.littleEndian ) ); + position.push( dataview.getFloat32( row + offset.y, this.littleEndian ) ); + position.push( dataview.getFloat32( row + offset.z, this.littleEndian ) ); + + } + + if ( offset.rgb !== undefined ) { + + color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 ); + color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 ); + color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 ); + + } + + if ( offset.normal_x !== undefined ) { + + normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) ); + normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) ); + normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) ); + + } + + } + + } + + // build geometry + + const geometry = new BufferGeometry(); + + if ( position.length > 0 ) geometry.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) ); + if ( normal.length > 0 ) geometry.setAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) ); + if ( color.length > 0 ) geometry.setAttribute( 'color', new Float32BufferAttribute( color, 3 ) ); + + geometry.computeBoundingSphere(); + + // build material + + const material = new PointsMaterial( { size: 0.005 } ); + + if ( color.length > 0 ) { + + material.vertexColors = true; + + } else { + + material.color.setHex( Math.random() * 0xffffff ); + + } + + // build point cloud + + const mesh = new Points( geometry, material ); + let name = url.split( '' ).reverse().join( '' ); + name = /([^\/]*)/.exec( name ); + name = name[ 1 ].split( '' ).reverse().join( '' ); + mesh.name = name; + + return mesh; + + } + +} + +export { PCDLoader }; diff --git a/jsm/loaders/PDBLoader.js b/jsm/loaders/PDBLoader.js new file mode 100644 index 0000000..87c048c --- /dev/null +++ b/jsm/loaders/PDBLoader.js @@ -0,0 +1,227 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader +} from 'three'; + +class PDBLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + // Based on CanvasMol PDB parser + + parse( text ) { + + function trim( text ) { + + return text.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); + + } + + function capitalize( text ) { + + return text.charAt( 0 ).toUpperCase() + text.slice( 1 ).toLowerCase(); + + } + + function hash( s, e ) { + + return 's' + Math.min( s, e ) + 'e' + Math.max( s, e ); + + } + + function parseBond( start, length, satom, i ) { + + const eatom = parseInt( lines[ i ].slice( start, start + length ) ); + + if ( eatom ) { + + const h = hash( satom, eatom ); + + if ( _bhash[ h ] === undefined ) { + + _bonds.push( [ satom - 1, eatom - 1, 1 ] ); + _bhash[ h ] = _bonds.length - 1; + + } else { + + // doesn't really work as almost all PDBs + // have just normal bonds appearing multiple + // times instead of being double/triple bonds + // bonds[bhash[h]][2] += 1; + + } + + } + + } + + function buildGeometry() { + + const build = { + geometryAtoms: new BufferGeometry(), + geometryBonds: new BufferGeometry(), + json: { + atoms: atoms + } + }; + + const geometryAtoms = build.geometryAtoms; + const geometryBonds = build.geometryBonds; + + const verticesAtoms = []; + const colorsAtoms = []; + const verticesBonds = []; + + // atoms + + for ( let i = 0, l = atoms.length; i < l; i ++ ) { + + const atom = atoms[ i ]; + + const x = atom[ 0 ]; + const y = atom[ 1 ]; + const z = atom[ 2 ]; + + verticesAtoms.push( x, y, z ); + + const r = atom[ 3 ][ 0 ] / 255; + const g = atom[ 3 ][ 1 ] / 255; + const b = atom[ 3 ][ 2 ] / 255; + + colorsAtoms.push( r, g, b ); + + } + + // bonds + + for ( let i = 0, l = _bonds.length; i < l; i ++ ) { + + const bond = _bonds[ i ]; + + const start = bond[ 0 ]; + const end = bond[ 1 ]; + + const startAtom = _atomMap[ start ]; + const endAtom = _atomMap[ end ]; + + let x = startAtom[ 0 ]; + let y = startAtom[ 1 ]; + let z = startAtom[ 2 ]; + + verticesBonds.push( x, y, z ); + + x = endAtom[ 0 ]; + y = endAtom[ 1 ]; + z = endAtom[ 2 ]; + + verticesBonds.push( x, y, z ); + + } + + // build geometry + + geometryAtoms.setAttribute( 'position', new Float32BufferAttribute( verticesAtoms, 3 ) ); + geometryAtoms.setAttribute( 'color', new Float32BufferAttribute( colorsAtoms, 3 ) ); + + geometryBonds.setAttribute( 'position', new Float32BufferAttribute( verticesBonds, 3 ) ); + + return build; + + } + + const CPK = { h: [ 255, 255, 255 ], he: [ 217, 255, 255 ], li: [ 204, 128, 255 ], be: [ 194, 255, 0 ], b: [ 255, 181, 181 ], c: [ 144, 144, 144 ], n: [ 48, 80, 248 ], o: [ 255, 13, 13 ], f: [ 144, 224, 80 ], ne: [ 179, 227, 245 ], na: [ 171, 92, 242 ], mg: [ 138, 255, 0 ], al: [ 191, 166, 166 ], si: [ 240, 200, 160 ], p: [ 255, 128, 0 ], s: [ 255, 255, 48 ], cl: [ 31, 240, 31 ], ar: [ 128, 209, 227 ], k: [ 143, 64, 212 ], ca: [ 61, 255, 0 ], sc: [ 230, 230, 230 ], ti: [ 191, 194, 199 ], v: [ 166, 166, 171 ], cr: [ 138, 153, 199 ], mn: [ 156, 122, 199 ], fe: [ 224, 102, 51 ], co: [ 240, 144, 160 ], ni: [ 80, 208, 80 ], cu: [ 200, 128, 51 ], zn: [ 125, 128, 176 ], ga: [ 194, 143, 143 ], ge: [ 102, 143, 143 ], as: [ 189, 128, 227 ], se: [ 255, 161, 0 ], br: [ 166, 41, 41 ], kr: [ 92, 184, 209 ], rb: [ 112, 46, 176 ], sr: [ 0, 255, 0 ], y: [ 148, 255, 255 ], zr: [ 148, 224, 224 ], nb: [ 115, 194, 201 ], mo: [ 84, 181, 181 ], tc: [ 59, 158, 158 ], ru: [ 36, 143, 143 ], rh: [ 10, 125, 140 ], pd: [ 0, 105, 133 ], ag: [ 192, 192, 192 ], cd: [ 255, 217, 143 ], in: [ 166, 117, 115 ], sn: [ 102, 128, 128 ], sb: [ 158, 99, 181 ], te: [ 212, 122, 0 ], i: [ 148, 0, 148 ], xe: [ 66, 158, 176 ], cs: [ 87, 23, 143 ], ba: [ 0, 201, 0 ], la: [ 112, 212, 255 ], ce: [ 255, 255, 199 ], pr: [ 217, 255, 199 ], nd: [ 199, 255, 199 ], pm: [ 163, 255, 199 ], sm: [ 143, 255, 199 ], eu: [ 97, 255, 199 ], gd: [ 69, 255, 199 ], tb: [ 48, 255, 199 ], dy: [ 31, 255, 199 ], ho: [ 0, 255, 156 ], er: [ 0, 230, 117 ], tm: [ 0, 212, 82 ], yb: [ 0, 191, 56 ], lu: [ 0, 171, 36 ], hf: [ 77, 194, 255 ], ta: [ 77, 166, 255 ], w: [ 33, 148, 214 ], re: [ 38, 125, 171 ], os: [ 38, 102, 150 ], ir: [ 23, 84, 135 ], pt: [ 208, 208, 224 ], au: [ 255, 209, 35 ], hg: [ 184, 184, 208 ], tl: [ 166, 84, 77 ], pb: [ 87, 89, 97 ], bi: [ 158, 79, 181 ], po: [ 171, 92, 0 ], at: [ 117, 79, 69 ], rn: [ 66, 130, 150 ], fr: [ 66, 0, 102 ], ra: [ 0, 125, 0 ], ac: [ 112, 171, 250 ], th: [ 0, 186, 255 ], pa: [ 0, 161, 255 ], u: [ 0, 143, 255 ], np: [ 0, 128, 255 ], pu: [ 0, 107, 255 ], am: [ 84, 92, 242 ], cm: [ 120, 92, 227 ], bk: [ 138, 79, 227 ], cf: [ 161, 54, 212 ], es: [ 179, 31, 212 ], fm: [ 179, 31, 186 ], md: [ 179, 13, 166 ], no: [ 189, 13, 135 ], lr: [ 199, 0, 102 ], rf: [ 204, 0, 89 ], db: [ 209, 0, 79 ], sg: [ 217, 0, 69 ], bh: [ 224, 0, 56 ], hs: [ 230, 0, 46 ], mt: [ 235, 0, 38 ], ds: [ 235, 0, 38 ], rg: [ 235, 0, 38 ], cn: [ 235, 0, 38 ], uut: [ 235, 0, 38 ], uuq: [ 235, 0, 38 ], uup: [ 235, 0, 38 ], uuh: [ 235, 0, 38 ], uus: [ 235, 0, 38 ], uuo: [ 235, 0, 38 ] }; + + const atoms = []; + + const _bonds = []; + const _bhash = {}; + const _atomMap = {}; + + // parse + + const lines = text.split( '\n' ); + + for ( let i = 0, l = lines.length; i < l; i ++ ) { + + if ( lines[ i ].slice( 0, 4 ) === 'ATOM' || lines[ i ].slice( 0, 6 ) === 'HETATM' ) { + + const x = parseFloat( lines[ i ].slice( 30, 37 ) ); + const y = parseFloat( lines[ i ].slice( 38, 45 ) ); + const z = parseFloat( lines[ i ].slice( 46, 53 ) ); + const index = parseInt( lines[ i ].slice( 6, 11 ) ) - 1; + + let e = trim( lines[ i ].slice( 76, 78 ) ).toLowerCase(); + + if ( e === '' ) { + + e = trim( lines[ i ].slice( 12, 14 ) ).toLowerCase(); + + } + + const atomData = [ x, y, z, CPK[ e ], capitalize( e ) ]; + + atoms.push( atomData ); + _atomMap[ index ] = atomData; + + } else if ( lines[ i ].slice( 0, 6 ) === 'CONECT' ) { + + const satom = parseInt( lines[ i ].slice( 6, 11 ) ); + + parseBond( 11, 5, satom, i ); + parseBond( 16, 5, satom, i ); + parseBond( 21, 5, satom, i ); + parseBond( 26, 5, satom, i ); + + } + + } + + // build and return geometry + + return buildGeometry(); + + } + +} + +export { PDBLoader }; diff --git a/jsm/loaders/PLYLoader.js b/jsm/loaders/PLYLoader.js new file mode 100644 index 0000000..d9d1c71 --- /dev/null +++ b/jsm/loaders/PLYLoader.js @@ -0,0 +1,564 @@ +import { + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + LoaderUtils, + Color +} from 'three'; + +/** + * Description: A THREE loader for PLY ASCII files (known as the Polygon + * File Format or the Stanford Triangle Format). + * + * Limitations: ASCII decoding assumes file is UTF-8. + * + * Usage: + * const loader = new PLYLoader(); + * loader.load('./models/ply/ascii/dolphins.ply', function (geometry) { + * + * scene.add( new THREE.Mesh( geometry ) ); + * + * } ); + * + * If the PLY file uses non standard property names, they can be mapped while + * loading. For example, the following maps the properties + * “diffuse_(red|green|blue)” in the file to standard color names. + * + * loader.setPropertyNameMapping( { + * diffuse_red: 'red', + * diffuse_green: 'green', + * diffuse_blue: 'blue' + * } ); + * + */ + +const _color = new Color(); + +class PLYLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + this.propertyNameMapping = {}; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + setPropertyNameMapping( mapping ) { + + this.propertyNameMapping = mapping; + + } + + parse( data ) { + + function parseHeader( data ) { + + const patternHeader = /^ply([\s\S]*)end_header\r?\n/; + let headerText = ''; + let headerLength = 0; + const result = patternHeader.exec( data ); + + if ( result !== null ) { + + headerText = result[ 1 ]; + headerLength = new Blob( [ result[ 0 ] ] ).size; + + } + + const header = { + comments: [], + elements: [], + headerLength: headerLength, + objInfo: '' + }; + + const lines = headerText.split( '\n' ); + let currentElement; + + function make_ply_element_property( propertValues, propertyNameMapping ) { + + const property = { type: propertValues[ 0 ] }; + + if ( property.type === 'list' ) { + + property.name = propertValues[ 3 ]; + property.countType = propertValues[ 1 ]; + property.itemType = propertValues[ 2 ]; + + } else { + + property.name = propertValues[ 1 ]; + + } + + if ( property.name in propertyNameMapping ) { + + property.name = propertyNameMapping[ property.name ]; + + } + + return property; + + } + + for ( let i = 0; i < lines.length; i ++ ) { + + let line = lines[ i ]; + line = line.trim(); + + if ( line === '' ) continue; + + const lineValues = line.split( /\s+/ ); + const lineType = lineValues.shift(); + line = lineValues.join( ' ' ); + + switch ( lineType ) { + + case 'format': + + header.format = lineValues[ 0 ]; + header.version = lineValues[ 1 ]; + + break; + + case 'comment': + + header.comments.push( line ); + + break; + + case 'element': + + if ( currentElement !== undefined ) { + + header.elements.push( currentElement ); + + } + + currentElement = {}; + currentElement.name = lineValues[ 0 ]; + currentElement.count = parseInt( lineValues[ 1 ] ); + currentElement.properties = []; + + break; + + case 'property': + + currentElement.properties.push( make_ply_element_property( lineValues, scope.propertyNameMapping ) ); + + break; + + case 'obj_info': + + header.objInfo = line; + + break; + + + default: + + console.log( 'unhandled', lineType, lineValues ); + + } + + } + + if ( currentElement !== undefined ) { + + header.elements.push( currentElement ); + + } + + return header; + + } + + function parseASCIINumber( n, type ) { + + switch ( type ) { + + case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint': + case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32': + + return parseInt( n ); + + case 'float': case 'double': case 'float32': case 'float64': + + return parseFloat( n ); + + } + + } + + function parseASCIIElement( properties, line ) { + + const values = line.split( /\s+/ ); + + const element = {}; + + for ( let i = 0; i < properties.length; i ++ ) { + + if ( properties[ i ].type === 'list' ) { + + const list = []; + const n = parseASCIINumber( values.shift(), properties[ i ].countType ); + + for ( let j = 0; j < n; j ++ ) { + + list.push( parseASCIINumber( values.shift(), properties[ i ].itemType ) ); + + } + + element[ properties[ i ].name ] = list; + + } else { + + element[ properties[ i ].name ] = parseASCIINumber( values.shift(), properties[ i ].type ); + + } + + } + + return element; + + } + + function parseASCII( data, header ) { + + // PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format) + + const buffer = { + indices: [], + vertices: [], + normals: [], + uvs: [], + faceVertexUvs: [], + colors: [] + }; + + let result; + + const patternBody = /end_header\s([\s\S]*)$/; + let body = ''; + if ( ( result = patternBody.exec( data ) ) !== null ) { + + body = result[ 1 ]; + + } + + const lines = body.split( '\n' ); + let currentElement = 0; + let currentElementCount = 0; + + for ( let i = 0; i < lines.length; i ++ ) { + + let line = lines[ i ]; + line = line.trim(); + if ( line === '' ) { + + continue; + + } + + if ( currentElementCount >= header.elements[ currentElement ].count ) { + + currentElement ++; + currentElementCount = 0; + + } + + const element = parseASCIIElement( header.elements[ currentElement ].properties, line ); + + handleElement( buffer, header.elements[ currentElement ].name, element ); + + currentElementCount ++; + + } + + return postProcess( buffer ); + + } + + function postProcess( buffer ) { + + let geometry = new BufferGeometry(); + + // mandatory buffer data + + if ( buffer.indices.length > 0 ) { + + geometry.setIndex( buffer.indices ); + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( buffer.vertices, 3 ) ); + + // optional buffer data + + if ( buffer.normals.length > 0 ) { + + geometry.setAttribute( 'normal', new Float32BufferAttribute( buffer.normals, 3 ) ); + + } + + if ( buffer.uvs.length > 0 ) { + + geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.uvs, 2 ) ); + + } + + if ( buffer.colors.length > 0 ) { + + geometry.setAttribute( 'color', new Float32BufferAttribute( buffer.colors, 3 ) ); + + } + + if ( buffer.faceVertexUvs.length > 0 ) { + + geometry = geometry.toNonIndexed(); + geometry.setAttribute( 'uv', new Float32BufferAttribute( buffer.faceVertexUvs, 2 ) ); + + } + + geometry.computeBoundingSphere(); + + return geometry; + + } + + function handleElement( buffer, elementName, element ) { + + function findAttrName( names ) { + + for ( let i = 0, l = names.length; i < l; i ++ ) { + + const name = names[ i ]; + + if ( name in element ) return name; + + } + + return null; + + } + + const attrX = findAttrName( [ 'x', 'px', 'posx' ] ) || 'x'; + const attrY = findAttrName( [ 'y', 'py', 'posy' ] ) || 'y'; + const attrZ = findAttrName( [ 'z', 'pz', 'posz' ] ) || 'z'; + const attrNX = findAttrName( [ 'nx', 'normalx' ] ); + const attrNY = findAttrName( [ 'ny', 'normaly' ] ); + const attrNZ = findAttrName( [ 'nz', 'normalz' ] ); + const attrS = findAttrName( [ 's', 'u', 'texture_u', 'tx' ] ); + const attrT = findAttrName( [ 't', 'v', 'texture_v', 'ty' ] ); + const attrR = findAttrName( [ 'red', 'diffuse_red', 'r', 'diffuse_r' ] ); + const attrG = findAttrName( [ 'green', 'diffuse_green', 'g', 'diffuse_g' ] ); + const attrB = findAttrName( [ 'blue', 'diffuse_blue', 'b', 'diffuse_b' ] ); + + if ( elementName === 'vertex' ) { + + buffer.vertices.push( element[ attrX ], element[ attrY ], element[ attrZ ] ); + + if ( attrNX !== null && attrNY !== null && attrNZ !== null ) { + + buffer.normals.push( element[ attrNX ], element[ attrNY ], element[ attrNZ ] ); + + } + + if ( attrS !== null && attrT !== null ) { + + buffer.uvs.push( element[ attrS ], element[ attrT ] ); + + } + + if ( attrR !== null && attrG !== null && attrB !== null ) { + + _color.setRGB( + element[ attrR ] / 255.0, + element[ attrG ] / 255.0, + element[ attrB ] / 255.0 + ).convertSRGBToLinear(); + + buffer.colors.push( _color.r, _color.g, _color.b ); + + } + + } else if ( elementName === 'face' ) { + + const vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338 + const texcoord = element.texcoord; + + if ( vertex_indices.length === 3 ) { + + buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] ); + + if ( texcoord && texcoord.length === 6 ) { + + buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] ); + buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] ); + buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] ); + + } + + } else if ( vertex_indices.length === 4 ) { + + buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] ); + buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] ); + + } + + } + + } + + function binaryRead( dataview, at, type, little_endian ) { + + switch ( type ) { + + // corespondences for non-specific length types here match rply: + case 'int8': case 'char': return [ dataview.getInt8( at ), 1 ]; + case 'uint8': case 'uchar': return [ dataview.getUint8( at ), 1 ]; + case 'int16': case 'short': return [ dataview.getInt16( at, little_endian ), 2 ]; + case 'uint16': case 'ushort': return [ dataview.getUint16( at, little_endian ), 2 ]; + case 'int32': case 'int': return [ dataview.getInt32( at, little_endian ), 4 ]; + case 'uint32': case 'uint': return [ dataview.getUint32( at, little_endian ), 4 ]; + case 'float32': case 'float': return [ dataview.getFloat32( at, little_endian ), 4 ]; + case 'float64': case 'double': return [ dataview.getFloat64( at, little_endian ), 8 ]; + + } + + } + + function binaryReadElement( dataview, at, properties, little_endian ) { + + const element = {}; + let result, read = 0; + + for ( let i = 0; i < properties.length; i ++ ) { + + if ( properties[ i ].type === 'list' ) { + + const list = []; + + result = binaryRead( dataview, at + read, properties[ i ].countType, little_endian ); + const n = result[ 0 ]; + read += result[ 1 ]; + + for ( let j = 0; j < n; j ++ ) { + + result = binaryRead( dataview, at + read, properties[ i ].itemType, little_endian ); + list.push( result[ 0 ] ); + read += result[ 1 ]; + + } + + element[ properties[ i ].name ] = list; + + } else { + + result = binaryRead( dataview, at + read, properties[ i ].type, little_endian ); + element[ properties[ i ].name ] = result[ 0 ]; + read += result[ 1 ]; + + } + + } + + return [ element, read ]; + + } + + function parseBinary( data, header ) { + + const buffer = { + indices: [], + vertices: [], + normals: [], + uvs: [], + faceVertexUvs: [], + colors: [] + }; + + const little_endian = ( header.format === 'binary_little_endian' ); + const body = new DataView( data, header.headerLength ); + let result, loc = 0; + + for ( let currentElement = 0; currentElement < header.elements.length; currentElement ++ ) { + + for ( let currentElementCount = 0; currentElementCount < header.elements[ currentElement ].count; currentElementCount ++ ) { + + result = binaryReadElement( body, loc, header.elements[ currentElement ].properties, little_endian ); + loc += result[ 1 ]; + const element = result[ 0 ]; + + handleElement( buffer, header.elements[ currentElement ].name, element ); + + } + + } + + return postProcess( buffer ); + + } + + // + + let geometry; + const scope = this; + + if ( data instanceof ArrayBuffer ) { + + const text = LoaderUtils.decodeText( new Uint8Array( data ) ); + const header = parseHeader( text ); + + geometry = header.format === 'ascii' ? parseASCII( text, header ) : parseBinary( data, header ); + + } else { + + geometry = parseASCII( data, parseHeader( data ) ); + + } + + return geometry; + + } + +} + +export { PLYLoader }; diff --git a/jsm/loaders/PRWMLoader.js b/jsm/loaders/PRWMLoader.js new file mode 100644 index 0000000..1eb2268 --- /dev/null +++ b/jsm/loaders/PRWMLoader.js @@ -0,0 +1,299 @@ +import { + BufferAttribute, + BufferGeometry, + FileLoader, + Loader +} from 'three'; + +/** + * See https://github.com/kchapelier/PRWM for more informations about this file format + */ + +let bigEndianPlatform = null; + +/** + * Check if the endianness of the platform is big-endian (most significant bit first) + * @returns {boolean} True if big-endian, false if little-endian + */ +function isBigEndianPlatform() { + + if ( bigEndianPlatform === null ) { + + const buffer = new ArrayBuffer( 2 ), + uint8Array = new Uint8Array( buffer ), + uint16Array = new Uint16Array( buffer ); + + uint8Array[ 0 ] = 0xAA; // set first byte + uint8Array[ 1 ] = 0xBB; // set second byte + bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB ); + + } + + return bigEndianPlatform; + +} + +// match the values defined in the spec to the TypedArray types +const InvertedEncodingTypes = [ + null, + Float32Array, + null, + Int8Array, + Int16Array, + null, + Int32Array, + Uint8Array, + Uint16Array, + null, + Uint32Array +]; + +// define the method to use on a DataView, corresponding the TypedArray type +const getMethods = { + Uint16Array: 'getUint16', + Uint32Array: 'getUint32', + Int16Array: 'getInt16', + Int32Array: 'getInt32', + Float32Array: 'getFloat32', + Float64Array: 'getFloat64' +}; + + +function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) { + + const bytesPerElement = viewType.BYTES_PER_ELEMENT; + let result; + + if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) { + + result = new viewType( sourceArrayBuffer, position, length ); + + } else { + + const readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ), + getMethod = getMethods[ viewType.name ], + littleEndian = ! fromBigEndian; + + result = new viewType( length ); + + for ( let i = 0; i < length; i ++ ) { + + result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian ); + + } + + } + + return result; + +} + + +function decodePrwm( buffer ) { + + const array = new Uint8Array( buffer ), + version = array[ 0 ]; + + let flags = array[ 1 ]; + + const indexedGeometry = !! ( flags >> 7 & 0x01 ), + indicesType = flags >> 6 & 0x01, + bigEndian = ( flags >> 5 & 0x01 ) === 1, + attributesNumber = flags & 0x1F; + + let valuesNumber = 0, + indicesNumber = 0; + + if ( bigEndian ) { + + valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ]; + indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ]; + + } else { + + valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 ); + indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 ); + + } + + /** PRELIMINARY CHECKS **/ + + if ( version === 0 ) { + + throw new Error( 'PRWM decoder: Invalid format version: 0' ); + + } else if ( version !== 1 ) { + + throw new Error( 'PRWM decoder: Unsupported format version: ' + version ); + + } + + if ( ! indexedGeometry ) { + + if ( indicesType !== 0 ) { + + throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' ); + + } else if ( indicesNumber !== 0 ) { + + throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' ); + + } + + } + + /** PARSING **/ + + let pos = 8; + + const attributes = {}; + + for ( let i = 0; i < attributesNumber; i ++ ) { + + let attributeName = ''; + + while ( pos < array.length ) { + + const char = array[ pos ]; + pos ++; + + if ( char === 0 ) { + + break; + + } else { + + attributeName += String.fromCharCode( char ); + + } + + } + + flags = array[ pos ]; + + const attributeType = flags >> 7 & 0x01; + const cardinality = ( flags >> 4 & 0x03 ) + 1; + const encodingType = flags & 0x0F; + const arrayType = InvertedEncodingTypes[ encodingType ]; + + pos ++; + + // padding to next multiple of 4 + pos = Math.ceil( pos / 4 ) * 4; + + const values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian ); + + pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber; + + attributes[ attributeName ] = { + type: attributeType, + cardinality: cardinality, + values: values + }; + + } + + pos = Math.ceil( pos / 4 ) * 4; + + let indices = null; + + if ( indexedGeometry ) { + + indices = copyFromBuffer( + buffer, + indicesType === 1 ? Uint32Array : Uint16Array, + pos, + indicesNumber, + bigEndian + ); + + } + + return { + version: version, + attributes: attributes, + indices: indices + }; + +} + +// Define the public interface + +class PRWMLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + + url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' ); + + loader.load( url, function ( arrayBuffer ) { + + try { + + onLoad( scope.parse( arrayBuffer ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( arrayBuffer ) { + + const data = decodePrwm( arrayBuffer ), + attributesKey = Object.keys( data.attributes ), + bufferGeometry = new BufferGeometry(); + + for ( let i = 0; i < attributesKey.length; i ++ ) { + + const attribute = data.attributes[ attributesKey[ i ] ]; + bufferGeometry.setAttribute( attributesKey[ i ], new BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) ); + + } + + if ( data.indices !== null ) { + + bufferGeometry.setIndex( new BufferAttribute( data.indices, 1 ) ); + + } + + return bufferGeometry; + + } + + static isBigEndianPlatform() { + + return isBigEndianPlatform(); + + } + +} + +export { PRWMLoader }; diff --git a/jsm/loaders/PVRLoader.js b/jsm/loaders/PVRLoader.js new file mode 100644 index 0000000..f39f8e7 --- /dev/null +++ b/jsm/loaders/PVRLoader.js @@ -0,0 +1,251 @@ +import { + CompressedTextureLoader, + RGBA_PVRTC_2BPPV1_Format, + RGBA_PVRTC_4BPPV1_Format, + RGB_PVRTC_2BPPV1_Format, + RGB_PVRTC_4BPPV1_Format +} from 'three'; + +/* + * PVR v2 (legacy) parser + * TODO : Add Support for PVR v3 format + * TODO : implement loadMipmaps option + */ + +class PVRLoader extends CompressedTextureLoader { + + constructor( manager ) { + + super( manager ); + + } + + parse( buffer, loadMipmaps ) { + + const headerLengthInt = 13; + const header = new Uint32Array( buffer, 0, headerLengthInt ); + + const pvrDatas = { + buffer: buffer, + header: header, + loadMipmaps: loadMipmaps + }; + + if ( header[ 0 ] === 0x03525650 ) { + + // PVR v3 + + return _parseV3( pvrDatas ); + + } else if ( header[ 11 ] === 0x21525650 ) { + + // PVR v2 + + return _parseV2( pvrDatas ); + + } else { + + console.error( 'THREE.PVRLoader: Unknown PVR format.' ); + + } + + } + +} + +function _parseV3( pvrDatas ) { + + const header = pvrDatas.header; + let bpp, format; + + + const metaLen = header[ 12 ], + pixelFormat = header[ 2 ], + height = header[ 6 ], + width = header[ 7 ], + // numSurfs = header[ 9 ], + numFaces = header[ 10 ], + numMipmaps = header[ 11 ]; + + switch ( pixelFormat ) { + + case 0 : // PVRTC 2bpp RGB + bpp = 2; + format = RGB_PVRTC_2BPPV1_Format; + break; + + case 1 : // PVRTC 2bpp RGBA + bpp = 2; + format = RGBA_PVRTC_2BPPV1_Format; + break; + + case 2 : // PVRTC 4bpp RGB + bpp = 4; + format = RGB_PVRTC_4BPPV1_Format; + break; + + case 3 : // PVRTC 4bpp RGBA + bpp = 4; + format = RGBA_PVRTC_4BPPV1_Format; + break; + + default : + console.error( 'THREE.PVRLoader: Unsupported PVR format:', pixelFormat ); + + } + + pvrDatas.dataPtr = 52 + metaLen; + pvrDatas.bpp = bpp; + pvrDatas.format = format; + pvrDatas.width = width; + pvrDatas.height = height; + pvrDatas.numSurfaces = numFaces; + pvrDatas.numMipmaps = numMipmaps; + pvrDatas.isCubemap = ( numFaces === 6 ); + + return _extract( pvrDatas ); + +} + +function _parseV2( pvrDatas ) { + + const header = pvrDatas.header; + + const headerLength = header[ 0 ], + height = header[ 1 ], + width = header[ 2 ], + numMipmaps = header[ 3 ], + flags = header[ 4 ], + // dataLength = header[ 5 ], + // bpp = header[ 6 ], + // bitmaskRed = header[ 7 ], + // bitmaskGreen = header[ 8 ], + // bitmaskBlue = header[ 9 ], + bitmaskAlpha = header[ 10 ], + // pvrTag = header[ 11 ], + numSurfs = header[ 12 ]; + + + const TYPE_MASK = 0xff; + const PVRTC_2 = 24, + PVRTC_4 = 25; + + const formatFlags = flags & TYPE_MASK; + + let bpp, format; + const _hasAlpha = bitmaskAlpha > 0; + + if ( formatFlags === PVRTC_4 ) { + + format = _hasAlpha ? RGBA_PVRTC_4BPPV1_Format : RGB_PVRTC_4BPPV1_Format; + bpp = 4; + + } else if ( formatFlags === PVRTC_2 ) { + + format = _hasAlpha ? RGBA_PVRTC_2BPPV1_Format : RGB_PVRTC_2BPPV1_Format; + bpp = 2; + + } else { + + console.error( 'THREE.PVRLoader: Unknown PVR format:', formatFlags ); + + } + + pvrDatas.dataPtr = headerLength; + pvrDatas.bpp = bpp; + pvrDatas.format = format; + pvrDatas.width = width; + pvrDatas.height = height; + pvrDatas.numSurfaces = numSurfs; + pvrDatas.numMipmaps = numMipmaps + 1; + + // guess cubemap type seems tricky in v2 + // it juste a pvr containing 6 surface (no explicit cubemap type) + pvrDatas.isCubemap = ( numSurfs === 6 ); + + return _extract( pvrDatas ); + +} + + +function _extract( pvrDatas ) { + + const pvr = { + mipmaps: [], + width: pvrDatas.width, + height: pvrDatas.height, + format: pvrDatas.format, + mipmapCount: pvrDatas.numMipmaps, + isCubemap: pvrDatas.isCubemap + }; + + const buffer = pvrDatas.buffer; + + let dataOffset = pvrDatas.dataPtr, + dataSize = 0, + blockSize = 0, + blockWidth = 0, + blockHeight = 0, + widthBlocks = 0, + heightBlocks = 0; + + const bpp = pvrDatas.bpp, + numSurfs = pvrDatas.numSurfaces; + + if ( bpp === 2 ) { + + blockWidth = 8; + blockHeight = 4; + + } else { + + blockWidth = 4; + blockHeight = 4; + + } + + blockSize = ( blockWidth * blockHeight ) * bpp / 8; + + pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs; + + let mipLevel = 0; + + while ( mipLevel < pvrDatas.numMipmaps ) { + + const sWidth = pvrDatas.width >> mipLevel, + sHeight = pvrDatas.height >> mipLevel; + + widthBlocks = sWidth / blockWidth; + heightBlocks = sHeight / blockHeight; + + // Clamp to minimum number of blocks + if ( widthBlocks < 2 ) widthBlocks = 2; + if ( heightBlocks < 2 ) heightBlocks = 2; + + dataSize = widthBlocks * heightBlocks * blockSize; + + for ( let surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) { + + const byteArray = new Uint8Array( buffer, dataOffset, dataSize ); + + const mipmap = { + data: byteArray, + width: sWidth, + height: sHeight + }; + + pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap; + + dataOffset += dataSize; + + } + + mipLevel ++; + + } + + return pvr; + +} + +export { PVRLoader }; diff --git a/jsm/loaders/RGBELoader.js b/jsm/loaders/RGBELoader.js new file mode 100644 index 0000000..b6a8559 --- /dev/null +++ b/jsm/loaders/RGBELoader.js @@ -0,0 +1,476 @@ +import { + DataTextureLoader, + DataUtils, + FloatType, + HalfFloatType, + LinearEncoding, + LinearFilter +} from 'three'; + +// https://github.com/mrdoob/three.js/issues/5552 +// http://en.wikipedia.org/wiki/RGBE_image_format + +class RGBELoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + + } + + // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html + + parse( buffer ) { + + const + /* return codes for rgbe routines */ + //RGBE_RETURN_SUCCESS = 0, + RGBE_RETURN_FAILURE = - 1, + + /* default error routine. change this to change error handling */ + rgbe_read_error = 1, + rgbe_write_error = 2, + rgbe_format_error = 3, + rgbe_memory_error = 4, + rgbe_error = function ( rgbe_error_code, msg ) { + + switch ( rgbe_error_code ) { + + case rgbe_read_error: console.error( 'THREE.RGBELoader Read Error: ' + ( msg || '' ) ); + break; + case rgbe_write_error: console.error( 'THREE.RGBELoader Write Error: ' + ( msg || '' ) ); + break; + case rgbe_format_error: console.error( 'THREE.RGBELoader Bad File Format: ' + ( msg || '' ) ); + break; + default: + case rgbe_memory_error: console.error( 'THREE.RGBELoader: Error: ' + ( msg || '' ) ); + + } + + return RGBE_RETURN_FAILURE; + + }, + + /* offsets to red, green, and blue components in a data (float) pixel */ + //RGBE_DATA_RED = 0, + //RGBE_DATA_GREEN = 1, + //RGBE_DATA_BLUE = 2, + + /* number of floats per pixel, use 4 since stored in rgba image format */ + //RGBE_DATA_SIZE = 4, + + /* flags indicating which fields in an rgbe_header_info are valid */ + RGBE_VALID_PROGRAMTYPE = 1, + RGBE_VALID_FORMAT = 2, + RGBE_VALID_DIMENSIONS = 4, + + NEWLINE = '\n', + + fgets = function ( buffer, lineLimit, consume ) { + + const chunkSize = 128; + + lineLimit = ! lineLimit ? 1024 : lineLimit; + let p = buffer.pos, + i = - 1, len = 0, s = '', + chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ); + + while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) { + + s += chunk; len += chunk.length; + p += chunkSize; + chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ); + + } + + if ( - 1 < i ) { + + /*for (i=l-1; i>=0; i--) { + byteCode = m.charCodeAt(i); + if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++; + else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2; + if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate + }*/ + if ( false !== consume ) buffer.pos += len + i + 1; + return s + chunk.slice( 0, i ); + + } + + return false; + + }, + + /* minimal header reading. modify if you want to parse more information */ + RGBE_ReadHeader = function ( buffer ) { + + + // regexes to parse header info fields + const magic_token_re = /^#\?(\S+)/, + gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/, + exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/, + format_re = /^\s*FORMAT=(\S+)\s*$/, + dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/, + + // RGBE format header struct + header = { + + valid: 0, /* indicate which fields are valid */ + + string: '', /* the actual header string */ + + comments: '', /* comments found in header */ + + programtype: 'RGBE', /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */ + + format: '', /* RGBE format, default 32-bit_rle_rgbe */ + + gamma: 1.0, /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */ + + exposure: 1.0, /* a value of 1.0 in an image corresponds to watts/steradian/m^2. defaults to 1.0 */ + + width: 0, height: 0 /* image dimensions, width/height */ + + }; + + let line, match; + + if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) { + + return rgbe_error( rgbe_read_error, 'no header found' ); + + } + + /* if you want to require the magic token then uncomment the next line */ + if ( ! ( match = line.match( magic_token_re ) ) ) { + + return rgbe_error( rgbe_format_error, 'bad initial token' ); + + } + + header.valid |= RGBE_VALID_PROGRAMTYPE; + header.programtype = match[ 1 ]; + header.string += line + '\n'; + + while ( true ) { + + line = fgets( buffer ); + if ( false === line ) break; + header.string += line + '\n'; + + if ( '#' === line.charAt( 0 ) ) { + + header.comments += line + '\n'; + continue; // comment line + + } + + if ( match = line.match( gamma_re ) ) { + + header.gamma = parseFloat( match[ 1 ] ); + + } + + if ( match = line.match( exposure_re ) ) { + + header.exposure = parseFloat( match[ 1 ] ); + + } + + if ( match = line.match( format_re ) ) { + + header.valid |= RGBE_VALID_FORMAT; + header.format = match[ 1 ];//'32-bit_rle_rgbe'; + + } + + if ( match = line.match( dimensions_re ) ) { + + header.valid |= RGBE_VALID_DIMENSIONS; + header.height = parseInt( match[ 1 ], 10 ); + header.width = parseInt( match[ 2 ], 10 ); + + } + + if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break; + + } + + if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) { + + return rgbe_error( rgbe_format_error, 'missing format specifier' ); + + } + + if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) { + + return rgbe_error( rgbe_format_error, 'missing image size specifier' ); + + } + + return header; + + }, + + RGBE_ReadPixels_RLE = function ( buffer, w, h ) { + + const scanline_width = w; + + if ( + // run length encoding is not allowed so read flat + ( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) || + // this file is not run length encoded + ( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) ) + ) { + + // return the flat buffer + return new Uint8Array( buffer ); + + } + + if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) { + + return rgbe_error( rgbe_format_error, 'wrong scanline width' ); + + } + + const data_rgba = new Uint8Array( 4 * w * h ); + + if ( ! data_rgba.length ) { + + return rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' ); + + } + + let offset = 0, pos = 0; + + const ptr_end = 4 * scanline_width; + const rgbeStart = new Uint8Array( 4 ); + const scanline_buffer = new Uint8Array( ptr_end ); + let num_scanlines = h; + + // read in each successive scanline + while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) { + + if ( pos + 4 > buffer.byteLength ) { + + return rgbe_error( rgbe_read_error ); + + } + + rgbeStart[ 0 ] = buffer[ pos ++ ]; + rgbeStart[ 1 ] = buffer[ pos ++ ]; + rgbeStart[ 2 ] = buffer[ pos ++ ]; + rgbeStart[ 3 ] = buffer[ pos ++ ]; + + if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) { + + return rgbe_error( rgbe_format_error, 'bad rgbe scanline format' ); + + } + + // read each of the four channels for the scanline into the buffer + // first red, then green, then blue, then exponent + let ptr = 0, count; + + while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) { + + count = buffer[ pos ++ ]; + const isEncodedRun = count > 128; + if ( isEncodedRun ) count -= 128; + + if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) { + + return rgbe_error( rgbe_format_error, 'bad scanline data' ); + + } + + if ( isEncodedRun ) { + + // a (encoded) run of the same value + const byteValue = buffer[ pos ++ ]; + for ( let i = 0; i < count; i ++ ) { + + scanline_buffer[ ptr ++ ] = byteValue; + + } + //ptr += count; + + } else { + + // a literal-run + scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr ); + ptr += count; pos += count; + + } + + } + + + // now convert data from buffer into rgba + // first red, then green, then blue, then exponent (alpha) + const l = scanline_width; //scanline_buffer.byteLength; + for ( let i = 0; i < l; i ++ ) { + + let off = 0; + data_rgba[ offset ] = scanline_buffer[ i + off ]; + off += scanline_width; //1; + data_rgba[ offset + 1 ] = scanline_buffer[ i + off ]; + off += scanline_width; //1; + data_rgba[ offset + 2 ] = scanline_buffer[ i + off ]; + off += scanline_width; //1; + data_rgba[ offset + 3 ] = scanline_buffer[ i + off ]; + offset += 4; + + } + + num_scanlines --; + + } + + return data_rgba; + + }; + + const RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) { + + const e = sourceArray[ sourceOffset + 3 ]; + const scale = Math.pow( 2.0, e - 128.0 ) / 255.0; + + destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale; + destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale; + destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale; + destArray[ destOffset + 3 ] = 1; + + }; + + const RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) { + + const e = sourceArray[ sourceOffset + 3 ]; + const scale = Math.pow( 2.0, e - 128.0 ) / 255.0; + + // clamping to 65504, the maximum representable value in float16 + destArray[ destOffset + 0 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 0 ] * scale, 65504 ) ); + destArray[ destOffset + 1 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 1 ] * scale, 65504 ) ); + destArray[ destOffset + 2 ] = DataUtils.toHalfFloat( Math.min( sourceArray[ sourceOffset + 2 ] * scale, 65504 ) ); + destArray[ destOffset + 3 ] = DataUtils.toHalfFloat( 1 ); + + }; + + const byteArray = new Uint8Array( buffer ); + byteArray.pos = 0; + const rgbe_header_info = RGBE_ReadHeader( byteArray ); + + if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) { + + const w = rgbe_header_info.width, + h = rgbe_header_info.height, + image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h ); + + if ( RGBE_RETURN_FAILURE !== image_rgba_data ) { + + let data, format, type; + let numElements; + + switch ( this.type ) { + + case FloatType: + + numElements = image_rgba_data.length / 4; + const floatArray = new Float32Array( numElements * 4 ); + + for ( let j = 0; j < numElements; j ++ ) { + + RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 4 ); + + } + + data = floatArray; + type = FloatType; + break; + + case HalfFloatType: + + numElements = image_rgba_data.length / 4; + const halfArray = new Uint16Array( numElements * 4 ); + + for ( let j = 0; j < numElements; j ++ ) { + + RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 4 ); + + } + + data = halfArray; + type = HalfFloatType; + break; + + default: + + console.error( 'THREE.RGBELoader: unsupported type: ', this.type ); + break; + + } + + return { + width: w, height: h, + data: data, + header: rgbe_header_info.string, + gamma: rgbe_header_info.gamma, + exposure: rgbe_header_info.exposure, + format: format, + type: type + }; + + } + + } + + return null; + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + + load( url, onLoad, onProgress, onError ) { + + function onLoadCallback( texture, texData ) { + + switch ( texture.type ) { + + case FloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.flipY = true; + break; + + case HalfFloatType: + + texture.encoding = LinearEncoding; + texture.minFilter = LinearFilter; + texture.magFilter = LinearFilter; + texture.generateMipmaps = false; + texture.flipY = true; + break; + + } + + if ( onLoad ) onLoad( texture, texData ); + + } + + return super.load( url, onLoadCallback, onProgress, onError ); + + } + +} + +export { RGBELoader }; diff --git a/jsm/loaders/RGBMLoader.js b/jsm/loaders/RGBMLoader.js new file mode 100644 index 0000000..44420c4 --- /dev/null +++ b/jsm/loaders/RGBMLoader.js @@ -0,0 +1,1065 @@ +import { + DataTextureLoader, + RGBAFormat, + LinearFilter, + CubeTexture, + HalfFloatType, + DataUtils +} from 'three'; + +class RGBMLoader extends DataTextureLoader { + + constructor( manager ) { + + super( manager ); + + this.type = HalfFloatType; + this.maxRange = 7; // more information about this property at https://iwasbeingirony.blogspot.com/2010/06/difference-between-rgbm-and-rgbd.html + + } + + setDataType( value ) { + + this.type = value; + return this; + + } + + setMaxRange( value ) { + + this.maxRange = value; + return this; + + } + + loadCubemap( urls, onLoad, onProgress, onError ) { + + const texture = new CubeTexture(); + + let loaded = 0; + + const scope = this; + + function loadTexture( i ) { + + scope.load( urls[ i ], function ( image ) { + + texture.images[ i ] = image; + + loaded ++; + + if ( loaded === 6 ) { + + texture.needsUpdate = true; + + if ( onLoad ) onLoad( texture ); + + } + + }, undefined, onError ); + + } + + for ( let i = 0; i < urls.length; ++ i ) { + + loadTexture( i ); + + } + + texture.type = this.type; + texture.format = RGBAFormat; + texture.minFilter = LinearFilter; + texture.generateMipmaps = false; + + return texture; + + } + + 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 ); + + // decode RGBM + + 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 ( 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; + + } + + } + + 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 ( 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 ]; + + } + + return frms; + +}; + +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 ]; + + } + + 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 ( 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; + + } + + 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 + + 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 == 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; + + } + + } + + 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 == 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 ]; + + } + + 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 ]; + + } + + } 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; + + } + 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; + + } + + } + + } + + //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; + + } + + 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 ); + + } + + 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 ( 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 ); + + } + + 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 + ; + + } + + 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; + + } + + 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 ) + ; + + } + + 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 ) + ; + + }; + + 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; + + }; + + 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 ++; + + } + + } + + } + + 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 ++; + + } + + while ( A < l ) { + + V[ A << 1 ] = 0; V[ ( A << 1 ) + 1 ] = 0; A ++; + + } + + 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; + + } + + for ( l = 0; l < V; l += 2 ) { + + I = N[ l + 1 ]; if ( I != 0 ) { + + N[ l ] = b[ I ]; + b[ I ] ++ + ; + + } + + } + + }; + + 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 ++; + + } + + } + + }; + + 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; + + } + + }; + + H.H.M = function ( N, W, R ) { + + R = R << ( W & 7 ); var V = W >>> 3; N[ V ] |= R; N[ V + 1 ] |= R >>> 8; + + }; + + 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; + + }; + + H.H.e = function ( N, W, R ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1; + + }; + + 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; + + }; + + H.H.Z = function ( N, W ) { + + return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ); + + }; + + 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 ); + + }; + + 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 + ; + + } + + function n( A, l, M ) { + + while ( l -- != 0 )A.push( 0, M ) + ; + + } + + 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 ] + ; + + } + + 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 + ; + +}(); + + +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 ++; + + } + + 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 ) ) ); + + } + + 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; + + } + + if ( sw * sh != 0 ) di += sh * ( 1 + bpll ); + pass = pass + 1; + + } + + 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 ] ) ); + + } + + } + + return data; + +}; + +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; + +}; + +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 ++; + +}; + +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; + + } + + 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 true; + +}; + +export { RGBMLoader }; diff --git a/jsm/loaders/STLLoader.js b/jsm/loaders/STLLoader.js new file mode 100644 index 0000000..b3981df --- /dev/null +++ b/jsm/loaders/STLLoader.js @@ -0,0 +1,399 @@ +import { + BufferAttribute, + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + LoaderUtils, + Vector3 +} from 'three'; + +/** + * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. + * + * Supports both binary and ASCII encoded files, with automatic detection of type. + * + * The loader returns a non-indexed buffer geometry. + * + * Limitations: + * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). + * There is perhaps some question as to how valid it is to always assume little-endian-ness. + * ASCII decoding assumes file is UTF-8. + * + * Usage: + * const loader = new STLLoader(); + * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { + * scene.add( new THREE.Mesh( geometry ) ); + * }); + * + * For binary STLs geometry might contain colors for vertices. To use it: + * // use the same code to load STL as above + * if (geometry.hasColors) { + * material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true }); + * } else { .... } + * const mesh = new THREE.Mesh( geometry, material ); + * + * For ASCII STLs containing multiple solids, each solid is assigned to a different group. + * Groups can be used to assign a different color by defining an array of materials with the same length of + * geometry.groups and passing it to the Mesh constructor: + * + * const mesh = new THREE.Mesh( geometry, material ); + * + * For example: + * + * const materials = []; + * const nGeometryGroups = geometry.groups.length; + * + * const colorMap = ...; // Some logic to index colors. + * + * for (let i = 0; i < nGeometryGroups; i++) { + * + * const material = new THREE.MeshPhongMaterial({ + * color: colorMap[i], + * wireframe: false + * }); + * + * } + * + * materials.push(material); + * const mesh = new THREE.Mesh(geometry, materials); + */ + + +class STLLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( this.manager ); + loader.setPath( this.path ); + loader.setResponseType( 'arraybuffer' ); + loader.setRequestHeader( this.requestHeader ); + loader.setWithCredentials( this.withCredentials ); + + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( data ) { + + function isBinary( data ) { + + const reader = new DataView( data ); + const face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 ); + const n_faces = reader.getUint32( 80, true ); + const expect = 80 + ( 32 / 8 ) + ( n_faces * face_size ); + + if ( expect === reader.byteLength ) { + + return true; + + } + + // An ASCII STL data must begin with 'solid ' as the first six bytes. + // However, ASCII STLs lacking the SPACE after the 'd' are known to be + // plentiful. So, check the first 5 bytes for 'solid'. + + // Several encodings, such as UTF-8, precede the text with up to 5 bytes: + // https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding + // Search for "solid" to start anywhere after those prefixes. + + // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd' + + const solid = [ 115, 111, 108, 105, 100 ]; + + for ( let off = 0; off < 5; off ++ ) { + + // If "solid" text is matched to the current offset, declare it to be an ASCII STL. + + if ( matchDataViewAt( solid, reader, off ) ) return false; + + } + + // Couldn't find "solid" text at the beginning; it is binary STL. + + return true; + + } + + function matchDataViewAt( query, reader, offset ) { + + // Check if each byte in query matches the corresponding byte from the current offset + + for ( let i = 0, il = query.length; i < il; i ++ ) { + + if ( query[ i ] !== reader.getUint8( offset + i ) ) return false; + + } + + return true; + + } + + function parseBinary( data ) { + + const reader = new DataView( data ); + const faces = reader.getUint32( 80, true ); + + let r, g, b, hasColors = false, colors; + let defaultR, defaultG, defaultB, alpha; + + // process STL header + // check for default color in header ("COLOR=rgba" sequence). + + for ( let index = 0; index < 80 - 10; index ++ ) { + + if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) && + ( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) && + ( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) { + + hasColors = true; + colors = new Float32Array( faces * 3 * 3 ); + + defaultR = reader.getUint8( index + 6 ) / 255; + defaultG = reader.getUint8( index + 7 ) / 255; + defaultB = reader.getUint8( index + 8 ) / 255; + alpha = reader.getUint8( index + 9 ) / 255; + + } + + } + + const dataOffset = 84; + const faceLength = 12 * 4 + 2; + + const geometry = new BufferGeometry(); + + const vertices = new Float32Array( faces * 3 * 3 ); + const normals = new Float32Array( faces * 3 * 3 ); + + for ( let face = 0; face < faces; face ++ ) { + + const start = dataOffset + face * faceLength; + const normalX = reader.getFloat32( start, true ); + const normalY = reader.getFloat32( start + 4, true ); + const normalZ = reader.getFloat32( start + 8, true ); + + if ( hasColors ) { + + const packedColor = reader.getUint16( start + 48, true ); + + if ( ( packedColor & 0x8000 ) === 0 ) { + + // facet has its own unique color + + r = ( packedColor & 0x1F ) / 31; + g = ( ( packedColor >> 5 ) & 0x1F ) / 31; + b = ( ( packedColor >> 10 ) & 0x1F ) / 31; + + } else { + + r = defaultR; + g = defaultG; + b = defaultB; + + } + + } + + for ( let i = 1; i <= 3; i ++ ) { + + const vertexstart = start + i * 12; + const componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 ); + + vertices[ componentIdx ] = reader.getFloat32( vertexstart, true ); + vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true ); + vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true ); + + normals[ componentIdx ] = normalX; + normals[ componentIdx + 1 ] = normalY; + normals[ componentIdx + 2 ] = normalZ; + + if ( hasColors ) { + + colors[ componentIdx ] = r; + colors[ componentIdx + 1 ] = g; + colors[ componentIdx + 2 ] = b; + + } + + } + + } + + geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'normal', new BufferAttribute( normals, 3 ) ); + + if ( hasColors ) { + + geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) ); + geometry.hasColors = true; + geometry.alpha = alpha; + + } + + return geometry; + + } + + function parseASCII( data ) { + + const geometry = new BufferGeometry(); + const patternSolid = /solid([\s\S]*?)endsolid/g; + const patternFace = /facet([\s\S]*?)endfacet/g; + let faceCounter = 0; + + const patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; + const patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' ); + const patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' ); + + const vertices = []; + const normals = []; + + const normal = new Vector3(); + + let result; + + let groupCount = 0; + let startVertex = 0; + let endVertex = 0; + + while ( ( result = patternSolid.exec( data ) ) !== null ) { + + startVertex = endVertex; + + const solid = result[ 0 ]; + + while ( ( result = patternFace.exec( solid ) ) !== null ) { + + let vertexCountPerFace = 0; + let normalCountPerFace = 0; + + const text = result[ 0 ]; + + while ( ( result = patternNormal.exec( text ) ) !== null ) { + + normal.x = parseFloat( result[ 1 ] ); + normal.y = parseFloat( result[ 2 ] ); + normal.z = parseFloat( result[ 3 ] ); + normalCountPerFace ++; + + } + + while ( ( result = patternVertex.exec( text ) ) !== null ) { + + vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) ); + normals.push( normal.x, normal.y, normal.z ); + vertexCountPerFace ++; + endVertex ++; + + } + + // every face have to own ONE valid normal + + if ( normalCountPerFace !== 1 ) { + + console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter ); + + } + + // each face have to own THREE valid vertices + + if ( vertexCountPerFace !== 3 ) { + + console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter ); + + } + + faceCounter ++; + + } + + const start = startVertex; + const count = endVertex - startVertex; + + geometry.addGroup( start, count, groupCount ); + groupCount ++; + + } + + geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) ); + geometry.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) ); + + return geometry; + + } + + function ensureString( buffer ) { + + if ( typeof buffer !== 'string' ) { + + return LoaderUtils.decodeText( new Uint8Array( buffer ) ); + + } + + return buffer; + + } + + function ensureBinary( buffer ) { + + if ( typeof buffer === 'string' ) { + + const array_buffer = new Uint8Array( buffer.length ); + for ( let i = 0; i < buffer.length; i ++ ) { + + array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian + + } + + return array_buffer.buffer || array_buffer; + + } else { + + return buffer; + + } + + } + + // start + + const binData = ensureBinary( data ); + + return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) ); + + } + +} + +export { STLLoader }; diff --git a/jsm/loaders/SVGLoader.js b/jsm/loaders/SVGLoader.js new file mode 100644 index 0000000..f754b27 --- /dev/null +++ b/jsm/loaders/SVGLoader.js @@ -0,0 +1,2949 @@ +import { + Box2, + BufferGeometry, + FileLoader, + Float32BufferAttribute, + Loader, + Matrix3, + Path, + Shape, + ShapePath, + ShapeUtils, + Vector2, + Vector3 +} from 'three'; + +class SVGLoader extends Loader { + + constructor( manager ) { + + super( manager ); + + // Default dots per inch + this.defaultDPI = 90; + + // Accepted units: 'mm', 'cm', 'in', 'pt', 'pc', 'px' + this.defaultUnit = 'px'; + + } + + load( url, onLoad, onProgress, onError ) { + + const scope = this; + + const loader = new FileLoader( scope.manager ); + loader.setPath( scope.path ); + loader.setRequestHeader( scope.requestHeader ); + loader.setWithCredentials( scope.withCredentials ); + loader.load( url, function ( text ) { + + try { + + onLoad( scope.parse( text ) ); + + } catch ( e ) { + + if ( onError ) { + + onError( e ); + + } else { + + console.error( e ); + + } + + scope.manager.itemError( url ); + + } + + }, onProgress, onError ); + + } + + parse( text ) { + + const scope = this; + + function parseNode( node, style ) { + + if ( node.nodeType !== 1 ) return; + + const transform = getNodeTransform( node ); + + let isDefsNode = false; + + let path = null; + + switch ( node.nodeName ) { + + case 'svg': + break; + + case 'style': + parseCSSStylesheet( node ); + break; + + case 'g': + style = parseStyle( node, style ); + break; + + case 'path': + style = parseStyle( node, style ); + if ( node.hasAttribute( 'd' ) ) path = parsePathNode( node ); + break; + + case 'rect': + style = parseStyle( node, style ); + path = parseRectNode( node ); + break; + + case 'polygon': + style = parseStyle( node, style ); + path = parsePolygonNode( node ); + break; + + case 'polyline': + style = parseStyle( node, style ); + path = parsePolylineNode( node ); + break; + + case 'circle': + style = parseStyle( node, style ); + path = parseCircleNode( node ); + break; + + case 'ellipse': + style = parseStyle( node, style ); + path = parseEllipseNode( node ); + break; + + case 'line': + style = parseStyle( node, style ); + path = parseLineNode( node ); + break; + + case 'defs': + isDefsNode = true; + break; + + case 'use': + style = parseStyle( node, style ); + + const href = node.getAttributeNS( 'http://www.w3.org/1999/xlink', 'href' ) || ''; + const usedNodeId = href.substring( 1 ); + const usedNode = node.viewportElement.getElementById( usedNodeId ); + if ( usedNode ) { + + parseNode( usedNode, style ); + + } else { + + console.warn( 'SVGLoader: \'use node\' references non-existent node id: ' + usedNodeId ); + + } + + break; + + default: + // console.log( node ); + + } + + if ( path ) { + + if ( style.fill !== undefined && style.fill !== 'none' ) { + + path.color.setStyle( style.fill ); + + } + + transformPath( path, currentTransform ); + + paths.push( path ); + + path.userData = { node: node, style: style }; + + } + + const childNodes = node.childNodes; + + for ( let i = 0; i < childNodes.length; i ++ ) { + + const node = childNodes[ i ]; + + if ( isDefsNode && node.nodeName !== 'style' && node.nodeName !== 'defs' ) { + + // Ignore everything in defs except CSS style definitions + // and nested defs, because it is OK by the standard to have + //