当前位置: 首页 > news >正文

延庆网站制作织梦是怎么做网站

延庆网站制作,织梦是怎么做网站,接网站做项目赚钱吗,河南网站建设公司哪家好前言 Web脚本语言JavaScript入门容易#xff0c;但是想要熟练掌握却需要几年的学习与实践#xff0c;还要在弱类型开发语言中习惯于使用模块来构建你的代码#xff0c;就像小时候玩的乐高积木一样。 应用程序的模块化理念#xff0c;通过将实现隐藏在一个简单的接口后面但是想要熟练掌握却需要几年的学习与实践还要在弱类型开发语言中习惯于使用模块来构建你的代码就像小时候玩的乐高积木一样。 应用程序的模块化理念通过将实现隐藏在一个简单的接口后面您可以使您的应用程序万无一失且易于使用。它只做它应该做的没有别的。 通过隐藏实现我们对使用我们代码的人实施了良好的编码风格。您可以访问的实现越多它就越有可能成为您以后必须处理的复杂的半生不熟的“修复”。 创建3D场景时唯一的限制是您的想象力 - 以及您的技术知识深度。 描述3D空间的坐标系和用于在坐标系内移动对象是难点加重点。场景图用于描述构成我们场景的对象层次结构的结构向量用于描述3D空间中的位置以及许多其他事物 还有不少于两种描述旋转的方式欧拉角Euler angles和四元数quaternions。 对 three.js 和乐高模型web化相关知识点进行实战。希望能与大家交流技术心得和经验一起共同进步。涉及的知识点如下 3D 场景初始化场景、相机、渲染器 透视相机的位置调整 几何体BoxGeometry、CylinderGeometry、LatheGeometry 材质MeshLambertMaterial、MeshPhongMaterial、MeshBasicMaterial 光源AmbientLight、SpotLightHelper、DirectionalLight 更新材质的纹理TextureLoader 渲染 3D 文本TextGeometry、FontLoader 实现物体阴影效果 3D 坐标的计算 物体交互的实现Raycaster、坐标归一化 3D 资源的销毁释放 补间动画、动画编排 class 等 为了方便demo演示采用传统的 HTML 单文件importmap、module方式来编写代码。 实践 容器 首先准备一个空白容器让它的尺寸与浏览器视窗大小相同以充分利用屏幕空间。 div idscene-container/div 依赖 对于 JS 脚本使用 导入映射 配置资源的 CDN 地址这样就可以像使用 npm 包一样导入相关资源。 script typeimportmap{imports: {three: https://cdn.jsdelivr.net/npm/three0.162.0/esm,three/addons/: https://cdn.jsdelivr.net/npm/three0.162.0/examples/jsm/,lil-gui: https://threejsfundamentals.org/3rdparty/dat.gui.module.js,tweenjs/tween.js: https://cdn.jsdelivr.net/npm/tweenjs/tween.js23.1.1/dist/tween.esm.js,canvas-confetti: https://cdn.jsdelivr.net/npm/canvas-confetti1.9.2/esm}}/script 接着就可以引入依赖。 script typemoduleimport * as THREE from three;import * as TWEEN from tweenjs/tween.js;import confetti from canvas-confetti;import { GUI } from lil-gui; /script 设计变量、类、方法 定义相关变量 let container, progressBarDiv; let camera, scene, renderer, controls, gui, guiData, anLoop; let model; const modelFileList {Car: ./car.txt} 设计乐高类 class Ldraw {constructor(){// 首次使用构造器实例if (!(Ldraw.instance instanceof Ldraw)) {this.init();}return Ldraw.instance}init() {//container document.createElement( div );//document.body.appendChild( container );camera new THREE.PerspectiveCamera( 45, container.clientWidth / container.clientHeight, 1, 10000 );camera.position.set( 150, 200, 250 );// rendererrenderer new THREE.WebGLRenderer( { antialias: true } );//renderer.setSize( window.innerWidth, window.innerHeight );renderer.setSize(container.clientWidth, container.clientHeight);// eslint-disable-next-line no-undefrenderer.setPixelRatio(window.devicePixelRatio);renderer.toneMapping THREE.ACESFilmicToneMapping;// canvas画布绝对定位//renderer.domElement.style.display black;//renderer.domElement.style.position absolute;//renderer.domElement.style.top 0px;//renderer.domElement.style.left 0px;//renderer.domElement.style.zIndex -1;container.appendChild( renderer.domElement );// sceneconst pmremGenerator new THREE.PMREMGenerator( renderer );scene new THREE.Scene();scene.background new THREE.Color( 0xdeebed );scene.environment pmremGenerator.fromScene( new RoomEnvironment( renderer ) ).texture;controls new OrbitControls( camera, renderer.domElement );controls.enableDamping true;anLoop new Loop(camera, scene, renderer);// guiguiData {//modelFileName: modelFileList[ Car ],displayLines: true,conditionalLines: true,smoothNormals: true,buildingStep: 0,noBuildingSteps: No steps.,flatColors: false,mergeModel: false};window.addEventListener( resize, this.onWindowResize );progressBarDiv document.createElement( div );progressBarDiv.innerText Loading...;progressBarDiv.style.fontSize 3em;progressBarDiv.style.color #888;progressBarDiv.style.display block;progressBarDiv.style.position absolute;progressBarDiv.style.top 50%;progressBarDiv.style.width 100%;progressBarDiv.style.textAlign center;// load materials and then the modelthis.reloadObject( true );}updateObjectsVisibility() {model.traverse( c {if ( c.isLineSegments ) {if ( c.isConditionalLine ) {c.visible guiData.conditionalLines;} else {c.visible guiData.displayLines;}} else if ( c.isGroup ) {// Hide objects with building step gui settingc.visible c.userData.buildingStep guiData.buildingStep;}} );}reloadObject( resetCamera ) {if ( model ) {scene.remove( model );}model null;this.updateProgressBar( 0 );this.showProgressBar;// only smooth when not rendering with flat colors to improve processing timeconst lDrawLoader new LDrawLoader();lDrawLoader.smoothNormals guiData.smoothNormals ! guiData.flatColors;lDrawLoader.load( ./car.txt, ( group2 ) {//.setPath( ldrawPath )//.load( guiData.modelFileName, ( group2 ) {if ( model ) {scene.remove( model );}model group2;// demonstrate how to use convert to flat colors to better mimic the lego instructions lookif ( guiData.flatColors ) {const convertMaterial ( material ) {const newMaterial new THREE.MeshBasicMaterial();newMaterial.color.copy( material.color );newMaterial.polygonOffset material.polygonOffset;newMaterial.polygonOffsetUnits material.polygonOffsetUnits;newMaterial.polygonOffsetFactor material.polygonOffsetFactor;newMaterial.opacity material.opacity;newMaterial.transparent material.transparent;newMaterial.depthWrite material.depthWrite;newMaterial.toneMapping false;return newMaterial;}model.traverse( c {if ( c.isMesh ) {if ( Array.isArray( c.material ) ) {c.material c.material.map( convertMaterial );} else {c.material convertMaterial( c.material );}}} );}// Merge model geometries by materialif ( guiData.mergeModel ) model LDrawUtils.mergeObject( model );// Convert from LDraw coordinates: rotate 180 degrees around OXmodel.rotation.x Math.PI;scene.add( model );guiData.buildingStep model.userData.numBuildingSteps - 1;this.updateObjectsVisibility;// Adjust camera and lightconst bbox new THREE.Box3().setFromObject( model );const size bbox.getSize( new THREE.Vector3() );const radius Math.max( size.x, Math.max( size.y, size.z ) ) * 0.5;if ( resetCamera ) {controls.target0.copy( bbox.getCenter( new THREE.Vector3() ) );controls.position0.set( - 2.3, 1, 2 ).multiplyScalar( radius ).add( controls.target0 );controls.reset();}this.createGUI;this.hideProgressBar;}, this.onProgress, this.onError );//});}onWindowResize() {camera.aspect window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );}createGUI() {if ( gui ) {gui.destroy();}gui new GUI();gui.add( guiData, modelFileName, modelFileList ).name( Model ).onFinishChange( () {this.reloadObject( true );} );gui.add( guiData, flatColors ).name( Flat Colors ).onChange( () {this.reloadObject( false );} );gui.add( guiData, mergeModel ).name( Merge model ).onChange( () {this.reloadObject( false );} );if ( model.userData.numBuildingSteps 1 ) {gui.add( guiData, buildingStep, 0, model.userData.numBuildingSteps - 1 ).step( 1 ).name( Building step ).onChange( this.updateObjectsVisibility );} else {gui.add( guiData, noBuildingSteps ).name( Building step ).onChange( this.updateObjectsVisibility );}const changeNormals () {this.reloadObject( false );} gui.add( guiData, smoothNormals ).name( Smooth Normals ).onChange( changeNormals );gui.add( guiData, displayLines ).name( Display Lines ).onChange( this.updateObjectsVisibility );gui.add( guiData, conditionalLines ).name( Conditional Lines ).onChange( this.updateObjectsVisibility );}animate() {requestAnimationFrame( this.animate );controls.update();this.render;}render() {renderer.render( scene, camera );}updateProgressBar( fraction ) {progressBarDiv.innerText Loading... Math.round( fraction * 100, 2 ) %;}onProgress( xhr ) {if ( xhr.lengthComputable ) {this.updateProgressBar( xhr.loaded / xhr.total );console.log( Math.round( xhr.loaded / xhr.total * 100, 2 ) % downloaded );}}onError( error ) {const message Error loading model;progressBarDiv.innerText message;console.log( message );console.error( error );}showProgressBar() {document.body.appendChild( progressBarDiv );}hideProgressBar() {document.body.removeChild( progressBarDiv );}start() {anLoop.start();}stop() {anLoop.stop();}tick() {// Code to update animations will go hereanLoop.tick();}}//export { Ldraw } 创建一个场景Scene、一个透视相机PerspectiveCamera和一个 WebGL 渲染器WebGLRenderer并将渲染器添加到 DOM 中。同时编写一个渲染函数使用requestAnimationFrame 方法循环渲染场景。 import {EventDispatcher,MOUSE,Quaternion,Spherical,TOUCH,Plane,Ray,MathUtils,BackSide,BoxGeometry,Mesh,Scene,MeshBasicMaterial,MeshStandardMaterial,PointLight,BufferAttribute,BufferGeometry,FileLoader,Group,LineBasicMaterial,LineSegments,Loader,ShaderMaterial,SRGBColorSpace,UniformsLib,UniformsUtils,Clock,Color,Matrix3,Matrix4,PerspectiveCamera,Vector2,Vector3,Vector4,WebGLRenderTarget,HalfFloatType,Float32BufferAttribute,InstancedBufferAttribute,InterleavedBuffer,InterleavedBufferAttribute,TriangleFanDrawMode,TriangleStripDrawMode,TrianglesDrawMode,} from three;// OrbitControls 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 moveconst _changeEvent { type: change };const _startEvent { type: start };const _endEvent { type: end };const _ray new Ray();const _plane new Plane();const TILT_LIMIT Math.cos( 70 * MathUtils.DEG2RAD );class OrbitControls extends EventDispatcher {constructor( object, domElement ) {super();this.object object;this.domElement domElement;this.domElement.style.touchAction none; // disable touch scroll// Set to false to disable this controlthis.enabled true;// target sets the location of focus, where the object orbits aroundthis.target new Vector3();// Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effectthis.cursor 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;// Limit camera target within a spherical area around the cursorthis.minTargetRadius 0;this.maxTargetRadius Infinity;// How far you can orbit vertically, upper and lower limits.// Range is 0 to Math.PI radians.this.minPolarAngle 0; // radiansthis.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; // radiansthis.maxAzimuthAngle Infinity; // radians// Set to true to enable damping (inertia)// If damping is enabled, you must call controls.update() in your animation loopthis.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 zoomingthis.enableZoom true;this.zoomSpeed 1.0;// Set to false to disable rotatingthis.enableRotate true;this.rotateSpeed 1.0;// Set to false to disable panningthis.enablePan true;this.panSpeed 1.0;this.screenSpacePanning true; // if false, pan orthogonal to world-space direction camera.upthis.keyPanSpeed 7.0; // pixels moved per arrow key pushthis.zoomToCursor false;// Set to true to automatically rotate around the target// If auto-rotate is enabled, you must call controls.update() in your animation loopthis.autoRotate false;this.autoRotateSpeed 2.0; // 30 seconds per orbit when fps is 60// The four arrow keysthis.keys { LEFT: ArrowLeft, UP: ArrowUp, RIGHT: ArrowRight, BOTTOM: ArrowDown };// Mouse buttonsthis.mouseButtons { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };// Touch fingersthis.touches { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };// for resetthis.target0 this.target.clone();this.position0 this.object.position.clone();this.zoom0 this.object.zoom;// the target DOM element for key eventsthis._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.stopListenToKeyEvents function () {this._domElementKeyEvents.removeEventListener( keydown, onKeyDown );this._domElementKeyEvents null;};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 axisconst 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 lastTargetPosition new Vector3();const twoPI 2 * Math.PI;return function update( deltaTime null ) {const position scope.object.position;offset.copy( position ).sub( scope.target );// rotate offset to y-axis-is-up spaceoffset.applyQuaternion( quat );// angle from z-axis around y-axisspherical.setFromVector3( offset );if ( scope.autoRotate state STATE.NONE ) {rotateLeft( getAutoRotationAngle( deltaTime ) );}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 limitslet 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 limitsspherical.phi Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );spherical.makeSafe();// move target to panned locationif ( scope.enableDamping true ) {scope.target.addScaledVector( panOffset, scope.dampingFactor );} else {scope.target.add( panOffset );}// Limit the target distance from the cursor to create a sphere around the center of interestscope.target.sub( scope.cursor );scope.target.clampLength( scope.minTargetRadius, scope.maxTargetRadius );scope.target.add( scope.cursor );let zoomChanged false;// adjust the camera position based on zoom only if were not zooming to the cursor or if its an ortho camera// we adjust zoom later in these casesif ( scope.zoomToCursor performCursorZoom || scope.object.isOrthographicCamera ) {spherical.radius clampDistance( spherical.radius );} else {const prevRadius spherical.radius;spherical.radius clampDistance( spherical.radius * scale );zoomChanged prevRadius ! spherical.radius;}offset.setFromSpherical( spherical );// rotate offset back to camera-up-vector-is-up spaceoffset.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 );}// adjust camera positionif ( scope.zoomToCursor performCursorZoom ) {let newRadius null;if ( scope.object.isPerspectiveCamera ) {// move the camera down the pointer ray// this method avoids floating point errorconst prevRadius offset.length();newRadius clampDistance( prevRadius * scale );const radiusDelta prevRadius - newRadius;scope.object.position.addScaledVector( dollyDirection, radiusDelta );scope.object.updateMatrixWorld();zoomChanged !! radiusDelta;} else if ( scope.object.isOrthographicCamera ) {// adjust the ortho camera position based on zoom changesconst mouseBefore new Vector3( mouse.x, mouse.y, 0 );mouseBefore.unproject( scope.object );const prevZoom scope.object.zoom;scope.object.zoom Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );scope.object.updateProjectionMatrix();zoomChanged prevZoom ! scope.object.zoom;const mouseAfter new Vector3( mouse.x, mouse.y, 0 );mouseAfter.unproject( scope.object );scope.object.position.sub( mouseAfter ).add( mouseBefore );scope.object.updateMatrixWorld();newRadius offset.length();} else {console.warn( WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled. );scope.zoomToCursor false;}// handle the placement of the targetif ( newRadius ! null ) {if ( this.screenSpacePanning ) {// position the orbit target in front of the new camera positionscope.target.set( 0, 0, - 1 ).transformDirection( scope.object.matrix ).multiplyScalar( newRadius ).add( scope.object.position );} else {// get the ray and translation plane to compute target_ray.origin.copy( scope.object.position );_ray.direction.set( 0, 0, - 1 ).transformDirection( scope.object.matrix );// if the camera is 20 degrees above the horizon then dont adjust the focus target to avoid// extremely large valuesif ( Math.abs( scope.object.up.dot( _ray.direction ) ) TILT_LIMIT ) {object.lookAt( scope.target );} else {_plane.setFromNormalAndCoplanarPoint( scope.object.up, scope.target );_ray.intersectPlane( _plane, scope.target );}}}} else if ( scope.object.isOrthographicCamera ) {const prevZoom scope.object.zoom;scope.object.zoom Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / scale ) );if ( prevZoom ! scope.object.zoom ) {scope.object.updateProjectionMatrix();zoomChanged true;}}scale 1;performCursorZoom false;// update condition is:// min(camera displacement, camera rotation in radians)^2 EPS// using small-angle approximation cos(x/2) 1 - x^2 / 8if ( zoomChanged ||lastPosition.distanceToSquared( scope.object.position ) EPS ||8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) EPS ||lastTargetPosition.distanceToSquared( scope.target ) EPS ) {scope.dispatchEvent( _changeEvent );lastPosition.copy( scope.object.position );lastQuaternion.copy( scope.object.quaternion );lastTargetPosition.copy( scope.target );return true;}return false;};}();this.dispose function () {scope.domElement.removeEventListener( contextmenu, onContextMenu );scope.domElement.removeEventListener( pointerdown, onPointerDown );scope.domElement.removeEventListener( pointercancel, onPointerUp );scope.domElement.removeEventListener( wheel, onMouseWheel );scope.domElement.removeEventListener( pointermove, onPointerMove );scope.domElement.removeEventListener( pointerup, onPointerUp );const document scope.domElement.getRootNode(); // offscreen canvas compatibilitydocument.removeEventListener( keydown, interceptControlDown, { capture: true } );if ( scope._domElementKeyEvents ! null ) {scope._domElementKeyEvents.removeEventListener( keydown, onKeyDown );scope._domElementKeyEvents null;}//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 coordinatesconst spherical new Spherical();const sphericalDelta new Spherical();let scale 1;const panOffset new Vector3();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 dollyDirection new Vector3();const mouse new Vector2();let performCursorZoom false;const pointers [];const pointerPositions {};let controlActive false;function getAutoRotationAngle( deltaTime ) {if ( deltaTime ! null ) {return ( 2 * Math.PI / 60 * scope.autoRotateSpeed ) * deltaTime;} else {return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;}}function getZoomScale( delta ) {const normalizedDelta Math.abs( delta * 0.01 );return Math.pow( 0.95, scope.zoomSpeed * normalizedDelta );}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 objectMatrixv.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 positiveconst pan function () {const offset new Vector3();return function pan( deltaX, deltaY ) {const element scope.domElement;if ( scope.object.isPerspectiveCamera ) {// perspectiveconst position scope.object.position;offset.copy( position ).sub( scope.target );let targetDistance offset.length();// half of the fov is center to top of screentargetDistance * Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );// we use only clientHeight here so aspect ratio does not distort speedpanLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );} else if ( scope.object.isOrthographicCamera ) {// orthographicpanLeft( 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 perspectiveconsole.warn( WARNING: OrbitControls.js encountered an unknown camera type - pan disabled. );scope.enablePan false;}};}();function dollyOut( dollyScale ) {if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {scale / dollyScale;} else {console.warn( WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled. );scope.enableZoom false;}}function dollyIn( dollyScale ) {if ( scope.object.isPerspectiveCamera || scope.object.isOrthographicCamera ) {scale * dollyScale;} else {console.warn( WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled. );scope.enableZoom false;}}function updateZoomParameters( x, y ) {if ( ! scope.zoomToCursor ) {return;}performCursorZoom true;const rect scope.domElement.getBoundingClientRect();const dx x - rect.left;const dy y - rect.top;const w rect.width;const h rect.height;mouse.x ( dx / w ) * 2 - 1;mouse.y - ( dy / h ) * 2 1;dollyDirection.set( mouse.x, mouse.y, 1 ).unproject( scope.object ).sub( scope.object.position ).normalize();}function clampDistance( dist ) {return Math.max( scope.minDistance, Math.min( scope.maxDistance, dist ) );}//// event callbacks - update the object state//function handleMouseDownRotate( event ) {rotateStart.set( event.clientX, event.clientY );}function handleMouseDownDolly( event ) {updateZoomParameters( event.clientX, event.clientX );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, heightrotateUp( 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( dollyDelta.y ) );} else if ( dollyDelta.y 0 ) {dollyIn( getZoomScale( dollyDelta.y ) );}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 ) {updateZoomParameters( event.clientX, event.clientY );if ( event.deltaY 0 ) {dollyIn( getZoomScale( event.deltaY ) );} else if ( event.deltaY 0 ) {dollyOut( getZoomScale( event.deltaY ) );}scope.update();}function handleKeyDown( event ) {let needsUpdate false;switch ( event.code ) {case scope.keys.UP:if ( event.ctrlKey || event.metaKey || event.shiftKey ) {rotateUp( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );} else {pan( 0, scope.keyPanSpeed );}needsUpdate true;break;case scope.keys.BOTTOM:if ( event.ctrlKey || event.metaKey || event.shiftKey ) {rotateUp( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );} else {pan( 0, - scope.keyPanSpeed );}needsUpdate true;break;case scope.keys.LEFT:if ( event.ctrlKey || event.metaKey || event.shiftKey ) {rotateLeft( 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );} else {pan( scope.keyPanSpeed, 0 );}needsUpdate true;break;case scope.keys.RIGHT:if ( event.ctrlKey || event.metaKey || event.shiftKey ) {rotateLeft( - 2 * Math.PI * scope.rotateSpeed / scope.domElement.clientHeight );} else {pan( - scope.keyPanSpeed, 0 );}needsUpdate true;break;}if ( needsUpdate ) {// prevent the browser from scrolling on cursor keysevent.preventDefault();scope.update();}}function handleTouchStartRotate( event ) {if ( pointers.length 1 ) {rotateStart.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 );rotateStart.set( x, y );}}function handleTouchStartPan( event ) {if ( pointers.length 1 ) {panStart.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 );panStart.set( x, y );}}function handleTouchStartDolly( 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 );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 ( 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, heightrotateUp( 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 );const centerX ( event.pageX position.x ) * 0.5;const centerY ( event.pageY position.y ) * 0.5;updateZoomParameters( centerX, centerY );}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 );}//if ( isTrackingPointer( event ) ) return;//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 );switch ( pointers.length ) {case 0:scope.domElement.releasePointerCapture( event.pointerId );scope.domElement.removeEventListener( pointermove, onPointerMove );scope.domElement.removeEventListener( pointerup, onPointerUp );scope.dispatchEvent( _endEvent );state STATE.NONE;break;case 1:const pointerId pointers[ 0 ];const position pointerPositions[ pointerId ];// minimal placeholder event - allows state correction on pointer-uponTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );break;}}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 ) {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( customWheelEvent( event ) );scope.dispatchEvent( _endEvent );}function customWheelEvent( event ) {const mode event.deltaMode;// minimal wheel event altered to meet delta-zoom demandconst newEvent {clientX: event.clientX,clientY: event.clientY,deltaY: event.deltaY,};switch ( mode ) {case 1: // LINE_MODEnewEvent.deltaY * 16;break;case 2: // PAGE_MODEnewEvent.deltaY * 100;break;}// detect if event was triggered by pinchingif ( event.ctrlKey ! controlActive ) {newEvent.deltaY * 10;}return newEvent;}function interceptControlDown( event ) {if ( event.key Control ) {controlActive true;const document scope.domElement.getRootNode(); // offscreen canvas compatibilitydocument.addEventListener( keyup, interceptControlUp, { passive: true, capture: true } );}}function interceptControlUp( event ) {if ( event.key Control ) {controlActive false;const document scope.domElement.getRootNode(); // offscreen canvas compatibilitydocument.removeEventListener( keyup, interceptControlUp, { passive: true, capture: true } );}}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( 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 ) {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.pointerId );}function removePointer( event ) {delete pointerPositions[ event.pointerId ];for ( let i 0; i pointers.length; i ) {if ( pointers[ i ] event.pointerId ) {pointers.splice( i, 1 );return;}}}function isTrackingPointer( event ) {for ( let i 0; i pointers.length; i ) {if ( pointers[ i ] event.pointerId ) return true;}return false;}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 pointerId ( event.pointerId pointers[ 0 ] ) ? pointers[ 1 ] : pointers[ 0 ];return pointerPositions[ pointerId ];}//scope.domElement.addEventListener( contextmenu, onContextMenu );scope.domElement.addEventListener( pointerdown, onPointerDown );scope.domElement.addEventListener( pointercancel, onPointerUp );scope.domElement.addEventListener( wheel, onMouseWheel, { passive: false } );const document scope.domElement.getRootNode(); // offscreen canvas compatibilitydocument.addEventListener( keydown, interceptControlDown, { passive: true, capture: true } );// force an update at startthis.update();}}//export { OrbitControls };class RoomEnvironment extends Scene {constructor( renderer null ) {super();const geometry new BoxGeometry();geometry.deleteAttribute( uv );const roomMaterial new MeshStandardMaterial( { side: BackSide } );const boxMaterial new MeshStandardMaterial();let intensity 5;if ( renderer ! null renderer._useLegacyLights false ) intensity 900;const mainLight new PointLight( 0xffffff, intensity, 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 rightconst 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 leftconst 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 );// xconst 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 );// zconst 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 );// -zconst 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 );// yconst 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 );}dispose() {const resources new Set();this.traverse( ( object ) {if ( object.isMesh ) {resources.add( object.geometry );resources.add( object.material );}} );for ( const resource of resources ) {resource.dispose();}}}function createAreaLightMaterial( intensity ) {const material new MeshBasicMaterial();material.color.setScalar( intensity );return material;}//export { RoomEnvironment };// Special surface finish tag types.// Note: MATERIAL tag (e.g. GLITTER, SPECKLE) is not implementedconst 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_TRY_PARTS 0;const FILE_LOCATION_TRY_P 1;const FILE_LOCATION_TRY_MODELS 2;const FILE_LOCATION_AS_IS 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 COLOR_SPACE_LDRAW SRGBColorSpace;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 common#include color_pars_vertex#include fog_pars_vertex#include logdepthbuf_pars_vertex#include clipping_planes_pars_vertexvoid main() {#include color_vertexvec4 mvPosition modelViewMatrix * vec4( position, 1.0 );gl_Position projectionMatrix * mvPosition;// Transform the line segment ends and control points into camera clip spacevec4 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 vectorvec2 dir p1.xy - p0.xy;vec2 norm vec2( -dir.y, dir.x );// Get control point directions from the linevec2 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 logdepthbuf_vertex#include clipping_planes_vertex#include fog_vertex},fragmentShader: /* glsl */uniform vec3 diffuse;uniform float opacity;varying float discardFlag;#include common#include color_pars_fragment#include fog_pars_fragment#include logdepthbuf_pars_fragment#include clipping_planes_pars_fragmentvoid main() {if ( discardFlag 0.5 ) discard;#include clipping_planes_fragmentvec3 outgoingLight vec3( 0.0 );vec4 diffuseColor vec4( diffuse, opacity );#include logdepthbuf_fragment#include color_fragmentoutgoingLight diffuseColor.rgb; // simple shadergl_FragColor vec4( outgoingLight, diffuseColor.a );#include tonemapping_fragment#include colorspace_fragment#include fog_fragment#include premultiplied_alpha_fragment},} );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 wont// 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 hashfor ( 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 were checking subsegments because its more expensive to check// and requires more memory.if ( checkSubSegments ) {// add both ray directions to the mapconst 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 trianglefor ( 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 );// dont add the triangle if the edge is supposed to be hardif ( 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 boundsif ( 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 normalslet 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 weve tried to connect all faces to share normalswhile ( true ) {// Stop if there are no more faces leftlet halfEdge null;for ( const key in halfEdgeList ) {halfEdge halfEdgeList[ key ];break;}if ( halfEdge null ) {break;}// Exhaustively find all connected facesconst queue [ halfEdge ];while ( queue.length 0 ) {// initialize all vertex normals in this triangleconst 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 edgeconst 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 wont be found againconst 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 its// 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 wont be in// the halfEdgeList. If it has not then add it to the queue and delete// it so it wont be found again.if ( reverseHash in halfEdgeList ) {queue.push( otherInfo );delete halfEdgeList[ reverseHash ];}// share the first normalconst 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 ) {// its 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 normalif (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 spacewhile ( 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 modifiedresult.type original.type;result.category original.category;result.keywords original.keywords;result.author original.author;result.subobjects original.subobjects;result.fileName original.fileName;result.totalFaces original.totalFaces;result.startingBuildingStep original.startingBuildingStep;result.materials original.materials;result.group null;return result;}async fetchData( fileName ) {let triedLowerCase false;let locationState FILE_LOCATION_TRY_PARTS;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 pathlocationState FILE_LOCATION_NOT_FOUND;} else {// Next attempt is lower casefileName fileName.toLowerCase();subobjectURL fileName;triedLowerCase true;locationState FILE_LOCATION_TRY_PARTS;}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 resultsconst 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 author null;let totalFaces 0;// split into linesif ( text.indexOf( \r\n ) ! - 1 ) {// This is faster than String.split with regex that splits on bothtext 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 startingBuildingStep false;try{// Parse all line commandsfor ( 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 cachethis.setData( currentEmbeddedFileName, currentEmbeddedText );// New embedded text filecurrentEmbeddedFileName line.substring( 7 );currentEmbeddedText ;} else {currentEmbeddedText line \n;}continue;}const lp new LineParser( line, lineIndex 1 );lp.seekNonSpace();if ( lp.isAtTheEnd() ) {// Empty linecontinue;}// Parse the line typeconst 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 METAcase 0:// Parse meta directiveconst 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 parsingparsingEmbeddedFiles true;currentEmbeddedFileName lp.getRemainingString();currentEmbeddedText ;bfcCertified false;bfcCCW true;}break;case BFC:// Changes to the backface culling statewhile ( ! 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:startingBuildingStep true;break;case Author::author lp.getToken();break;default:// Other meta directives are not implementedbreak;}}break;// Line type 1: Sub-object filecase 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 mapfileName loader.fileMap[ fileName ];} else {// Standardized subfoldersif ( 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,startingBuildingStep: startingBuildingStep} );startingBuildingStep false;bfcInverted false;break;// Line type 2: Line segmentcase 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 segmentcase 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: Trianglecase 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: Quadrilateralcase 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() . );}}}catch(error){console.error(error);}if ( parsingEmbeddedFiles ) {this.setData( currentEmbeddedFileName, currentEmbeddedText );}return {faces,conditionalSegments,lineSegments,type,category,keywords,author,subobjects,totalFaces,startingBuildingStep,materials,fileName,group: null};}// returns an (optionally cloned) instance of the datagetData( 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 hasnt 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 processingthis._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 datasetData( 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 theyre 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 isnt 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;group.userData.author info.author;group.userData.type info.type;group.userData.fileName info.fileName;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 loadcontinue;}// if the subobject was loaded as a separate group then apply the parent scopes materialsif ( subobjectInfo.isGroup ) {const subobjectGroup subobjectInfo;subobject.matrix.decompose( subobjectGroup.position, subobjectGroup.quaternion, subobjectGroup.scale );subobjectGroup.userData.startingBuildingStep subobject.startingBuildingStep;subobjectGroup.name subobject.fileName;loader.applyMaterialsToMesh( subobjectGroup, subobject.colorCode, info.materials );subobjectGroup.userData.colorCode subobject.colorCode;group.add( subobjectGroup );continue;}// add the subobject group if it has children in case it has both children and primitivesif ( 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 );group.userData.colorCode subobject.colorCode;}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( this.loader, info.faces, 3, false, info.totalFaces ) );}if ( info.lineSegments.length 0 ) {group.add( createObject( this.loader, info.lineSegments, 2 ) );}if ( info.conditionalSegments.length 0 ) {group.add( createObject( this.loader, 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 its possible that another part parse has been waiting in parallel// so check the cache again to see if its been added since the last async operation so we dont// do unnecessary work.if ( this.hasCachedModel( fileName ) ) {return this.getCachedModel( fileName );}// Cache object if its a part so it can be reused later.if ( isPartType( info.type ) ) {this._cache[ key ] promise;}// return a copyconst 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( loader, 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 groupselements.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 facesif ( 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 providedlet 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 ) {const edgeMaterial loader.edgeMaterialCache.get( material );materials.push( loader.conditionalEdgeMaterialCache.get( edgeMaterial ) );} else {materials.push( loader.edgeMaterialCache.get( material ) );}}} 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.Materialthis.materials [];this.materialLibrary {};this.edgeMaterialCache new WeakMap();this.conditionalEdgeMaterialCache new WeakMap();// 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 materialsthis.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 ;// Material assigned to not available colors for meshes and edgesthis.missingColorMaterial new MeshStandardMaterial( { name: Loader.DEFAULT_MATERIAL_NAME, color: 0xFF00FF, roughness: 0.3, metalness: 0 } );this.missingEdgeColorMaterial new LineBasicMaterial( { name: Loader.DEFAULT_MATERIAL_NAME, color: 0xFF00FF } );this.missingConditionalEdgeColorMaterial new LDrawConditionalLineMaterial( { name: Loader.DEFAULT_MATERIAL_NAME, fog: true, color: 0xFF00FF } );this.edgeMaterialCache.set( this.missingColorMaterial, this.missingEdgeColorMaterial );this.conditionalEdgeMaterialCache.set( this.missingEdgeColorMaterial, this.missingConditionalEdgeColorMaterial );}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.computeBuildingSteps( group );group.userData.fileName url;onLoad( group );} ).catch( onError );}, onProgress, onError );}parse( text, onLoad ) {this.partsCache.parseModel( text, this.materialLibrary ).then( group {this.applyMaterialsToMesh( group, MAIN_COLOUR_CODE, this.materialLibrary, true );this.computeBuildingSteps( group );group.userData.fileName ;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 arrayconst 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 libconst 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 its// simply returned for the subsequent material application.function getMaterial( c, colorCode ) {// if our parent is a passthrough color code and we dont 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 colorsmaterial loader.getMaterial( colorCode );if ( material null ) {// otherwise throw a warning if this is final opportunity to set the materialconsole.warn( LDrawLoader: Material properties for code ${ colorCode } not available. );// And return the missing color materialmaterial loader.missingColorMaterial;}} else {return colorCode;}if ( c.isLineSegments ) {material loader.edgeMaterialCache.get( material );if ( c.isConditionalLine ) {material loader.conditionalEdgeMaterialCache.get( material );}}return material;}}getMainMaterial() {return this.getMaterial( MAIN_COLOUR_CODE );}getMainEdgeMaterial() {const mat this.getMaterial( MAIN_EDGE_COLOUR_CODE );return mat ? this.edgeMaterialCache.get( mat ) : null;}parseColorMetaDirective( lineParser ) {// Parses a color definition and returns a THREE.Materiallet code null;// Triangle and line colorslet fillColor #FF00FF;let edgeColor #FF00FF;// Transparencylet 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 parameterslet token null;while ( true ) {token lineParser.getToken();if ( ! token ) {break;}if ( ! parseLuminance( token ) ) {switch ( token.toUpperCase() ) {case CODE:code lineParser.getToken();break;case VALUE:fillColor lineParser.getToken();if ( fillColor.startsWith( 0x ) ) {fillColor # fillColor.substring( 2 );} else if ( ! fillColor.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 codeedgeMaterial this.getMaterial( edgeColor );if ( ! edgeMaterial ) {throw new Error( LDrawLoader: Invalid edge color while parsing material lineParser.getLineNumberString() . );}// Get the edge material for this triangle materialedgeMaterial this.edgeMaterialCache.get( 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:if ( ! parseLuminance( lineParser.getToken() ) ) {throw new Error( LDrawLoader: Invalid luminance value in material definition LineParser.getLineNumberString() . );}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 implementedlineParser.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( { roughness: 0.3, metalness: 0 } );break;case FINISH_TYPE_PEARLESCENT:// Try to imitate pearlescency by making the surface glossymaterial new MeshStandardMaterial( { roughness: 0.3, metalness: 0.25 } );break;case FINISH_TYPE_CHROME:// Mirror finish surfacematerial new MeshStandardMaterial( { roughness: 0, metalness: 1 } );break;case FINISH_TYPE_RUBBER:// Rubber finishmaterial new MeshStandardMaterial( { roughness: 0.9, metalness: 0 } );break;case FINISH_TYPE_MATTE_METALLIC:// Brushed metal finishmaterial new MeshStandardMaterial( { roughness: 0.8, metalness: 0.4 } );break;case FINISH_TYPE_METAL:// Average metal finishmaterial new MeshStandardMaterial( { roughness: 0.2, metalness: 0.85 } );break;default:// Should not happenbreak;}material.color.setStyle( fillColor, COLOR_SPACE_LDRAW );material.transparent isTransparent;material.premultipliedAlpha true;material.opacity alpha;material.depthWrite ! isTransparent;material.polygonOffset true;material.polygonOffsetFactor 1;if ( luminance ! 0 ) {material.emissive.setStyle( fillColor, COLOR_SPACE_LDRAW ).multiplyScalar( luminance );}if ( ! edgeMaterial ) {// This is the material used for edgesedgeMaterial new LineBasicMaterial( {color: new Color().setStyle( edgeColor, COLOR_SPACE_LDRAW ),transparent: isTransparent,opacity: alpha,depthWrite: ! isTransparent} );edgeMaterial.color;edgeMaterial.userData.code code;edgeMaterial.name name - Edge;// This is the material used for conditional edgesconst conditionalEdgeMaterial new LDrawConditionalLineMaterial( {fog: true,transparent: isTransparent,depthWrite: ! isTransparent,color: new Color().setStyle( edgeColor, COLOR_SPACE_LDRAW ),opacity: alpha,} );conditionalEdgeMaterial.userData.code code;conditionalEdgeMaterial.name name - Conditional Edge;this.conditionalEdgeMaterialCache.set( edgeMaterial, conditionalEdgeMaterial );}material.userData.code code;material.name name;this.edgeMaterialCache.set( material, edgeMaterial );this.addMaterial( material );return material;function parseLuminance( token ) {// Returns successlet lum;if ( token.startsWith( LUMINANCE ) ) {lum parseInt( token.substring( 9 ) );} else {lum parseInt( token );}if ( isNaN( lum ) ) {return false;}luminance Math.max( 0, Math.min( 1, lum / 255 ) );return true;}}computeBuildingSteps( model ) {// Sets userdata.buildingStep number in Group objects and userData.numBuildingSteps number in the root Group object.let stepNumber 0;model.traverse( c {if ( c.isGroup ) {if ( c.userData.startingBuildingStep ) {stepNumber ;}c.userData.buildingStep stepNumber;}} );model.userData.numBuildingSteps stepNumber 1;}}//export { LDrawLoader };class Reflector extends Mesh {constructor( geometry, options {} ) {super( geometry );this.isReflector true;this.type Reflector;this.camera new PerspectiveCamera();const scope this;const color ( options.color ! undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F );const textureWidth options.textureWidth || 512;const textureHeight options.textureHeight || 512;const clipBias options.clipBias || 0;const shader options.shader || Reflector.ReflectorShader;const multisample ( options.multisample ! undefined ) ? options.multisample : 4;//const reflectorPlane new Plane();const normal new Vector3();const reflectorWorldPosition new Vector3();const cameraWorldPosition new Vector3();const rotationMatrix new Matrix4();const lookAtPosition new Vector3( 0, 0, - 1 );const clipPlane new Vector4();const view new Vector3();const target new Vector3();const q new Vector4();const textureMatrix new Matrix4();const virtualCamera this.camera;const renderTarget new WebGLRenderTarget( textureWidth, textureHeight, { samples: multisample, type: HalfFloatType } );const material new ShaderMaterial( {name: ( shader.name ! undefined ) ? shader.name : unspecified,uniforms: UniformsUtils.clone( shader.uniforms ),fragmentShader: shader.fragmentShader,vertexShader: shader.vertexShader} );material.uniforms[ tDiffuse ].value renderTarget.texture;material.uniforms[ color ].value color;material.uniforms[ textureMatrix ].value textureMatrix;this.material material;this.onBeforeRender function ( renderer, scene, camera ) {reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );rotationMatrix.extractRotation( scope.matrixWorld );normal.set( 0, 0, 1 );normal.applyMatrix4( rotationMatrix );view.subVectors( reflectorWorldPosition, cameraWorldPosition );// Avoid rendering when reflector is facing awayif ( view.dot( normal ) 0 ) return;view.reflect( normal ).negate();view.add( reflectorWorldPosition );rotationMatrix.extractRotation( camera.matrixWorld );lookAtPosition.set( 0, 0, - 1 );lookAtPosition.applyMatrix4( rotationMatrix );lookAtPosition.add( cameraWorldPosition );target.subVectors( reflectorWorldPosition, lookAtPosition );target.reflect( normal ).negate();target.add( reflectorWorldPosition );virtualCamera.position.copy( view );virtualCamera.up.set( 0, 1, 0 );virtualCamera.up.applyMatrix4( rotationMatrix );virtualCamera.up.reflect( normal );virtualCamera.lookAt( target );virtualCamera.far camera.far; // Used in WebGLBackgroundvirtualCamera.updateMatrixWorld();virtualCamera.projectionMatrix.copy( camera.projectionMatrix );// Update the texture matrixtextureMatrix.set(0.5, 0.0, 0.0, 0.5,0.0, 0.5, 0.0, 0.5,0.0, 0.0, 0.5, 0.5,0.0, 0.0, 0.0, 1.0);textureMatrix.multiply( virtualCamera.projectionMatrix );textureMatrix.multiply( virtualCamera.matrixWorldInverse );textureMatrix.multiply( scope.matrixWorld );// Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html// Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdfreflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant );const projectionMatrix virtualCamera.projectionMatrix;q.x ( Math.sign( clipPlane.x ) projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ];q.y ( Math.sign( clipPlane.y ) projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ];q.z - 1.0;q.w ( 1.0 projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ];// Calculate the scaled plane vectorclipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) );// Replacing the third row of the projection matrixprojectionMatrix.elements[ 2 ] clipPlane.x;projectionMatrix.elements[ 6 ] clipPlane.y;projectionMatrix.elements[ 10 ] clipPlane.z 1.0 - clipBias;projectionMatrix.elements[ 14 ] clipPlane.w;// Renderscope.visible false;const currentRenderTarget renderer.getRenderTarget();const currentXrEnabled renderer.xr.enabled;const currentShadowAutoUpdate renderer.shadowMap.autoUpdate;renderer.xr.enabled false; // Avoid camera modificationrenderer.shadowMap.autoUpdate false; // Avoid re-computing shadowsrenderer.setRenderTarget( renderTarget );renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897if ( renderer.autoClear false ) renderer.clear();renderer.render( scene, virtualCamera );renderer.xr.enabled currentXrEnabled;renderer.shadowMap.autoUpdate currentShadowAutoUpdate;renderer.setRenderTarget( currentRenderTarget );// Restore viewportconst viewport camera.viewport;if ( viewport ! undefined ) {renderer.state.viewport( viewport );}scope.visible true;};this.getRenderTarget function () {return renderTarget;};this.dispose function () {renderTarget.dispose();scope.material.dispose();};}}Reflector.ReflectorShader {name: ReflectorShader,uniforms: {color: {value: null},tDiffuse: {value: null},textureMatrix: {value: null}},vertexShader: /* glsl */uniform mat4 textureMatrix;varying vec4 vUv;#include common#include logdepthbuf_pars_vertexvoid main() {vUv textureMatrix * vec4( position, 1.0 );gl_Position projectionMatrix * modelViewMatrix * vec4( position, 1.0 );#include logdepthbuf_vertex},fragmentShader: /* glsl */uniform vec3 color;uniform sampler2D tDiffuse;varying vec4 vUv;#include logdepthbuf_pars_fragmentfloat blendOverlay( float base, float blend ) {return( base 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );}vec3 blendOverlay( vec3 base, vec3 blend ) {return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) );}void main() {#include logdepthbuf_fragmentvec4 base texture2DProj( tDiffuse, vUv );gl_FragColor vec4( blendOverlay( base.rgb, color ), 1.0 );#include tonemapping_fragment#include colorspace_fragment}};//export { Reflector };function computeMikkTSpaceTangents( geometry, MikkTSpace, negateSign true ) {if ( ! MikkTSpace || ! MikkTSpace.isReady ) {throw new Error( BufferGeometryUtils: Initialized MikkTSpace library required. );}if ( ! geometry.hasAttribute( position ) || ! geometry.hasAttribute( normal ) || ! geometry.hasAttribute( uv ) ) {throw new Error( BufferGeometryUtils: Tangents require position, normal, and uv attributes. );}function getAttributeArray( attribute ) {if ( attribute.normalized || attribute.isInterleavedBufferAttribute ) {const dstArray new Float32Array( attribute.count * attribute.itemSize );for ( let i 0, j 0; i attribute.count; i ) {dstArray[ j ] attribute.getX( i );dstArray[ j ] attribute.getY( i );if ( attribute.itemSize 2 ) {dstArray[ j ] attribute.getZ( i );}}return dstArray;}if ( attribute.array instanceof Float32Array ) {return attribute.array;}return new Float32Array( attribute.array );}// MikkTSpace algorithm requires non-indexed input.const _geometry geometry.index ? geometry.toNonIndexed() : geometry;// Compute vertex tangents.const tangents MikkTSpace.generateTangents(getAttributeArray( _geometry.attributes.position ),getAttributeArray( _geometry.attributes.normal ),getAttributeArray( _geometry.attributes.uv ));// Texture coordinate convention of glTF differs from the apparent// default of the MikkTSpace library; .w component must be flipped.if ( negateSign ) {for ( let i 3; i tangents.length; i 4 ) {tangents[ i ] * - 1;}}//_geometry.setAttribute( tangent, new BufferAttribute( tangents, 4 ) );if ( geometry ! _geometry ) {geometry.copy( _geometry );}return geometry;}/*** param {ArrayBufferGeometry} geometries* param {Boolean} useGroups* return {BufferGeometry}*/function mergeGeometries( geometries, useGroups false ) {const isIndexed geometries[ 0 ].index ! null;const attributesUsed new Set( Object.keys( geometries[ 0 ].attributes ) );const morphAttributesUsed new Set( Object.keys( geometries[ 0 ].morphAttributes ) );const attributes {};const morphAttributes {};const morphTargetsRelative geometries[ 0 ].morphTargetsRelative;const mergedGeometry new BufferGeometry();let offset 0;for ( let i 0; i geometries.length; i ) {const geometry geometries[ i ];let attributesCount 0;// ensure that all geometries are indexed, or noneif ( isIndexed ! ( geometry.index ! null ) ) {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index i . All geometries must have compatible attributes; make sure index attribute exists among all geometries, or in none of them. );return null;}// gather attributes, exit early if theyre differentfor ( const name in geometry.attributes ) {if ( ! attributesUsed.has( name ) ) {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index i . All geometries must have compatible attributes; make sure name attribute exists among all geometries, or in none of them. );return null;}if ( attributes[ name ] undefined ) attributes[ name ] [];attributes[ name ].push( geometry.attributes[ name ] );attributesCount ;}// ensure geometries have the same number of attributesif ( attributesCount ! attributesUsed.size ) {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index i . Make sure all geometries have the same number of attributes. );return null;}// gather morph attributes, exit early if theyre differentif ( morphTargetsRelative ! geometry.morphTargetsRelative ) {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index i . .morphTargetsRelative must be consistent throughout all geometries. );return null;}for ( const name in geometry.morphAttributes ) {if ( ! morphAttributesUsed.has( name ) ) {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index i . .morphAttributes must be consistent throughout all geometries. );return null;}if ( morphAttributes[ name ] undefined ) morphAttributes[ name ] [];morphAttributes[ name ].push( geometry.morphAttributes[ name ] );}if ( useGroups ) {let count;if ( isIndexed ) {count geometry.index.count;} else if ( geometry.attributes.position ! undefined ) {count geometry.attributes.position.count;} else {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed with geometry at index i . The geometry must have either an index or a position attribute );return null;}mergedGeometry.addGroup( offset, count, i );offset count;}}// merge indicesif ( isIndexed ) {let indexOffset 0;const mergedIndex [];for ( let i 0; i geometries.length; i ) {const index geometries[ i ].index;for ( let j 0; j index.count; j ) {mergedIndex.push( index.getX( j ) indexOffset );}indexOffset geometries[ i ].attributes.position.count;}mergedGeometry.setIndex( mergedIndex );}// merge attributesfor ( const name in attributes ) {const mergedAttribute mergeAttributes( attributes[ name ] );if ( ! mergedAttribute ) {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the name attribute. );return null;}mergedGeometry.setAttribute( name, mergedAttribute );}// merge morph attributesfor ( const name in morphAttributes ) {const numMorphTargets morphAttributes[ name ][ 0 ].length;if ( numMorphTargets 0 ) break;mergedGeometry.morphAttributes mergedGeometry.morphAttributes || {};mergedGeometry.morphAttributes[ name ] [];for ( let i 0; i numMorphTargets; i ) {const morphAttributesToMerge [];for ( let j 0; j morphAttributes[ name ].length; j ) {morphAttributesToMerge.push( morphAttributes[ name ][ j ][ i ] );}const mergedMorphAttribute mergeAttributes( morphAttributesToMerge );if ( ! mergedMorphAttribute ) {console.error( THREE.BufferGeometryUtils: .mergeGeometries() failed while trying to merge the name morphAttribute. );return null;}mergedGeometry.morphAttributes[ name ].push( mergedMorphAttribute );}}return mergedGeometry;}/*** param {ArrayBufferAttribute} attributes* return {BufferAttribute}*/function mergeAttributes( attributes ) {let TypedArray;let itemSize;let normalized;let gpuType - 1;let arrayLength 0;for ( let i 0; i attributes.length; i ) {const attribute attributes[ i ];if ( TypedArray undefined ) TypedArray attribute.array.constructor;if ( TypedArray ! attribute.array.constructor ) {console.error( THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.array must be of consistent array types across matching attributes. );return null;}if ( itemSize undefined ) itemSize attribute.itemSize;if ( itemSize ! attribute.itemSize ) {console.error( THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.itemSize must be consistent across matching attributes. );return null;}if ( normalized undefined ) normalized attribute.normalized;if ( normalized ! attribute.normalized ) {console.error( THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.normalized must be consistent across matching attributes. );return null;}if ( gpuType - 1 ) gpuType attribute.gpuType;if ( gpuType ! attribute.gpuType ) {console.error( THREE.BufferGeometryUtils: .mergeAttributes() failed. BufferAttribute.gpuType must be consistent across matching attributes. );return null;}arrayLength attribute.count * itemSize;}const array new TypedArray( arrayLength );const result new BufferAttribute( array, itemSize, normalized );let offset 0;for ( let i 0; i attributes.length; i ) {const attribute attributes[ i ];if ( attribute.isInterleavedBufferAttribute ) {const tupleOffset offset / itemSize;for ( let j 0, l attribute.count; j l; j ) {for ( let c 0; c itemSize; c ) {const value attribute.getComponent( j, c );result.setComponent( j tupleOffset, c, value );}}} else {array.set( attribute.array, offset );}offset attribute.count * itemSize;}if ( gpuType ! undefined ) {result.gpuType gpuType;}return result;}/*** param {BufferAttribute}* return {BufferAttribute}*/export function deepCloneAttribute( attribute ) {if ( attribute.isInstancedInterleavedBufferAttribute || attribute.isInterleavedBufferAttribute ) {return deinterleaveAttribute( attribute );}if ( attribute.isInstancedBufferAttribute ) {return new InstancedBufferAttribute().copy( attribute );}return new BufferAttribute().copy( attribute );}/*** param {ArrayBufferAttribute} attributes* return {ArrayInterleavedBufferAttribute}*/function interleaveAttributes( attributes ) {// Interleaves the provided attributes into an InterleavedBuffer and returns// a set of InterleavedBufferAttributes for each attributelet TypedArray;let arrayLength 0;let stride 0;// calculate the length and type of the interleavedBufferfor ( let i 0, l attributes.length; i l; i ) {const attribute attributes[ i ];if ( TypedArray undefined ) TypedArray attribute.array.constructor;if ( TypedArray ! attribute.array.constructor ) {console.error( AttributeBuffers of different types cannot be interleaved );return null;}arrayLength attribute.array.length;stride attribute.itemSize;}// Create the set of buffer attributesconst interleavedBuffer new InterleavedBuffer( new TypedArray( arrayLength ), stride );let offset 0;const res [];const getters [ getX, getY, getZ, getW ];const setters [ setX, setY, setZ, setW ];for ( let j 0, l attributes.length; j l; j ) {const attribute attributes[ j ];const itemSize attribute.itemSize;const count attribute.count;const iba new InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, attribute.normalized );res.push( iba );offset itemSize;// Move the data for each attribute into the new interleavedBuffer// at the appropriate offsetfor ( let c 0; c count; c ) {for ( let k 0; k itemSize; k ) {iba[ setters[ k ] ]( c, attribute[ getters[ k ] ]( c ) );}}}return res;}// returns a new, non-interleaved version of the provided attributeexport function deinterleaveAttribute( attribute ) {const cons attribute.data.array.constructor;const count attribute.count;const itemSize attribute.itemSize;const normalized attribute.normalized;const array new cons( count * itemSize );let newAttribute;if ( attribute.isInstancedInterleavedBufferAttribute ) {newAttribute new InstancedBufferAttribute( array, itemSize, normalized, attribute.meshPerAttribute );} else {newAttribute new BufferAttribute( array, itemSize, normalized );}for ( let i 0; i count; i ) {newAttribute.setX( i, attribute.getX( i ) );if ( itemSize 2 ) {newAttribute.setY( i, attribute.getY( i ) );}if ( itemSize 3 ) {newAttribute.setZ( i, attribute.getZ( i ) );}if ( itemSize 4 ) {newAttribute.setW( i, attribute.getW( i ) );}}return newAttribute;}// deinterleaves all attributes on the geometryexport function deinterleaveGeometry( geometry ) {const attributes geometry.attributes;const morphTargets geometry.morphTargets;const attrMap new Map();for ( const key in attributes ) {const attr attributes[ key ];if ( attr.isInterleavedBufferAttribute ) {if ( ! attrMap.has( attr ) ) {attrMap.set( attr, deinterleaveAttribute( attr ) );}attributes[ key ] attrMap.get( attr );}}for ( const key in morphTargets ) {const attr morphTargets[ key ];if ( attr.isInterleavedBufferAttribute ) {if ( ! attrMap.has( attr ) ) {attrMap.set( attr, deinterleaveAttribute( attr ) );}morphTargets[ key ] attrMap.get( attr );}}}/*** param {BufferGeometry} geometry* return {number}*/function estimateBytesUsed( geometry ) {// Return the estimated memory used by this geometry in bytes// Calculate using itemSize, count, and BYTES_PER_ELEMENT to account// for InterleavedBufferAttributes.let mem 0;for ( const name in geometry.attributes ) {const attr geometry.getAttribute( name );mem attr.count * attr.itemSize * attr.array.BYTES_PER_ELEMENT;}const indices geometry.getIndex();mem indices ? indices.count * indices.itemSize * indices.array.BYTES_PER_ELEMENT : 0;return mem;}/*** param {BufferGeometry} geometry* param {number} tolerance* return {BufferGeometry}*/function mergeVertices( geometry, tolerance 1e-4 ) {tolerance Math.max( tolerance, Number.EPSILON );// Generate an index buffer if the geometry doesnt have one, or optimize it// if its already available.const hashToIndex {};const indices geometry.getIndex();const positions geometry.getAttribute( position );const vertexCount indices ? indices.count : positions.count;// next value for triangle indiceslet nextIndex 0;// attributes and new attribute arraysconst attributeNames Object.keys( geometry.attributes );const tmpAttributes {};const tmpMorphAttributes {};const newIndices [];const getters [ getX, getY, getZ, getW ];const setters [ setX, setY, setZ, setW ];// Initialize the arrays, allocating space conservatively. Extra// space will be trimmed in the last step.for ( let i 0, l attributeNames.length; i l; i ) {const name attributeNames[ i ];const attr geometry.attributes[ name ];tmpAttributes[ name ] new BufferAttribute(new attr.array.constructor( attr.count * attr.itemSize ),attr.itemSize,attr.normalized);const morphAttr geometry.morphAttributes[ name ];if ( morphAttr ) {tmpMorphAttributes[ name ] new BufferAttribute(new morphAttr.array.constructor( morphAttr.count * morphAttr.itemSize ),morphAttr.itemSize,morphAttr.normalized);}}// convert the error tolerance to an amount of decimal places to truncate toconst halfTolerance tolerance * 0.5;const exponent Math.log10( 1 / tolerance );const hashMultiplier Math.pow( 10, exponent );const hashAdditive halfTolerance * hashMultiplier;for ( let i 0; i vertexCount; i ) {const index indices ? indices.getX( i ) : i;// Generate a hash for the vertex attributes at the current index ilet hash ;for ( let j 0, l attributeNames.length; j l; j ) {const name attributeNames[ j ];const attribute geometry.getAttribute( name );const itemSize attribute.itemSize;for ( let k 0; k itemSize; k ) {// double tilde truncates the decimal valuehash ${ ~ ~ ( attribute[ getters[ k ] ]( index ) * hashMultiplier hashAdditive ) },;}}// Add another reference to the vertex if its already// used by another indexif ( hash in hashToIndex ) {newIndices.push( hashToIndex[ hash ] );} else {// copy data to the new index in the temporary attributesfor ( let j 0, l attributeNames.length; j l; j ) {const name attributeNames[ j ];const attribute geometry.getAttribute( name );const morphAttr geometry.morphAttributes[ name ];const itemSize attribute.itemSize;const newarray tmpAttributes[ name ];const newMorphArrays tmpMorphAttributes[ name ];for ( let k 0; k itemSize; k ) {const getterFunc getters[ k ];const setterFunc setters[ k ];newarray[ setterFunc ]( nextIndex, attribute[ getterFunc ]( index ) );if ( morphAttr ) {for ( let m 0, ml morphAttr.length; m ml; m ) {newMorphArrays[ m ][ setterFunc ]( nextIndex, morphAttr[ m ][ getterFunc ]( index ) );}}}}hashToIndex[ hash ] nextIndex;newIndices.push( nextIndex );nextIndex ;}}// generate result BufferGeometryconst result geometry.clone();for ( const name in geometry.attributes ) {const tmpAttribute tmpAttributes[ name ];result.setAttribute( name, new BufferAttribute(tmpAttribute.array.slice( 0, nextIndex * tmpAttribute.itemSize ),tmpAttribute.itemSize,tmpAttribute.normalized,) );if ( ! ( name in tmpMorphAttributes ) ) continue;for ( let j 0; j tmpMorphAttributes[ name ].length; j ) {const tmpMorphAttribute tmpMorphAttributes[ name ][ j ];result.morphAttributes[ name ][ j ] new BufferAttribute(tmpMorphAttribute.array.slice( 0, nextIndex * tmpMorphAttribute.itemSize ),tmpMorphAttribute.itemSize,tmpMorphAttribute.normalized,);}}// indicesresult.setIndex( newIndices );return result;}/*** param {BufferGeometry} geometry* param {number} drawMode* return {BufferGeometry}*/function toTrianglesDrawMode( geometry, drawMode ) {if ( drawMode TrianglesDrawMode ) {console.warn( THREE.BufferGeometryUtils.toTrianglesDrawMode(): Geometry already defined as triangles. );return geometry;}if ( drawMode TriangleFanDrawMode || drawMode TriangleStripDrawMode ) {let index geometry.getIndex();// generate index if not presentif ( 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.BufferGeometryUtils.toTrianglesDrawMode(): Undefined position attribute. Processing not possible. );return geometry;}}//const numberOfTriangles index.count - 2;const newIndices [];if ( drawMode TriangleFanDrawMode ) {// gl.TRIANGLE_FANfor ( 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_STRIPfor ( 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.BufferGeometryUtils.toTrianglesDrawMode(): Unable to generate correct amount of triangles. );}// build final geometryconst newGeometry geometry.clone();newGeometry.setIndex( newIndices );newGeometry.clearGroups();return newGeometry;} else {console.error( THREE.BufferGeometryUtils.toTrianglesDrawMode(): Unknown draw mode:, drawMode );return geometry;}}/*** Calculates the morphed attributes of a morphed/skinned BufferGeometry.* Helpful for Raytracing or Decals.* param {Mesh | Line | Points} object An instance of Mesh, Line or Points.* return {Object} An Object with original position/normal attributes and morphed ones.*/function computeMorphedAttributes( object ) {const _vA new Vector3();const _vB new Vector3();const _vC new Vector3();const _tempA new Vector3();const _tempB new Vector3();const _tempC new Vector3();const _morphA new Vector3();const _morphB new Vector3();const _morphC new Vector3();function _calculateMorphedAttributeData(object,attribute,morphAttribute,morphTargetsRelative,a,b,c,modifiedAttributeArray) {_vA.fromBufferAttribute( attribute, a );_vB.fromBufferAttribute( attribute, b );_vC.fromBufferAttribute( attribute, c );const morphInfluences object.morphTargetInfluences;if ( morphAttribute morphInfluences ) {_morphA.set( 0, 0, 0 );_morphB.set( 0, 0, 0 );_morphC.set( 0, 0, 0 );for ( let i 0, il morphAttribute.length; i il; i ) {const influence morphInfluences[ i ];const morph morphAttribute[ i ];if ( influence 0 ) continue;_tempA.fromBufferAttribute( morph, a );_tempB.fromBufferAttribute( morph, b );_tempC.fromBufferAttribute( morph, c );if ( morphTargetsRelative ) {_morphA.addScaledVector( _tempA, influence );_morphB.addScaledVector( _tempB, influence );_morphC.addScaledVector( _tempC, influence );} else {_morphA.addScaledVector( _tempA.sub( _vA ), influence );_morphB.addScaledVector( _tempB.sub( _vB ), influence );_morphC.addScaledVector( _tempC.sub( _vC ), influence );}}_vA.add( _morphA );_vB.add( _morphB );_vC.add( _morphC );}if ( object.isSkinnedMesh ) {object.applyBoneTransform( a, _vA );object.applyBoneTransform( b, _vB );object.applyBoneTransform( c, _vC );}modifiedAttributeArray[ a * 3 0 ] _vA.x;modifiedAttributeArray[ a * 3 1 ] _vA.y;modifiedAttributeArray[ a * 3 2 ] _vA.z;modifiedAttributeArray[ b * 3 0 ] _vB.x;modifiedAttributeArray[ b * 3 1 ] _vB.y;modifiedAttributeArray[ b * 3 2 ] _vB.z;modifiedAttributeArray[ c * 3 0 ] _vC.x;modifiedAttributeArray[ c * 3 1 ] _vC.y;modifiedAttributeArray[ c * 3 2 ] _vC.z;}const geometry object.geometry;const material object.material;let a, b, c;const index geometry.index;const positionAttribute geometry.attributes.position;const morphPosition geometry.morphAttributes.position;const morphTargetsRelative geometry.morphTargetsRelative;const normalAttribute geometry.attributes.normal;const morphNormal geometry.morphAttributes.position;const groups geometry.groups;const drawRange geometry.drawRange;let i, j, il, jl;let group;let start, end;const modifiedPosition new Float32Array( positionAttribute.count * positionAttribute.itemSize );const modifiedNormal new Float32Array( normalAttribute.count * normalAttribute.itemSize );if ( index ! null ) {// indexed buffer geometryif ( Array.isArray( material ) ) {for ( i 0, il groups.length; i il; i ) {group groups[ i ];start Math.max( group.start, drawRange.start );end Math.min( ( group.start group.count ), ( drawRange.start drawRange.count ) );for ( j start, jl end; j jl; j 3 ) {a index.getX( j );b index.getX( j 1 );c index.getX( j 2 );_calculateMorphedAttributeData(object,positionAttribute,morphPosition,morphTargetsRelative,a, b, c,modifiedPosition);_calculateMorphedAttributeData(object,normalAttribute,morphNormal,morphTargetsRelative,a, b, c,modifiedNormal);}}} else {start Math.max( 0, drawRange.start );end Math.min( index.count, ( drawRange.start drawRange.count ) );for ( i start, il end; i il; i 3 ) {a index.getX( i );b index.getX( i 1 );c index.getX( i 2 );_calculateMorphedAttributeData(object,positionAttribute,morphPosition,morphTargetsRelative,a, b, c,modifiedPosition);_calculateMorphedAttributeData(object,normalAttribute,morphNormal,morphTargetsRelative,a, b, c,modifiedNormal);}}} else {// non-indexed buffer geometryif ( Array.isArray( material ) ) {for ( i 0, il groups.length; i il; i ) {group groups[ i ];start Math.max( group.start, drawRange.start );end Math.min( ( group.start group.count ), ( drawRange.start drawRange.count ) );for ( j start, jl end; j jl; j 3 ) {a j;b j 1;c j 2;_calculateMorphedAttributeData(object,positionAttribute,morphPosition,morphTargetsRelative,a, b, c,modifiedPosition);_calculateMorphedAttributeData(object,normalAttribute,morphNormal,morphTargetsRelative,a, b, c,modifiedNormal);}}} else {start Math.max( 0, drawRange.start );end Math.min( positionAttribute.count, ( drawRange.start drawRange.count ) );for ( i start, il end; i il; i 3 ) {a i;b i 1;c i 2;_calculateMorphedAttributeData(object,positionAttribute,morphPosition,morphTargetsRelative,a, b, c,modifiedPosition);_calculateMorphedAttributeData(object,normalAttribute,morphNormal,morphTargetsRelative,a, b, c,modifiedNormal);}}}const morphedPositionAttribute new Float32BufferAttribute( modifiedPosition, 3 );const morphedNormalAttribute new Float32BufferAttribute( modifiedNormal, 3 );return {positionAttribute: positionAttribute,normalAttribute: normalAttribute,morphedPositionAttribute: morphedPositionAttribute,morphedNormalAttribute: morphedNormalAttribute};}function mergeGroups( geometry ) {if ( geometry.groups.length 0 ) {console.warn( THREE.BufferGeometryUtils.mergeGroups(): No groups are defined. Nothing to merge. );return geometry;}let groups geometry.groups;// sort groups by material indexgroups groups.sort( ( a, b ) {if ( a.materialIndex ! b.materialIndex ) return a.materialIndex - b.materialIndex;return a.start - b.start;} );// create index for non-indexed geometriesif ( geometry.getIndex() null ) {const positionAttribute geometry.getAttribute( position );const indices [];for ( let i 0; i positionAttribute.count; i 3 ) {indices.push( i, i 1, i 2 );}geometry.setIndex( indices );}// sort indexconst index geometry.getIndex();const newIndices [];for ( let i 0; i groups.length; i ) {const group groups[ i ];const groupStart group.start;const groupLength groupStart group.count;for ( let j groupStart; j groupLength; j ) {newIndices.push( index.getX( j ) );}}geometry.dispose(); // Required to force buffer recreationgeometry.setIndex( newIndices );// update groups indiceslet start 0;for ( let i 0; i groups.length; i ) {const group groups[ i ];group.start start;start group.count;}// merge groupslet currentGroup groups[ 0 ];geometry.groups [ currentGroup ];for ( let i 1; i groups.length; i ) {const group groups[ i ];if ( currentGroup.materialIndex group.materialIndex ) {currentGroup.count group.count;} else {currentGroup group;geometry.groups.push( currentGroup );}}return geometry;}/*** Modifies the supplied geometry if it is non-indexed, otherwise creates a new,* non-indexed geometry. Returns the geometry with smooth normals everywhere except* faces that meet at an angle greater than the crease angle.** param {BufferGeometry} geometry* param {number} [creaseAngle]* return {BufferGeometry}*/function toCreasedNormals( geometry, creaseAngle Math.PI / 3 /* 60 degrees */ ) {const creaseDot Math.cos( creaseAngle );const hashMultiplier ( 1 1e-10 ) * 1e2;// reusable vectorsconst verts [ new Vector3(), new Vector3(), new Vector3() ];const tempVec1 new Vector3();const tempVec2 new Vector3();const tempNorm new Vector3();const tempNorm2 new Vector3();// hashes a vectorfunction hashVertex( v ) {const x ~ ~ ( v.x * hashMultiplier );const y ~ ~ ( v.y * hashMultiplier );const z ~ ~ ( v.z * hashMultiplier );return ${x},${y},${z};}// BufferGeometry.toNonIndexed() warns if the geometry is non-indexed// and returns the original geometryconst resultGeometry geometry.index ? geometry.toNonIndexed() : geometry;const posAttr resultGeometry.attributes.position;const vertexMap {};// find all the normals shared by commonly located verticesfor ( let i 0, l posAttr.count / 3; i l; i ) {const i3 3 * i;const a verts[ 0 ].fromBufferAttribute( posAttr, i3 0 );const b verts[ 1 ].fromBufferAttribute( posAttr, i3 1 );const c verts[ 2 ].fromBufferAttribute( posAttr, i3 2 );tempVec1.subVectors( c, b );tempVec2.subVectors( a, b );// add the normal to the map for all verticesconst normal new Vector3().crossVectors( tempVec1, tempVec2 ).normalize();for ( let n 0; n 3; n ) {const vert verts[ n ];const hash hashVertex( vert );if ( ! ( hash in vertexMap ) ) {vertexMap[ hash ] [];}vertexMap[ hash ].push( normal );}}// average normals from all vertices that share a common location if they are within the// provided crease thresholdconst normalArray new Float32Array( posAttr.count * 3 );const normAttr new BufferAttribute( normalArray, 3, false );for ( let i 0, l posAttr.count / 3; i l; i ) {// get the face normal for this vertexconst i3 3 * i;const a verts[ 0 ].fromBufferAttribute( posAttr, i3 0 );const b verts[ 1 ].fromBufferAttribute( posAttr, i3 1 );const c verts[ 2 ].fromBufferAttribute( posAttr, i3 2 );tempVec1.subVectors( c, b );tempVec2.subVectors( a, b );tempNorm.crossVectors( tempVec1, tempVec2 ).normalize();// average all normals that meet the threshold and set the normal valuefor ( let n 0; n 3; n ) {const vert verts[ n ];const hash hashVertex( vert );const otherNormals vertexMap[ hash ];tempNorm2.set( 0, 0, 0 );for ( let k 0, lk otherNormals.length; k lk; k ) {const otherNorm otherNormals[ k ];if ( tempNorm.dot( otherNorm ) creaseDot ) {tempNorm2.add( otherNorm );}}tempNorm2.normalize();normAttr.setXYZ( i3 n, tempNorm2.x, tempNorm2.y, tempNorm2.z );}}resultGeometry.setAttribute( normal, normAttr );return resultGeometry;}/*export {computeMikkTSpaceTangents,mergeGeometries,mergeAttributes,interleaveAttributes,estimateBytesUsed,mergeVertices,toTrianglesDrawMode,computeMorphedAttributes,mergeGroups,toCreasedNormals};*///import { mergeGeometries } from ./BufferGeometryUtils.js;class LDrawUtils {static mergeObject( object ) {// Merges geometries in object by materials and returns new object. Use on not indexed geometries.// The object buffers reference the old object ones.// Special treatment is done to the conditional lines generated by LDrawLoader.function extractGroup( geometry, group, elementSize, isConditionalLine ) {// Extracts a group from a geometry as a new geometry (with attribute buffers referencing original buffers)const newGeometry new BufferGeometry();const originalPositions geometry.getAttribute( position ).array;const originalNormals elementSize 3 ? geometry.getAttribute( normal ).array : null;const numVertsGroup Math.min( group.count, Math.floor( originalPositions.length / 3 ) - group.start );const vertStart group.start * 3;const vertEnd ( group.start numVertsGroup ) * 3;const positions originalPositions.subarray( vertStart, vertEnd );const normals originalNormals ! null ? originalNormals.subarray( vertStart, vertEnd ) : null;newGeometry.setAttribute( position, new BufferAttribute( positions, 3 ) );if ( normals ! null ) newGeometry.setAttribute( normal, new BufferAttribute( normals, 3 ) );if ( isConditionalLine ) {const controlArray0 geometry.getAttribute( control0 ).array.subarray( vertStart, vertEnd );const controlArray1 geometry.getAttribute( control1 ).array.subarray( vertStart, vertEnd );const directionArray geometry.getAttribute( direction ).array.subarray( vertStart, vertEnd );newGeometry.setAttribute( control0, new BufferAttribute( controlArray0, 3, false ) );newGeometry.setAttribute( control1, new BufferAttribute( controlArray1, 3, false ) );newGeometry.setAttribute( direction, new BufferAttribute( directionArray, 3, false ) );}return newGeometry;}function addGeometry( mat, geometry, geometries ) {const geoms geometries[ mat.uuid ];if ( ! geoms ) {geometries[ mat.uuid ] {mat: mat,arr: [ geometry ]};} else {geoms.arr.push( geometry );}}function permuteAttribute( attribute, elemSize ) {// Permutes first two vertices of each attribute elementif ( ! attribute ) return;const verts attribute.array;const numVerts Math.floor( verts.length / 3 );let offset 0;for ( let i 0; i numVerts; i ) {const x verts[ offset ];const y verts[ offset 1 ];const z verts[ offset 2 ];verts[ offset ] verts[ offset 3 ];verts[ offset 1 ] verts[ offset 4 ];verts[ offset 2 ] verts[ offset 5 ];verts[ offset 3 ] x;verts[ offset 4 ] y;verts[ offset 5 ] z;offset elemSize * 3;}}// Traverse the object hierarchy collecting geometries and transforming them to world spaceconst meshGeometries {};const linesGeometries {};const condLinesGeometries {};object.updateMatrixWorld( true );const normalMatrix new Matrix3();object.traverse( c {if ( c.isMesh | c.isLineSegments ) {const elemSize c.isMesh ? 3 : 2;const geometry c.geometry.clone();const matrixIsInverted c.matrixWorld.determinant() 0;if ( matrixIsInverted ) {permuteAttribute( geometry.attributes.position, elemSize );permuteAttribute( geometry.attributes.normal, elemSize );}geometry.applyMatrix4( c.matrixWorld );if ( c.isConditionalLine ) {geometry.attributes.control0.applyMatrix4( c.matrixWorld );geometry.attributes.control1.applyMatrix4( c.matrixWorld );normalMatrix.getNormalMatrix( c.matrixWorld );geometry.attributes.direction.applyNormalMatrix( normalMatrix );}const geometries c.isMesh ? meshGeometries : ( c.isConditionalLine ? condLinesGeometries : linesGeometries );if ( Array.isArray( c.material ) ) {for ( const groupIndex in geometry.groups ) {const group geometry.groups[ groupIndex ];const mat c.material[ group.materialIndex ];const newGeometry extractGroup( geometry, group, elemSize, c.isConditionalLine );addGeometry( mat, newGeometry, geometries );}} else {addGeometry( c.material, geometry, geometries );}}} );// Create object with merged geometriesconst mergedObject new Group();const meshMaterialsIds Object.keys( meshGeometries );for ( const meshMaterialsId of meshMaterialsIds ) {const meshGeometry meshGeometries[ meshMaterialsId ];const mergedGeometry mergeGeometries( meshGeometry.arr );mergedObject.add( new Mesh( mergedGeometry, meshGeometry.mat ) );}const linesMaterialsIds Object.keys( linesGeometries );for ( const linesMaterialsId of linesMaterialsIds ) {const lineGeometry linesGeometries[ linesMaterialsId ];const mergedGeometry mergeGeometries( lineGeometry.arr );mergedObject.add( new LineSegments( mergedGeometry, lineGeometry.mat ) );}const condLinesMaterialsIds Object.keys( condLinesGeometries );for ( const condLinesMaterialsId of condLinesMaterialsIds ) {const condLineGeometry condLinesGeometries[ condLinesMaterialsId ];const mergedGeometry mergeGeometries( condLineGeometry.arr );const condLines new LineSegments( mergedGeometry, condLineGeometry.mat );condLines.isConditionalLine true;mergedObject.add( condLines );}mergedObject.userData.constructionStep 0;mergedObject.userData.numConstructionSteps 1;return mergedObject;}}//export { LDrawUtils };const clock new Clock();class Loop {constructor(camera, scene, renderer) {this.camera camera;this.scene scene;this.renderer renderer;// somewhere in the Loop class:this.updatables []}start() {this.renderer.setAnimationLoop(() {// tell every animated object to tick forward one frame// this.tick();// render a framethis.renderer.render(this.scene, this.camera);});}stop() {this.renderer.setAnimationLoop(null);}tick(){// only call the getDelta function once per frame!const delta clock.getDelta();// console.log(// The last frame rendered in ${delta * 1000} milliseconds,// );// eslint-disable-next-line typescript-eslint/strict-boolean-expressionsif(this.updatables.length){for (const object of this.updatables) {if(typeof object.tick function){object.tick(delta);}}}}}//export { Loop };initViewer (){container document.querySelector(#scene-container);let ldraw new Ldraw();ldraw.start();} 执行代码 现在我们已经成功添加了很多功能和复杂的交互逻辑将不同的细节进行分层管理。后续可采用 MVC 模式重构代码将代码分为三个层级模型层、视图层和控制层。模型层负责数据的管理视图层负责展示数据和渲染 UI控制层则负责协调模型层和视图层之间的交互同时处理一些业务逻辑。重构后代码层级会更清晰方便拓展其功能。 最后将脚本执行到dom即可看到模型。 !DOCTYPE html html langzh-CN headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0meta nametheme-color content#000000 /meta http-equivX-UA-Compatible contentIEedgemeta namerenderer contentwebkitmeta nameforce-rendering contentwebkitmeta namegoogle-site-verification contentFTeR0c8arOPKh8c5DYh_9uu98_zJbaWw53J-Sch9MTgmeta data-rhtrue namekeywords contentthree.js实现乐高小轿车meta data-rhtrue namedescription contentthree.js实现乐高小轿车meta data-rhtrue propertyog:title contentthree.js实现乐高小轿车link relicon href./favicon.icotitlethree.js实现乐高小轿车/titlestylebody {padding: 0;margin: 0;font: normal 14px/1.42857 Tahoma;}#scene-container {height: 100vh;}/style /head body onloadinitViewer()div idscene-container/divscriptlet initViewer null/script /body /html 模型描述文本 0 LDraw.org Configuration File 0 Name: LDConfig.ldr 0 Author: LDraw.org 0 !LDRAW_ORG Configuration UPDATE 2017-12-15 0 // LDraw Solid Colours 0                              // LEGOID  26 - Black 0 !COLOUR Black                                                 CODE   0   VALUE #05131D   EDGE #595959 0                              // LEGOID  23 - Bright Blue 0 !COLOUR Blue                                                  CODE   1   VALUE #0055BF   EDGE #333333 0                              // LEGOID  28 - Dark Green 0 !COLOUR Green                                                 CODE   2   VALUE #257A3E   EDGE #333333 0                              // LEGOID 107 - Bright Bluish Green 0 !COLOUR Dark_Turquoise                                        CODE   3   VALUE #00838F   EDGE #333333 0                              // LEGOID  21 - Bright Red 0 !COLOUR Red                                                   CODE   4   VALUE #C91A09   EDGE #333333 0                              // LEGOID 221 - Bright Purple 0 !COLOUR Dark_Pink                                             CODE   5   VALUE #C870A0   EDGE #333333 0                              // LEGOID 217 - Brown 0 !COLOUR Brown                                                 CODE   6   VALUE #583927   EDGE #1E1E1E 0                              // LEGOID   2 - Grey 0 !COLOUR Light_Grey                                            CODE   7   VALUE #9BA19D   EDGE #333333 0                              // LEGOID  27 - Dark Grey 0 !COLOUR Dark_Grey                                             CODE   8   VALUE #6D6E5C   EDGE #333333 0                              // LEGOID  45 - Light Blue 0 !COLOUR Light_Blue                                            CODE   9   VALUE #B4D2E3   EDGE #333333 0                              // LEGOID  37 - Bright Green 0 !COLOUR Bright_Green                                          CODE  10   VALUE #4B9F4A   EDGE #333333 0                              // LEGOID 116 - Medium Bluish Green 0 !COLOUR Light_Turquoise                                       CODE  11   VALUE #55A5AF   EDGE #333333 0                              // LEGOID   4 - Brick Red 0 !COLOUR Salmon                                                CODE  12   VALUE #F2705E   EDGE #333333 0                              // LEGOID   9 - Light Reddish Violet 0 !COLOUR Pink                                                  CODE  13   VALUE #FC97AC   EDGE #333333 0                              // LEGOID  24 - Bright Yellow 0 !COLOUR Yellow                                                CODE  14   VALUE #F2CD37   EDGE #333333 还原模型到三维场景 参见 3. 开发和学习环境引入threejs | Three.js中文网 LDraw.org - LDraw.org Homepage
http://www.dnsts.com.cn/news/197513.html

