threejs 读取zip中的文件

302 阅读4分钟

需要准备的插件和包(threeJS等包省略,这个三是实现标题功能核心的包):

  1. jszip  Docs:JSZip (stuk.github.io)

  2. jszip-utils

  3. FileSaver

 

        package1&2 是 从 压缩包中读取 模型的关键!!!, package1&3是模型打包压缩必须的!

 【代码有很多地方可以自行精简】

首先实现上传模型,自动打包成zip文件并自动下载zip包

复制代码

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/jszip/3.7.1/jszip.min.js"
    type="application/javascript"></script>
    <script src="https://lf9-cdn-tos.bytecdntp.com/cdn/expire-1-M/FileSaver.js/2014-08-29/FileSaver.min.js" type="application/javascript"></script>  <title>Document</title>
</head>

<body>
  
    <input type="file" name="file" id="fileID">
    <button type="submit" onclick="toZip()">sure</button>
  
</body>
<script>
  function toZip() {
    var file = document.getElementById("fileID");
  // 文件上传后 点击submit, 获取到上传文件
    var zip = new JSZip(); 
    zip.file(file.files[0].name, file.files[0]);
    //console.log(file.files[0]);
    zip.generateAsync({ // 这里可以看jszip官方文档
      type: "blob",
      compression: "DEFLATE",
      compressionOptions: {
        level: 9
      }
    }).then(function (content) {
      // console.log(2);
      saveAs(content, file.files[0].name.split('.')[0]+'.zip');
    //到这里 就可以上传模型,自动会压缩并打包。
    });
    //  if (false) {
    //     var content = "这里可以拿到接口返回的压缩包二进制数据,还原后解压";
    //     zip.loadAsync(content).then(function (zip) {
    //        new_zip.file("getContent.txt").async("string");
    //     });
    //  }
  }
</script>

</html>

复制代码

 

再实现结合vue3+threeJS+jszip 读取渲染压缩包里模型功能[threeJS 基本操作,例如打灯光,设置相机等操作就不注释了。网上很多例子。]

复制代码

<script setup>
import JSZip from "jszip";
import * as THREE from "three";
import { OrbitControls } from "@/js/Controls";
import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer.js";
import { ref, onMounted, getCurrentInstance, nextTick, onUpdated } from "vue";

const { proxy } = getCurrentInstance();
// let scene, mesh;base
let scene, raycaster, mouse, sphere103, lineGeometry, line, base;
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
const num = ref(0);
const isShow = ref(0);
const info = ref("model loading...");
const camera = ref(null);
const renderer = ref(null);
const labelRenderer = ref(null);
const controls = ref(null);
const path = "/model/408.fbx";

