import * as THREE from "../js/three.module.js"; import initTinyUSDZ from "../js/tinyusdz.js"; import { OrbitControls } from "../js/OrbitControls.js"; const urlAll = window.location.href.split("?m=")[1]; // console.log(123456,urlAll); const USDZ_FILEPATH = `https://4dkankan.oss-cn-shenzhen.aliyuncs.com/sxz/${urlAll}.usdz`; document.addEventListener("DOMContentLoaded", async () => { const loadingBar = document.getElementById("loading"); // 获取加载条元素 loadingBar.style.display = "block"; // 显示加载条 const usd_res = await fetch(USDZ_FILEPATH); const totalBytes = parseInt(usd_res.headers.get("Content-Length"), 10); // 获取总字节数 const reader = usd_res.body.getReader(); // 获取读取器 const chunks = []; // 存储数据块 let receivedLength = 0; // 已接收字节数 let timeOut = -1; // 更新加载条的函数 const updateLoadingBar = (loaded) => { let percentage = (loaded / totalBytes) * 100; if (percentage >= 100) { percentage = 100; // 隐藏加载条 clearTimeout(timeOut); timeOut = setTimeout(() => { const loadingBoxDom = document.querySelector(".loadingBox"); loadingBoxDom.style.opacity = 0; loadingBoxDom.style.pointerEvents = "none"; }, 300); } loadingBar.style.width = `${percentage}%`; // 更新加载条宽度 }; // 读取数据流 while (true) { const { done, value } = await reader.read(); // 读取数据 if (done) break; // 如果读取完成,退出循环 chunks.push(value); // 存储数据块 receivedLength += value.length; // 更新已接收字节数 updateLoadingBar(receivedLength); // 更新加载条 } const usd_data = new Uint8Array(receivedLength); // 创建最终数据数组 let position = 0; for (const chunk of chunks) { usd_data.set(chunk, position); // 将数据块写入最终数组 position += chunk.length; // 更新位置 } // const usd_binary = new Uint8Array(usd_data); // 加载 TinyUSDZ initTinyUSDZ().then(function (TinyUSDZLoader) { const usd = new TinyUSDZLoader.TinyUSDZLoader(usd_data); // console.log(usd.numMeshes()); const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 ); const renderer = new THREE.WebGLRenderer({ alpha: true, // 关键配置,设置背景透明 antialias: true, // 可选抗锯齿 }); renderer.setClearColor(0x000000, 0); // 第二个参数为透明度(0表示完全透明) renderer.setSize(window.innerWidth, window.innerHeight); renderer.setAnimationLoop(animate); document.body.appendChild(renderer.domElement); // First mesh only const mesh = usd.getMesh(0); //console.log("usd", usd) //console.log("mesh", mesh); //const geometry = new THREE.BoxGeometry( 1, 1, 1 ); const geometry = new THREE.BufferGeometry(); geometry.setAttribute( "position", new THREE.BufferAttribute(mesh.points, 3) ); // TODO: set normal from mesh if (mesh.hasOwnProperty("texcoords")) { // console.log(mesh.texcoords); geometry.setAttribute("uv", new THREE.BufferAttribute(mesh.texcoords, 2)); } const usdMaterial = usd.getMaterial(mesh.materialId); //console.log("usdMat", usdMaterial); //if (usdMaterial.aaa) { // console.log("aaa"); //} var material; if (usdMaterial.hasOwnProperty("diffuseColorTextureId")) { const diffTex = usd.getTexture(usdMaterial.diffuseColorTextureId); const img = usd.getImage(diffTex.textureImageId); // console.log(img); // assume RGBA for now. let image8Array = new Uint8ClampedArray(img.data); let imgData = new ImageData(image8Array, img.width, img.height); const texture = new THREE.DataTexture(imgData, img.width, img.height); texture.flipY = true; texture.needsUpdate = true; material = new THREE.MeshBasicMaterial({ map: texture, }); } else { material = new THREE.MeshNormalMaterial(); } // Assume triangulated indices. geometry.setIndex( new THREE.Uint32BufferAttribute(mesh.faceVertexIndices, 1) ); geometry.computeVertexNormals(); //const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); const cube = new THREE.Mesh(geometry, material); // 缩放模型 cube.scale.set(2, 2, 2); cube.updateMatrixWorld(true); // 强制更新世界矩阵[3](@ref) // 计算包围盒 const box = new THREE.Box3().setFromObject(cube); const center = new THREE.Vector3(); box.getCenter(center); // 居中模型 cube.position.sub(center); // 计算尺寸并调整相机 const size = box.getSize(new THREE.Vector3()).length(); camera.near = size / 100; camera.far = size * 100; camera.updateProjectionMatrix(); camera.position.set(size * 0.5, size * 0.5, size * 0.5); camera.lookAt(0, 0, 0); scene.add(cube); //camera.position.z = 25; // camera.position.z = 1.0; const controls = new OrbitControls(camera, renderer.domElement); controls.enablePan = false; // 禁用右键平移功能 controls.enableZoom = false; // 必须禁用轨道控制器的默认缩放[1](@ref) controls.enableDamping = true; controls.dampingFactor = 0.25; controls.screenSpacePanning = false; controls.maxPolarAngle = Math.PI / 2; // 兼容鼠标滚轮与触摸屏双指缩放 const handleZoom = (delta) => { // console.log('--------',delta); cube.updateMatrixWorld(true); // 计算包围盒 const box = new THREE.Box3().setFromObject(cube); const center = new THREE.Vector3(); box.getCenter(center); // 居中模型 cube.position.sub(center); cube.scale.multiplyScalar(delta); cube.scale.clampScalar(0.5, 4); // 限制缩放范围0.5-3倍[1,6](@ref) }; // 鼠标滚轮事件 window.addEventListener( "wheel", (e) => { e.preventDefault(); const zoomFactor = e.deltaY > 0 ? 0.95 : 1.05; handleZoom(zoomFactor); }, { passive: false } ); // 触摸屏双指缩放 let initialDistance = 0; window.addEventListener("touchstart", (e) => { if (e.touches.length === 2) { initialDistance = Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ); } }); window.addEventListener("touchmove", (e) => { if (e.touches.length === 2) { const currentDistance = Math.hypot( e.touches[0].pageX - e.touches[1].pageX, e.touches[0].pageY - e.touches[1].pageY ); const zoomFactor = currentDistance > initialDistance ? 1.01 : 0.99; handleZoom(zoomFactor); initialDistance = currentDistance; } }); function animate() { //cube.rotation.x += 0.01; // cube.rotation.y += 0.02; controls.update(); renderer.render(scene, camera); } window.addEventListener("resize", () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }); }); });