相关文章:

  • 网站开发 架构第三方做农产品价格数据的网站
  • 腾讯免费网站空间做同城特价的网站
  • 长春网站建设服务扶贫网站建设优势
  • wordpress网站打开慢商城网站有什么好处
  • 网站开发.networdpress 相邻文章
  • 社区门户网站模板网站建站的步骤流程
  • 怎么选择网站模板北京网站开发公司一网天行
  • 网站怎么制作 优帮云企业网站搜索优化
  • 康定网站建设营销软文100字
  • wordpress15天教程网站改名 seo
  • 建立一个网站需要多久wordpress 糗百
  • 旅游网站建设的目标建设视频网站多少钱
  • 休闲食品网站建设策划书钓鱼转转网站在线生成
  • 建设积分兑换官方网站wordpress去版权插件
  • 网站开发代理合同制作微信网页
  • 梧州单身相亲网站erp网站建设
  • 网站手机端和电脑端禁止百度收录的网站
  • 盐城百度推广公司上海seo优化培训机构
  • dede网站百度统计怎么做Wordpress iPhone 上传
  • 昆明猫咪网站建设公司代刷网站推广链接免费
  • 网站建设论文二稿网站对于一个企业的优势
  • wordpress做网站优点wordpress智能小程序
  • 服务器怎么发布网站徐州网站建设公司哪个好
  • 寺庙网站开发建设方案wordpress留言板设置
  • 多媒体网站设计开发是指什么建网站做商城个体户资质可以
  • 免费网站建设能做吗网站设计流程的步骤
  • 贵阳市城乡建设部网站公司做一个静态网站多少钱
  • 个人网站开发是学什么语言做视频网站盈利模式
  • 做销售在哪个网站找客户端廊坊seo建站
  • 阿里巴巴国际网站建设wordpress+书店