const createScene = () => {
  scene = new THREE.Scene();
  scene.position.y = 0;
};
const createLight = () => {
  scene.add(new THREE.AmbientLight(0x444444));

  const light = new THREE.PointLight(0xffffff);

  light.position.set(0, 50, 50);

  //告诉平行光需要开启阴影投射

  light.castShadow = true;

  scene.add(light);
};
const createCamera = () => {
  camera.value = new THREE.PerspectiveCamera(
    45,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  camera.value.position.set(100, 35, 0); // 设置相机位置

  camera.value.lookAt(new THREE.Vector3(0, 0, 0)); // 设置相机方向
  scene.add(camera.value);
};
const createRender = async () => {
  const element = document.getElementById("container");
  renderer.value = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  renderer.value.setSize(element.clientWidth, element.clientHeight); // 设置渲染区域尺寸
  renderer.value.shadowMap.enabled = true; // 显示阴影
  renderer.value.shadowMap.type = THREE.PCFSoftShadowMap;
  renderer.value.setClearColor(0x3f3f3f, 1); // 设置背景颜色
  element.appendChild(renderer.value.domElement); // 这里是canvas

  labelRenderer.value = new CSS2DRenderer();
  labelRenderer.value.setSize(element.clientWidth, element.clientHeight);
  labelRenderer.value.domElement.style.position = "absolute";
  labelRenderer.value.domElement.style.top = "0px";
  // console.dir(labelRenderer.value.domElement);
  element.appendChild(labelRenderer.value.domElement); // 这里domElement是 divlabel的父级
};
const createControls = () => {
  controls.value = new OrbitControls(
    camera.value,
    labelRenderer.value.domElement
  );
  controls.value.minDistance = 5;
  controls.value.maxDistance = 100;
};

function setScaleToFitSize(obj) { // 这里是将模型适配到一个合适的比例
  const boxHelper = new THREE.BoxHelper(obj);
  boxHelper.geometry.computeBoundingBox();
  const box = boxHelper.geometry.boundingBox;
  const maxDiameter = Math.max(
    box.max.x - box.min.x,
    box.max.y - box.min.y,
    box.max.z - box.min.z
  );
  const scaleValue = camera.value.position.x / maxDiameter;
  obj.scale.set(scaleValue, scaleValue, scaleValue);
}

const loadSTL = (scale = 0.02) => {
  const new_zip = new JSZip(); // 实例化jszip
  const loader = new GLTFLoader();
  // 这里的 工具类是配合jszip使用的,工具类读取zip数据,得到二进制数据流,存到 blob中,之后,threeJS 加载器loader去加载这个二进制数据就好了
  JSZipUtils.getBinaryContent("/model/825B.zip", function (err, data) {
    if (err) {
      throw err; // or handle err
    }

    new_zip.loadAsync(data).then(function (res) {
      let fileList = res.files;
      for (let key in fileList) {
        // 读取模型文件内容
        new_zip
          .file(key)
          .async("arraybuffer")
          .then((content) => {
            // Blob构造文件地址,通过url加载模型
            let blob = new Blob([content]);
            let modelUrl = URL.createObjectURL(blob);
            console.log(modelUrl);
            loader.load(modelUrl, (gltf) => {
              gltf.scene.traverse(function (child) {
                if (child.isMesh) {
                  //模型自发光
                  child.material.emissive = child.material.color;
                  child.material.emissiveMap = child.material.map;
                }
              });
              setScaleToFitSize(gltf.scene);
              scene.add(gltf.scene);
            });
          });
      }
    });
  });
};

const onProgress = (xhr) => {
  // console.log("加载完成的百分比" + (xhr.loaded / xhr.total) * 100 + "%");
  num.value = Math.floor((xhr.loaded / xhr.total) * 100);
};
const render = () => {
  requestAnimationFrame(render);

  renderer.value.render(scene, camera.value);
  labelRenderer.value.render(scene, camera.value);
  controls.value.update();
  // console.log(scene)
};
const loadLabel = async (base) => { // 自定义 标签的加载
  const { data } = await proxy.$axios.post("/getPoints", {
    belongTo: path,
  });
  var radius = 0.4,
    segemnt = 16,
    rings = 16;
  var sphereMaterial = new THREE.MeshLambertMaterial({ color: "#ECF0F1" });
  var material = new THREE.LineBasicMaterial({ color: 0x26a7f2 });
  data.forEach((v, index) => {
    sphere103 = new THREE.Mesh(
      new THREE.SphereGeometry(radius, segemnt, rings),
      sphereMaterial
    );
    sphere103.name = "mesPoint";
    lineGeometry = new THREE.BufferGeometry(); //three.js 125 版本以上就废弃掉  目前支持的是 bufferGeometry

    const div = document.createElement("div");
    div.className = "tag";
    div.innerHTML = `${v.id}`;
    div.addEventListener("click", (e) => {
      console.log(e.target);
    });

    const divLabel = new CSS2DObject(div);
    divLabel.addEventListener("click", (e) => {
      consolelog(e.target);
    });
    // divLabel.visible = false;
    divLabel.position.set(v.positionX * 2, v.positionY * 2, v.positionZ * 2);

    const begin = new THREE.Vector3(v.positionX, v.positionY, v.positionZ);
    const end = new THREE.Vector3(
      v.positionX * 2,
      v.positionY * 2,
      v.positionZ * 2
    );

    lineGeometry.setFromPoints([begin, end]);

    line = new THREE.Line(lineGeometry, material, THREE.LineSegments);
    sphere103.position.set(v.positionX, v.positionY, v.positionZ);
    line.add(divLabel);
    scene.add(sphere103);
    scene.add(line);
  });
};
const init = async () => {
  createScene(); // 创建场景
  createLight(); // 创建光源
  createCamera(); // 创建相机
  createRender(); // 创建渲染器
  createControls(); // 创建控件对象
  render(); // 渲染
  loadSTL(); // 加载模型
};

function raycastMeshes(callback, raycaster) {
  let intersects = [];
  let meshes = [];
  // 获取整个场景
  let theScene = scene || new THREE.Scene();
  console.log(theScene);
  // 获取鼠标点击点的射线
  let theRaycaster = raycaster || new THREE.Raycaster();
  // 对场景及其子节点遍历
  for (let i in theScene.children) {
    // 如果场景的子节点是Group或者Scene对象
    if (
      theScene.children[i] instanceof THREE.Group ||
      theScene.children[i] instanceof THREE.Scene
    ) {
      // 场景子节点及其后代,被射线穿过的模型的数组集合
      // intersects = theRaycaster.intersectObjects(theScene.children[i].children, true)
      let rayArr = theRaycaster.intersectObjects(
        theScene.children[i].children,
        true
      );
      intersects.push(...rayArr);
      console.log(intersects);
    } else if (theScene.children[i] instanceof THREE.Mesh) {
      let rayArr = theRaycaster.intersectObjects(
        theScene.children[i].children,
        true
      );
      meshes.push(rayArr);
      console.log(meshes);
      // 如果场景的子节点是Mesh网格对象,场景子节点被射线穿过的模型的数组集合
      // intersects.push(theRaycaster.intersectObject(theScene.children[i]))
    }
  }
  intersects = filtersVisibleFalse(intersects); // 过滤掉不可见的
  // 被射线穿过的模型的数组集合
  if (intersects && intersects.length > 0) {
    return callback(intersects);
  } else {
    // this.hiddenDetailDiv()
    return null;
  }
}
function filtersVisibleFalse(arr) {
  let arrList = arr;
  if (arr && arr.length > 0) {
    arrList = [];
    for (let i = 0; i < arr.length; i++) {
      if (arr[i].object.visible) {
        arrList.push(arr[i]);
      }
    }
  }
  return arrList;
}
function clickApp(intersects) {
  if (intersects[0].object !== undefined) {
    // console.log(intersects[0].object, '这就是成功点击到的对象了~')
    console.log(intersects);
  }
}

onMounted(() => {
  init();
});
</script>
<template>
  <div>
    <div class="progress" v-show="isShow">
      <el-progress
        :text-inside="true"
        :stroke-width="24"
        :percentage="num"
        status="success"
      />
      <div class="progressInfo">{{ info }}</div>
    </div>
    <div class="main" id="container"></div>
  </div>
</template>

复制代码

最终效果: