import * as THREE from 'three'; import {FontLoader} from 'three/addons/loaders/FontLoader.js'; import {CinematicCamera} from 'three/addons/cameras/CinematicCamera.js'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js'; import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js'; import { CSS3DRenderer } from 'three/addons/renderers/CSS3DRenderer.js'; import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js'; import { Storage as _Storage } from 'three/libs/Storage.js'; import {unzipSync} from 'three/libs/fflate.module.js'; console.log('Three.js version:', THREE.REVISION); // --------------------- 折叠 ----------------------- //region Description //endregion function IOT3D() { console.log("-------------- IOT3D --------------") window.iot3d = this window.THREE = THREE; // Used by APP Scripts. window.FontLoader = FontLoader; // Used by APP Scripts. // window.VRButton = VRButton; // Used by APP Scripts. window.OrbitControls = OrbitControls; // Used by APP Scripts. window.CinematicCamera = CinematicCamera; // Used by APP Scripts. window.EffectComposer = EffectComposer; // Used by APP Scripts. window.RenderPass = RenderPass; // Used by APP Scripts. window.ShaderPass = ShaderPass; // Used by APP Scripts. window.OutlinePass = OutlinePass; // Used by APP Scripts. this.loading_open = function (time){ //1000为遮罩层显示时长,若不传则一直显示,须调用关闭方法 $.mask_fullscreen(time); } this.loading_close = function (){ //关闭遮罩层 $.mask_close_all(); } this.loading_text = function (text){ //关闭遮罩层 $.mask_text(text); } this.loading_html = function (html){ //关闭遮罩层 $.mask_html(html); } // var self; THREE.Cache.enabled = true; //这是一个全局属性,只需要设置一次,供内部使用FileLoader的所有加载器使用。 // 项目 // var IOT3D_Url = "https://iot3d.baozhida.cn" // var IOT3D_Url = "https://iot3d.baozhida.cn" // if(url.indexOf("127.0.0.1") != -1){ // IOT3D_Url = "http://127.0.0.1:6210" // }else { // IOT3D_Url = "https://iot3d.baozhida.cn" // } var clock = new THREE.Clock(); // 公共 var dom_width = 500, dom_height = 500; var camera, scene, dom, renderer, rendererCss2, rendererCss3,controls; var events = {}; window.parkId = 0; // 园区ID var project; // 项目配置 /// =- 选取 var raycaster, mouse, INTERSECTED = null; // 变量初始化 self = this; dom_width = window.innerWidth; dom_height = window.innerHeight; self.width = dom_width; self.height = dom_width; // var vrButton = VRButton.createButton( renderer ); // eslint-disable-line no-undef // --------------------- 初始化 ----------------------- //region Description // 舞台 scene = new THREE.Scene(); // 相机 camera = new THREE.PerspectiveCamera(45, dom_width / dom_height, 0.1, 3000); camera.position.set(-10, 10, 30); camera.lookAt(scene.position); window.mixer = new THREE.AnimationMixer( scene ); // 动画 // HTML dom dom = document.getElementById("iot3d"); //渲染器 renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); // TODO: Use setPixelRatio() // renderer.outputEncoding = THREE.sRGBEncoding; renderer.shadowMap.enabled = true;// 阴影 renderer.setSize( dom_width, dom_height ); renderer.setAnimationLoop( animate ); dom.appendChild(renderer.domElement); // rendererCss3 rendererCss3 = new CSS3DRenderer(); rendererCss3.setSize( dom.innerWidth, dom.innerHeight ); rendererCss3.domElement.style.position = 'absolute'; rendererCss3.domElement.style.top = 0; dom.appendChild( rendererCss3.domElement ); // rendererCss2 rendererCss2 = new CSS2DRenderer(); rendererCss2.setSize( dom.innerWidth, dom.innerHeight ); rendererCss2.domElement.style.position = 'absolute'; rendererCss2.domElement.style.top = '0px'; dom.appendChild( rendererCss2.domElement ); // 加载到 网页 document.body.appendChild(dom); window.addEventListener('resize', function () { self.setSize(window.innerWidth, window.innerHeight); }); /// =- 选取 raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(); var selectedObjects = [],compose,renderPass,outlinePass; // 鼠标移动 -轨道控制 // var AutomaticRotationPerspective = [1,2,20] //自动旋转视角 0 【关闭】 1【360度旋转[1,旋转速度{1~9,2},俯视角度{0~100,20}]】 var AutomaticRotationPerspectiveInterval = undefined // 定时任务 var AutomaticRotationPerspectiveTally = 0 // 计数 // - 鼠标移动视角 controls = new OrbitControls(camera, renderer.domElement); // controls.addEventListener('change', animate); // use if there is no animation loop controls.dampingFactor = 0.25; controls.minDistance = 5; // 最小距离 controls.maxDistance = 1000; controls.screenSpacePanning = false; // 允许相机平移 // 设置最大和最小角度 controls.maxPolarAngle = Math.PI / 2; // 最大角度 (90度) - 可视化平面 controls.minPolarAngle = 0; // 最小角度 (0度) - 直接向下 controls.target.set(0, 0, -2.2); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 // controls.position0.set(200, 200, 500 ) controls.update(); // 检查 鼠标是否有操作 renderer.domElement.addEventListener( 'pointerdown', function () { if(AutomaticRotationPerspectiveInterval !== undefined){ clearInterval(AutomaticRotationPerspectiveInterval); // 停止定时任务的执行 } AutomaticRotationPerspectiveTally = 0 AutomaticRotationPerspectiveInterval = undefined } ); // 定时 开始触发 自动旋转视角 setInterval(() => { if(project === undefined || project.autoangle === undefined || project.autoangle === "None") return; // 直接跳过 if(AutomaticRotationPerspectiveInterval === undefined){ AutomaticRotationPerspectiveTally += 1 if(AutomaticRotationPerspectiveTally === 3){ console.log("project.autoangle:",project.autoangle) switch (project.autoangle) { case "Angle360": // 360度旋转 self.AroundRotation(scene,project.autoangle_speed,project.autoangle_angle) break; case "Regainstate": // 回到原始视角 self.Focus(self.GetScene()) break; } } } }, 1000); // 每秒 // 数据订阅 window.pubSub = { list: {}, // 订阅 subscribe: function(key, fn) { if (!this.list[key]) this.list[key] = []; this.list[key].push(fn); }, //取消订阅 unsubscribe: function(key, fn) { let fnList = this.list[key]; if (!fnList) return false; if (!fn) { // 不传入指定的方法,清空所用 key 下的订阅 fnList && (fnList.length = 0); } else { fnList.forEach((item, index) => { item === fn && fnList.splice(index, 1); }); } }, // 发布 publish: function(key, ...args) { if(this.list[key] === undefined) return; for (let fn of this.list[key]) fn.call(this, ...args); } } // this.storage.get(fxx) // // function fxx(xxx) { // console.log("fxx-------------------------",xxx) // } // 测试 this.Test = function (rotationSpeed = 0.001) { console.log("Test-------------------------") return } // 测试 function cubeDr(a, x, y, z) { var cubeGeo = new THREE.BoxGeometry(a, a, a); var cubeMat = new THREE.MeshPhongMaterial({ color: 0xfff000 * Math.random() }); var cube = new THREE.Mesh(cubeGeo, cubeMat); cube.position.set(x, y, z); cube.castShadow = true; scene.add(cube); return cube; } //endregion // --------------------- 核心 ----------------------- //region Description // 核心方法 //region Description // 舞台 this.GetScene = function () { return scene } // 相机 this.GetCamera = function () { return camera } //渲染器 this.GetRenderer = function () { return renderer } // 获取 UUID 模型 this.GetModelByUuid = function (uuid) { return scene.getObjectByProperty('uuid', uuid, true); } // 获取 UUID 模型 内部函数 this.Model = function (uuid,fun) { fun(scene.getObjectByProperty('uuid', uuid, true)); } // 设置显示比例 this.setPixelRatio = function (pixelRatio) { renderer.setPixelRatio(pixelRatio); }; // 设置大小 this.setSize = function (width, height) { dom_width = width; dom_height = height; if (camera) { camera.aspect = dom_width / dom_height; camera.updateProjectionMatrix(); } renderer.setSize(width, height); if ( rendererCss3 !== null ) { rendererCss3.setSize( dom.offsetWidth, dom.offsetHeight ); } if ( rendererCss2 !== null ) { rendererCss2.setSize( dom.offsetWidth, dom.offsetHeight ); } self.width = dom_width; self.height = dom_width; }; //endregion // 鼠标 //region Description //更新视角中心点 this.orbitControls_target = function (position) { console.log("更新视角中心点:", position) controls.target.set(position.x, position.y, position.z); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 controls.update(); } // 计算场景最远距离,并控制参数 function orbitControls_maxDistance() { // 计算场景中的包围盒 function calculateSceneBoundingBox(scene) { const box = new THREE.Box3(); scene.traverse((object) => { if (object.isMesh) { // 更新包围盒以包含当前对象的包围盒 const objectBox = new THREE.Box3().setFromObject(object); box.union(objectBox); } }); return box; } // 计算场景的包围盒 const boundingBox = calculateSceneBoundingBox(scene); // 获取包围盒的尺寸 const size = new THREE.Vector3(); boundingBox.getSize(size); // 获取包围盒的中心 const center = new THREE.Vector3(); boundingBox.getCenter(center); // 计算最大尺寸距离 (从中心到某个角的距离) const maxDistance = center.distanceTo(boundingBox.max); // console.log("从中心到最远角的距离:", maxDistance); controls.maxDistance = maxDistance * 10; } // 鼠标选择初始化 var OutlinePass_selectedObjects_Map = new Map(); function OutlinePass_inte(){ // console.log("OutlinePass_inte") // 清空所有选项 selectedObjects = [] self.Model_Selected_Clear() camera.lookAt(scene.position); compose = new EffectComposer(renderer); renderPass = new RenderPass(scene, camera); outlinePass = new OutlinePass(new THREE.Vector2(window.innerWidth,window.innerHeight),scene,camera); outlinePass.renderToScreen = true; outlinePass.selectedObjects = selectedObjects; compose.addPass(renderPass); compose.addPass(outlinePass); // https://threejs.org/examples/?q=webgl_postprocessing_outline#webgl_postprocessing_outline outlinePass.renderToScreen = true; outlinePass.edgeStrength = 3 //粗 0.01, 10 outlinePass.edgeGlow = 1 //发光 0.0, 1 outlinePass.edgeThickness = 2 //光晕粗 1, 4 outlinePass.pulsePeriod = 0 //闪烁 0.0, 5 outlinePass.usePatternTexture = false //是否使用贴图 let visibleEdgeColor = '#00a1fd'; // 选择颜色 let hiddenEdgeColor = '#00a1fd'; //遮挡部分颜色 outlinePass.visibleEdgeColor.set(visibleEdgeColor); outlinePass.hiddenEdgeColor.set(hiddenEdgeColor); // let light = new THREE.AmbientLight(0x333333); // scene.add(light); // // 这里没有 渲染会报错 let light = new THREE.SpotLight(0xFFFFFF); light.position.set(0, 40, 30); light.castShadow = true; light.shadow.mapSize.height = 1; light.shadow.mapSize.width = 1; light.angle = 0; scene.add(light); // light = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6); // light.position.set(0, 200, 0); // scene.add(light); // const light = new THREE.DirectionalLight( 0xffffff, 0.6 ); // light.position.set( 1, 1, 1 ); // light.castShadow = true; // light.shadow.mapSize.width = 1024; // light.shadow.mapSize.height = 1024; // // const d = 10; // // light.shadow.camera.left = - d; // light.shadow.camera.right = d; // light.shadow.camera.top = d; // light.shadow.camera.bottom = - d; // light.shadow.camera.far = 1000; // // scene.add( light ); } // 刷新 function OutlinePass_selectedObjects_Refresh() { if (outlinePass == undefined) return; selectedObjects = []; OutlinePass_selectedObjects_Map.forEach(function(value, key) { // console.log(key, value); selectedObjects.push( value ); }) if (INTERSECTED != null){ if(!OutlinePass_selectedObjects_Map.has(INTERSECTED.uuid)){ selectedObjects.push( INTERSECTED ); } } // console.log("selectedObjects:",selectedObjects) outlinePass.selectedObjects = selectedObjects; // render() } // 选中配置 选择颜色 ,遮挡部分颜色 this.Model_Selected_Config = function(visibleEdgeColor="#00ff18",hiddenEdgeColor="#ff0000") { outlinePass.visibleEdgeColor.set(visibleEdgeColor); outlinePass.hiddenEdgeColor.set(hiddenEdgeColor); } // 添加 this.Model_Selected_Add = function(Model) { OutlinePass_selectedObjects_Map.set(Model.uuid, Model) OutlinePass_selectedObjects_Refresh() } // 删除 this.Model_Selected_Del = function(Model) { OutlinePass_selectedObjects_Map.delete(Model.uuid) OutlinePass_selectedObjects_Refresh() } // 清空 this.Model_Selected_Clear = function() { OutlinePass_selectedObjects_Map.clear() OutlinePass_selectedObjects_Refresh() } //endregion // ------------------------------ 运动 --------------------------------------------- // 聚焦物体 - var startMove_is = false // 聚焦物体 - V1 // this.startFocus = function (ob,MoveTime=1) { // // if(startMove_is) { // console.log("任务还没结束,不能开始新任务!") // return; // } // startMove_is = true // 开始 // // if(ob.type != "Group") { // // console.log("Group != ") // // startMove_is = false // // return; // // } // if(ob.children.length == 0) { // console.log("children.length == 0!") // startMove_is = false // return; // } // if(ob.children[0].type != "PerspectiveCamera") { // console.log("children[0].type != PerspectiveCamera") // startMove_is = false // return; // } // let MoveList = [] // MoveList.push([camera.position.x,camera.position.y,camera.position.z]) // // MoveList.push([ob.position.x,ob.position.y,ob.position.z]) // // MoveList.push([ob.children[0].position.x + ob.position.x, ob.children[0].position.y + ob.position.y, ob.children[0].position.z + ob.position.z]) // MoveList.push([ob.children[0].matrixWorld.elements[12], ob.children[0].matrixWorld.elements[13], ob.children[0].matrixWorld.elements[14]]) // // MoveList.push([ob.matrixWorld.elements[12], ob.matrixWorld.elements[13], ob.matrixWorld.elements[14]]) // console.log("MoveList:",MoveList) // // let MoveListCurve = [] // for(var item of MoveList) { // MoveListCurve.push(new THREE.Vector3(item[0], item[1], item[2])) // } // let curve = new THREE.CatmullRomCurve3(MoveListCurve) // // var curveList = curve.getPoints(20 * MoveTime) // // console.log("curveList:",curveList) // // var testIndex = 0 // var t = setInterval(function () { // if(!startMove_is) { // curveList = [] // testIndex = 0 // // clearTimeout(t) //停止 t 定时器 // return // } // // 模仿管道的镜头推进 // if (curveList.length !== 0) { // if (testIndex < curveList.length ) { // // const point = curveList[testIndex] //获取样条曲线指定点坐标,作为相机的位置 // const pointBox = curveList[testIndex+2] //获取样条曲线指定点坐标 // // camera.position.set(point.x, point.y , point.z) // camera.lookAt(ob.matrixWorld.elements[12], ob.matrixWorld.elements[13], ob.matrixWorld.elements[14]) // // controls.target.set(ob.matrixWorld.elements[12], ob.matrixWorld.elements[13], ob.matrixWorld.elements[14]); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 // controls.update(); // // camera.lookAt(ob.position.x,ob.position.y,ob.position.z) // testIndex += 1 // } else { // curveList = [] // testIndex = 0 // // clearTimeout(t) //停止 t 定时器 // startMove_is = false // // // // 更新视角中心点 // controls.target.set(ob.matrixWorld.elements[12], ob.matrixWorld.elements[13], ob.matrixWorld.elements[14]); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 // controls.update(); // // } // } // // render() // }, 50) // // return // } // 围绕旋转 ,围绕对象 , 旋转速度{1~9,2} , 俯视角度{0~100,20} this.AroundRotation = function (ob,anglespeed_ = 2,targetHeight = 20) { // 自动计算包围盒 const box = new THREE.Box3().setFromObject(ob); const size = box.getSize(new THREE.Vector3()); const center = box.getCenter(new THREE.Vector3()); // const targetHeight = 20; // 俯视角度的高度 const radius = Math.max(size.x, size.z) *1.2; // 基于包围盒计算半径 let anglespeed = 0.001 * anglespeed_; // 旋转速度 let angle = Math.atan2(camera.position.z - center.z, camera.position.x - center.x); // 当前相机角度 // 设置相机位置的函数 function updateCameraPosition() { camera.position.x = radius * Math.cos(angle); camera.position.z = radius * Math.sin(angle); camera.position.y = targetHeight; // 固定Y坐标高度 camera.lookAt(center); // 始终朝向场景中心 } // 定时循环 AutomaticRotationPerspectiveInterval = setInterval(() => { angle += anglespeed; // 旋转速度 updateCameraPosition(); renderer.render(scene, camera); }, 1000 / 60); // 每秒60帧 } // 聚焦物体 - V2 this.Focus = function (ob) { // 计算物体的边界盒 const box = new THREE.Box3().setFromObject(ob); const size = box.getSize(new THREE.Vector3()); const distance = size.length() * 2.0; // 增加一些距离,以便能看得到 // 按照目标物体的大小和 20 度俯视角度计算相机位置 const pitchAngle = THREE.MathUtils.degToRad(20); // 转换为弧度 20 // const yawAngle = THREE.MathUtils.degToRad(angle); // 旋转 90 度 const yawAngle = ob.rotation._y const targetEnd = box.getCenter(new THREE.Vector3()); const targetPosition = new THREE.Vector3( targetEnd.x + distance * Math.sin(yawAngle), targetEnd.y + distance * Math.sin(pitchAngle), targetEnd.z + distance * Math.cos(yawAngle) ); let initialPosition = new THREE.Vector3(); initialPosition.copy(camera.position); // 记录初始位置 // 在动画开始前,确保相机在正确的初始位置 // camera.position.copy(targetPosition); // controls.target.copy(target); // controls.update(); // 更新控件 const targetStart = new THREE.Vector3(0, 0, 0); // 初始中心点 // const targetEnd = new THREE.Vector3(1, 1, 1); // 目标中心点 targetStart.x = controls.target.x targetStart.y = controls.target.y targetStart.z = controls.target.z // console.log("targetStart:",targetStart) // console.log("targetEnd:",targetEnd) // console.log("initialPosition:",initialPosition) // console.log("targetPosition:",targetPosition) const distancex = initialPosition.distanceTo(targetPosition); // console.log("运动距离:", distancex); if(startMove_is) { console.log("任务还没结束,不能开始新任务!") return; } startMove_is = true // 开始 // 动画属性 const animationDuration = distancex * 2; // 动画持续 2 秒 let animationDurationTime = 0; // 动画开始时间 var It = setInterval(function () { animationDurationTime += 1; const t = Math.min(animationDurationTime / animationDuration, 1); // 归一化 // console.log("t:",animationDurationTime) // 插值计算新的相机位置 camera.position.x = THREE.MathUtils.lerp(initialPosition.x, targetPosition.x, t); camera.position.y = THREE.MathUtils.lerp(initialPosition.y, targetPosition.y, t); camera.position.z = THREE.MathUtils.lerp(initialPosition.z, targetPosition.z, t); // 插值控制目标位置 controls.target.x = THREE.MathUtils.lerp(targetStart.x, targetEnd.x, t); controls.target.y = THREE.MathUtils.lerp(targetStart.y, targetEnd.y, t); controls.target.z = THREE.MathUtils.lerp(targetStart.z, targetEnd.z, t); // 更新控件目标 camera.lookAt(controls.target); // 始终看向目标 controls.update(); // 更新控件以应用新位置和目标 // render() if(animationDuration <= animationDurationTime || !startMove_is){ startMove_is = false clearTimeout(It) //停止 t 定时器 } }, 10) } // 聚焦物体运动 - // this.startFocusMotion = function (ob,MoveList,MoveTime=1) { // if(startMove_is) { // console.log("任务还没结束,不能开始新任务!") // return; // } // startMove_is = true // 开始 // // console.log("MoveList:",MoveList) // // let MoveListCurve = [] // for(var item of MoveList) { // MoveListCurve.push(new THREE.Vector3(item[0], item[1], item[2])) // } // let curve = new THREE.CatmullRomCurve3(MoveListCurve) // // var curveList = curve.getPoints(20 * MoveTime) // // console.log("curveList:",curveList) // // var testIndex = 0 // var t = setInterval(function () { // if(!startMove_is) { // curveList = [] // testIndex = 0 // // clearTimeout(t) //停止 t 定时器 // } // // 模仿管道的镜头推进 // if (curveList.length !== 0) { // if (testIndex < curveList.length ) { // // const point = curveList[testIndex] //获取样条曲线指定点坐标,作为相机的位置 // // camera.position.set(point.x, point.y , point.z) // // camera.lookAt(ob[0], ob[1], ob[2]) // controls.target.set(ob[0], ob[1], ob[2]); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 // controls.update(); // // camera.lookAt(ob.position.x,ob.position.y,ob.position.z) // testIndex += 1 // } else { // curveList = [] // testIndex = 0 // // clearTimeout(t) //停止 t 定时器 // startMove_is = false // // // // 更新视角中心点 // controls.target.set(ob[0], ob[1], ob[2]); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 // controls.update(); // // } // } // // render() // }, 50) // // return // } // 路径移动 this.startMove = function (MoveList,MoveTime=1,orbitControls_target=[]) { if(startMove_is) { console.log("任务还没结束,不能开始新任务!") return; } startMove_is = true // 开始 if(MoveList.length == 0) { console.log("数据异常!") startMove_is = false return; } if(MoveList[0].length != 3) { console.log("数据异常!") startMove_is = false return; } let MoveListCurve = [] for(var item of MoveList) { MoveListCurve.push(new THREE.Vector3(item[0], item[1], item[2])) } let curve = new THREE.CatmullRomCurve3(MoveListCurve) var curveList = curve.getPoints(20 * MoveTime) // console.log("curveList:",curveList) var testIndex = 0 var t = setInterval(function () { if(!startMove_is) { curveList = [] testIndex = 0 clearTimeout(t) //停止 t 定时器 } // 模仿管道的镜头推进 if (curveList.length !== 0) { if (testIndex < curveList.length - 2) { const point = curveList[testIndex] //获取样条曲线指定点坐标,作为相机的位置 const pointBox = curveList[testIndex+2] //获取样条曲线指定点坐标 camera.position.set(point.x, point.y , point.z) camera.lookAt(pointBox.x, pointBox.y , pointBox.z) testIndex += 1 } else { curveList = [] testIndex = 0 clearTimeout(t) //停止 t 定时器 startMove_is = false // 更新视角中心点 if(orbitControls_target.length == 3){ controls.target.set(orbitControls_target[0], orbitControls_target[1], orbitControls_target[2]); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 controls.update(); } } } // render() }, 50) return } // 停止移动 this.stopMove = function () { startMove_is = false } // 渲染 //region Description // ---------- 渲染 ----------- // renderer.setAnimationLoop( render ); var clock = new THREE.Clock(); // only used for animations // function render() { // // console.log("render") // // // 渲染方式 // if(compose != undefined) { // if(selectedObjects.length > 0){ // compose.render() // }else { // renderer.render(scene, camera); // } // }else { // renderer.render(scene, camera); // } // // if ( rendererCss2 !== null ) { // rendererCss2.render( scene, camera ); // } // if ( rendererCss3 !== null ) { // rendererCss3.render( scene, camera ); // } // // } // this.Render = function () { // render() // } // 渲染 // let lastRender = performance.now(); function animate() { // let timestamp = timestamp.now(); // if (timestamp - lastRender < 1000 / 60) return; // 限制为 60fps // lastRender = timestamp; if(window.ScenePlane !== undefined){ scene.add( window.ScenePlane ); } // renderer.render(scene, camera); if(compose != undefined) { if(selectedObjects.length > 0){ compose.render() }else { renderer.render(scene, camera); } }else { renderer.render(scene, camera); } if ( rendererCss2 !== null ) { rendererCss2.render( scene, camera ); } if ( rendererCss3 !== null ) { rendererCss3.render( scene, camera ); } // 更新控制器 // controls.update(); // 仅在需要时调用,例如当你使相机移动时 if(window.ScenePlane !== undefined){ scene.remove( window.ScenePlane ); } // Animations 动画 if(window.mixer != null){ window.mixer.update( clock.getDelta() ); } // requestAnimationFrame(animate); } //endregion //endregion // --------------------- 鼠标事件 ----------------------- //region Description var Model_onEvents = { mousemove:undefined, click:undefined, dblclick:undefined, mousedown:undefined, } // 递归寻找上级可触发 function finde_parent_choice(Ob) { if (Ob.choice) { return {Ob:Ob,is:true} } if(Ob.parent.isScene) return null,false var Obf = finde_parent_choice(Ob.parent) if(Obf.is){ return Obf } return {Ob:null,is:false} } // 移动 dom.addEventListener('mousemove', onMouseMove, false); function onMouseMove(event) { // console.log("onMouseMove:",event) if (event.isPrimary === false) return; // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1) mouse.x = (event.clientX / dom_width) * 2 - 1; mouse.y = -(event.clientY / dom_height) * 2 + 1; raycaster.setFromCamera(mouse, camera); // 计算物体和射线的焦点 var intersects = raycaster.intersectObjects(scene.children); // console.log("intersects:",intersects) if (intersects.length > 0){ // for (let n = 0;intersects.length > n;n++) { let objectP = intersects[n].object // console.log("objectP:",objectP) if(objectP === undefined) break; let objectF = finde_parent_choice(objectP) // console.log("finde_parent_choice:",objectF) if (objectF.is) { if(INTERSECTED !== null && INTERSECTED.uuid === objectF.Ob.uuid) break; INTERSECTED = objectF.Ob; // console.log("移入:",INTERSECTED) if (Model_onEvents.mousemove !== undefined) Model_onEvents.mousemove(INTERSECTED) if(INTERSECTED.scriptsf !== undefined ){ INTERSECTED.scriptsf.forEach(scriptf => { if(scriptf["onMouseMoveIn"] !== undefined){ scriptf.onMouseMoveIn() } }) } break; } // for (let xn = 0; 10 > xn; xn++) { // console.log(xn,"-objectP:",objectP) // if(objectP == null) break; // // if (objectP.type == "Object3D") { // if (INTERSECTED !== objectP) { // console.log("intersects[0].object:", intersects[0].object.parent) // if (objectP.choice === true) { // INTERSECTED = objectP; // if (Model_onEvents.mousemove !== undefined) Model_onEvents.mousemove(INTERSECTED) // break; // } // } // // } // objectP = objectP.parent // } } // console.log("onMouseMove:",INTERSECTED) }else { // console.log("移出:",INTERSECTED) if(INTERSECTED !== null && INTERSECTED.scriptsf !== undefined ){ INTERSECTED.scriptsf.forEach(scriptf => { if(scriptf["onMouseMoveOut"] !== undefined){ scriptf.onMouseMoveOut() } }) } INTERSECTED = null; } OutlinePass_selectedObjects_Refresh() // render() // 可优化空间 } this.Model_onMouseMove = function (fun) { Model_onEvents.mousemove = fun } //单击延时触发 var clickTimeId,clickTimeIs = false; // 单击 dom.addEventListener('click', onClick, false); function onClick(event) { // console.log("onClick:",INTERSECTED) if(INTERSECTED === null){return} // 取消上次延时未执行的方法 clearTimeout(clickTimeId); const INTERSECTED_ = INTERSECTED //执行延时 clickTimeId = setTimeout( function () { if (INTERSECTED_) { if(Model_onEvents.click != undefined) Model_onEvents.click(INTERSECTED) } // console.log("onClick:",INTERSECTED) if(INTERSECTED_.scriptsf !== undefined ){ INTERSECTED_.scriptsf.forEach(scriptf => { if(scriptf["onClick"] !== undefined){ scriptf.onClick() } }) } // render() }, 250); } this.Model_onClick = function (fun) { Model_onEvents.click = fun } // 双击 dom.addEventListener('dblclick', onDblclick, false); function onDblclick(event) { if(INTERSECTED === null){return} clearTimeout(clickTimeId); // 取消上次延时未执行的方法 if (INTERSECTED) { if(Model_onEvents.dblclick != undefined) Model_onEvents.dblclick(INTERSECTED) } if(INTERSECTED.scriptsf !== undefined ){ INTERSECTED.scriptsf.forEach(scriptf => { if(scriptf["onDblclick"] !== undefined){ scriptf.onDblclick() } }) } // render() } this.Model_onDblclick = function (fun) { Model_onEvents.dblclick = fun } // 右击--- dom.addEventListener('contextmenu', onMousedown, false); function onMousedown(event) { if(clickTimeIs) return; clickTimeIs = true; setTimeout( function () { clickTimeIs = false; }, 2000); // 防止 反复触发退出 if(Model_onEvents.mousedown != undefined) Model_onEvents.mousedown(INTERSECTED,event) onBackclick() // render() } this.Model_onMousedown = function (fun) { Model_onEvents.mousedown = fun } // // this.ondBlclick_Model = function (position) { // console.log("更新视角中心点:", position) // controls.target.set(position.x, position.y, position.z); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 // controls.update(); // } //endregion // --------------------- 项目 ----------------------- //region Description // 本地调试模式 this.LocalRun = function ( parkId = 0 ) { window.parkId = parkId new _Storage(function(result) { console.log(result); // scene = result.scene loadJson(result,"000000"+parkId) }); } var loadProject_Map = []; //ProjectID 映射 var Now_ProjectID = ""; // 当前 ProjectID this.load = function (){ console.log("加载完毕") } // 加载项目 this.LoadProject = function (ProjectID) { if(Now_ProjectID == ProjectID){ return } console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "加载 ProjectID:", ProjectID) // 是否加载与缓存过 if (loadProject_Map[ProjectID] == undefined) { var httpRequest = new XMLHttpRequest();//第一步:建立所需的对象 httpRequest.open('GET', './GetProject?T_ViewID=' + ProjectID, true);//第二步:打开连接 将请求参数写在url中 ps:"./Ptest.php?name=test&nameone=testone" httpRequest.send();//第三步:发送请求 将请求参数写在URL中 httpRequest.onreadystatechange = function () { if (httpRequest.readyState == 4 && httpRequest.status == 200) { var json = JSON.parse(httpRequest.responseText);//获取到json字符串,还需解析 console.log(json); if (json.Code != 200) { console.log("ProjectID 错误!", ProjectID) return "ProjectID 错误!" } var json = JSON.parse(json.Data.T_url);//获取到json字符串,还需解析 console.log(json); window.parkList = json window.parkId = 0 // 如果需要兼容低版本的浏览器,需要判断一下FileReader对象是否存在。 if (window.FileReader) { blobLoad(ProjectID, window.parkList[window.parkId].T_url) } else { console.log('你的浏览器不支持读取文件'); loadProject_Map[ProjectID] = src } } }; } else { console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "缓存加载 ProjectID:", ProjectID) f_load(ProjectID) } } this.LoadLocal = function () { var httpRequest = new XMLHttpRequest();//第一步:建立所需的对象 httpRequest.open('GET', './static/LocalProject/main.json', true);//第二步:打开连接 将请求参数写在url中 ps:"./Ptest.php?name=test&nameone=testone" httpRequest.responseType = 'json'; // 设置响应类型为 JSON httpRequest.onload = function() { if (httpRequest.status >= 200 && httpRequest.status < 300) { var jsonArray = httpRequest.response; // 直接获取 JSON 数组 console.log("jsonArray:",jsonArray); window.parkList = jsonArray window.parkId = 0 // 如果需要兼容低版本的浏览器,需要判断一下FileReader对象是否存在。 if (window.FileReader) { blobLoad("00000", window.parkList[window.parkId].T_url) } else { console.log('你的浏览器不支持读取文件'); loadProject_Map[ProjectID] = src } } }; httpRequest.send();//第三步:发送请求 将请求参数写在URL中 } // 解压并更新舞台 function f_load(ProjectID) { console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "开始加载 file:", loadProject_Map[ProjectID]) // 解压模型 // var promise = fetch('../static/16635717199e4e03e8-8850-4152-9c08-9c203c882f7a.zip') var promise = fetch(loadProject_Map[ProjectID]) .then((d) => d.arrayBuffer()) promise = promise.then(function (data) { //响应的内容 console.log("data:", data); const decompressed = unzipSync(new Uint8Array(data), { // You may optionally supply a filter for files. By default, all files in a // ZIP archive are extracted, but a filter can save resources by telling // the library not to decompress certain files filter(file) { // Don't decompress the massive image or any files larger than 10 MiB return file; } }); console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "file:", decompressed['app.json']) var obj = JSON.parse(new TextDecoder().decode(decompressed['app.json'])); loadJson(obj,ProjectID);// 加载场景 // self.setSize(dom_width, dom_height); // orbitControls() // 鼠标移动 -轨道控制 Now_ProjectID = ProjectID console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "JSON obj:", obj) }).catch(function (err) { console.log(err); }) } // 导入 json function loadJson(json,ProjectID) { let loader = new THREE.ObjectLoader(); // 加载 console.log("loadJson:", json) project = json.project; // if (project.vr !== undefined) renderer.xr.enabled = project.vr; // if (project.shadows !== undefined) renderer.shadowMap.enabled = project.shadows; // if (project.shadowType !== undefined) renderer.shadowMap.type = project.shadowType; // if (project.toneMapping !== undefined) renderer.toneMapping = project.toneMapping; // if (project.toneMappingExposure !== undefined) renderer.toneMappingExposure = project.toneMappingExposure; // if (project.physicallyCorrectLights !== undefined) renderer.physicallyCorrectLights = project.physicallyCorrectLights; console.log("rendererCss2:", rendererCss2) if ( rendererCss2 !== null ) { rendererCss2.clean() rendererCss3.clean() } scene = loader.parse(json.scene); // camera = loader.parse(json.camera); // console.log("json.camera:",json.camera) // console.log("camera:",camera) camera.position.x = json.camera.object.matrix[12] camera.position.y = json.camera.object.matrix[13] camera.position.z = json.camera.object.matrix[14] // camera.aspect = dom_width / dom_height; // camera.updateProjectionMatrix(); // renderer.setSize(dom_width, dom_height); // 中心点 controls.target.set(0, 0, 0); // 控件的焦点,.object围绕它运行。它可以随时手动更新以更改控件的焦点。 controls.update(); //////-------------- // ScenePlane 场景平面 if ( project.sceneplane !== undefined ){ // console.log("sceneplane:",project.sceneplane) switch ( project.sceneplane ) { case 'None': window.ScenePlane = undefined break; case 'Grass': //草地平面 const gt = new THREE.TextureLoader().load( './static/js/img/cd.jpg' ); const gg = new THREE.PlaneGeometry( 300, 300 ); const gm = new THREE.MeshPhongMaterial( { color: 0xffffff, map: gt } ); window.ScenePlane = new THREE.Mesh( gg, gm ); window.ScenePlane.rotation.x = - Math.PI / 2; // window.ScenePlane.rotation.y = -0.1 window.ScenePlane.material.map.repeat.set( 64, 64 ); window.ScenePlane.material.map.wrapS = THREE.RepeatWrapping; window.ScenePlane.material.map.wrapT = THREE.RepeatWrapping; window.ScenePlane.material.map.colorSpace = THREE.SRGBColorSpace; // note that because the ground does not cast a shadow, .castShadow is left false window.ScenePlane.receiveShadow = true; break; } } //// ------------ events = { init: [], keydown: [], keyup: [], onMouseMoveIn: [], //鼠标移入 onMouseMoveOut: [], //鼠标移入 onClick: [], //鼠标单击 onDblclick: [], //鼠标双击 onBackclick: [], //鼠标右击 退后 renderer: [], update: [] }; window.Getevents = function () { return events } var scriptWrapParams = 'iot3d,renderer,scene,camera'; var scriptWrapResultObj = {}; for (var eventKey in events) { scriptWrapParams += ',' + eventKey; scriptWrapResultObj[eventKey] = eventKey; } var scriptWrapResult = JSON.stringify(scriptWrapResultObj).replace(/\"/g, ''); //执行代码 // 递归遍历 所有脚本 并添加 function traverseChildrenScript(object) { if ( object.scripts !== undefined && object.scripts.length > 0 ) { console.log("addObjectScript:",object.uuid,object.scripts) object.scriptsf = [] var scripts = object.scripts; for ( var i = 0; i < scripts.length; i ++ ) { // 每个 script 代码 var script = scripts[i]; var functions = ( new Function( scriptWrapParams, script.source + '\nreturn ' + scriptWrapResult + ';' ).bind( object ) )( this, renderer, scene, camera ); // console.log("functions.name",functions) object.scriptsf.push(functions) for ( var name in functions ) { // console.log("functions.name",name) if ( functions[ name ] === undefined ) continue; if ( events[ name ] === undefined ) { console.warn( 'APP.Player: Event type not supported (', name, ')' ); continue; } if(name !== "update"){ events[ name ].push( functions[ name ].bind( object ) ); }else { var subSN = SeekParameterNodes(object) // console.log("subSN:",subSN) // 订阅 pubSub.subscribe(subSN, data => { // console.log("subSN:",subSN,data); functions["update"].bind( object )( data ) }) } } } } // 遍历当前对象的所有子对象 object.children.forEach(child => { traverseChildrenScript(child); // 递归调用 }); } traverseChildrenScript(scene) // console.log("events:", events) dispatch(events.init, arguments); console.log("加载完成 ProjectID:",ProjectID) // 场景加载后 视角归为 self.setSize(window.innerWidth, window.innerHeight); orbitControls_maxDistance() // 计算场景最远距离,并控制参数 OutlinePass_inte() // 鼠标选择初始化 }; // 文件缓存本地 function blobLoad(ProjectID, src) { self.loading_open() // console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "文件缓存本地 :", ProjectID) // let self = this; const req = new XMLHttpRequest(); req.open("GET", src, true); req.responseType = "blob"; req.onload = function () { // Onload is triggered even on 404 // so we need to check the status code if (this.status === 200) { const videoBlob = this.response; console.log("videoBlob:", videoBlob) const blobSrc = URL.createObjectURL(videoBlob); // IE10+ console.log("blobSrc:", blobSrc) loadProject_Map[ProjectID] = blobSrc console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "文件缓存本地完成 ProjectID:", ProjectID) f_load(ProjectID) } self.loading_close() }; //监听进度事件 req.addEventListener("progress", function (evt) { if (evt.lengthComputable) { var percentComplete = evt.loaded / evt.total; //进度 console.log('[' + /\d\d\:\d\d\:\d\d/.exec(new Date())[0] + ']', "ProjectID:", ProjectID, " 进度:", (percentComplete * 100) + "%") self.loading_text("模型加载中... "+parseInt(percentComplete * 100) + "%") // 替换内容 } }, false); req.onerror = function () { // Error console.log("blobLoad Error!", src) loadProject_Map[ProjectID] = src }; req.send(); } //endregion // --------------------- 脚本 ----------------------- //region Description // var time, startTime; // startTime = performance.now(); // // 创建一个每秒执行一次的定时循环 // setInterval(function() { // // 在这里编写需要重复执行的代码 // time = performance.now(); // const rendertime = { time: time - startTime } // // console.log("renderer:",rendertime) // try { // dispatch( events.renderer, rendertime); // } catch ( e ) { // console.error( ( e.message || e ), ( e.stack || '' ) ); // } // // }, 1000); // 1000 毫秒 = 1 秒 function dispatch( array, event ) { for ( var i = 0, l = array.length; i < l; i ++ ) { array[ i ]( event ); } } // // function animate() { // // time = performance.now(); // // // // prevTime = time; // // } // this.play = function () { // // if ( renderer.xr.enabled ) dom.append( vrButton ); // // startTime = prevTime = performance.now(); // // document.addEventListener( 'keydown', oneyDown ); // document.addEventListener( 'keyup', onKeyUp ); // document.addEventListener( 'pointerup', onPointerUp ); // document.addEventListener( 'pointermove', onPointerMove ); // // // dispatch( events.start, arguments ); // // renderer.setAnimationLoop( animate ); // // }; this.render = function ( time ) { dispatch( events.update, { time: time * 1000, delta: 0 /* TODO */ } ); renderer.render( scene, camera ); }; this.dispose = function () { renderer.dispose(); camera = undefined; scene = undefined; }; // function onKeyDown( event ) { dispatch( events.keydown, event ); } function onKeyUp( event ) { dispatch( events.keyup, event ); } // function onPointerDown( event ) { // dispatch( events.pointerdown, event ); // // } // // function onPointerUp( event ) { // dispatch( events.pointerup, event ); // // } // function onBackclick( event ) { dispatch( events.onBackclick, event ); } function upDate( event ) { try { dispatch( events.update, event ); } catch ( e ) { console.error( ( e.message || e ), ( e.stack || '' ) ); } } //endregion // --------------------- 功能 ----------------------- //region Description // this.BackgroundScene = function () { // var urls = [ // './static/js/img/posx.jpg', // './static/js/img/negx.jpg', // './static/js/img/posy.jpg', // './static/js/img/negy.jpg', // './static/js/img/posz.jpg', // './static/js/img/negz.jpg' // ]; // // var cubeLoader = new THREE.CubeTextureLoader(); // scene.background = cubeLoader.load(urls); // setTimeout(function(){ // render() // },3000); // // } // 屏幕坐标与世界坐标 this.scene_3Dto2D = function (position) { const worldVector = new THREE.Vector3( position.x, position.y, position.z ); const standardVec = worldVector.project(camera); const centerX = dom_width / 2; const centerY = dom_height / 2; const screenX = Math.round(centerX * standardVec.x + centerX); const screenY = Math.round(-centerY * standardVec.y + centerY); console.log("screen:", screenX, screenY) return screenX, screenY } //endregion console.log("window.runmode:",window.runmode) if(window.runmode === undefined) window.runmode = 2 // 开始 加载场景 if (window.runmode === 0){ this.LocalRun()// 本地调试模式 }else if(window.runmode === 1){ this.LoadProject(window.T_ViewID) // 在线 }else if(window.runmode === 2){ this.LoadLocal() // 离线 } } export {IOT3D}; new IOT3D();