Cesium加载zip文件中的GLTF模型

1,731 阅读2分钟

Cesium处理zip文件,解压后加载其中的GLTF模型

壹(序)

Cesium支持加载本地gltf模型文件,但需要发布模型服务时,还必须同时发布bin文件及纹理文件;现在想要实现只发布一个zip模型文件,前端读取zip文件流后,加载模型;

贰(头脑风暴)

  • 读取zip文件流可以使用zip.js

  • 使用URL.createObjectURL()生成各个文件的url;

  • 得到生成过后的url,再使用Cesium加载模型;

叁(坎坷)

  • 成功读取zip文件(下为封装好的读取zip文件方法)
    const readZipFile = (url) => {
      // 使用JSZip读取zip文件
      return new JSZip.external.Promise(function (resolve, reject) {
        // 使用JSZipUtils获取数据
        JSZipUtils.getBinaryContent(url, function (err, data) {
          // 如果读取出错
          if (err) {
            // 抛出
            reject(err);
          } else {
            // 返回数据
            resolve(data);
          }
        });
      })
        .then(function (data) {
          // 异步加载数据
          return JSZip.loadAsync(data);
        })
        .then((resp) => {
          // 返回加载好的数据
          return new Promise((resolve) => {
            resolve(resp);
          });
        });
      };
    
  • 生成各个文件对应的url
    const entry = zip.file(file);
    entry.async('blob').then((blob) => {
      // 生成url并放进fileMap
      fileMap[file] = URL.createObjectURL(blob);
    }),
    
  • 加载模型文件,读取gltf文件时需要去读取bin文件及纹理文件,但是bin文件与纹理文件的url此时是URL.createObjectURL()生成的,无法正确读取

肆(终章)

  • 遇到该问题时,我想到之前使用Three.js加载文件流的经历,但是那是Three.js可以处理每一个url,Cesium不行
  • 突发奇想,gltf本身是JSON类的文件,那么我是否可以将gltf文件读取未string类型,再转为JSON类型,再修改其中需要需要的uri,再将修改好的文件,转为Blob,再使用URL.createObjectURL()重新生成gltf文件的url;
  • 修改代码过后,发现方法可行
    // 加载gltf文件为string类型
    const res = await gltfEntry.async('string');
    // 转化为json
    const gltfJson = JSON.parse(res);
    // 修改buffers和纹理的uri
    gltfJson.buffers.forEach((item) => {
      item.uri = fileMap[item.uri];
    });
    gltfJson.images.forEach((item) => {
      item.uri = fileMap[item.uri];
    });
    // 更新fileMap中gltf文件url
    fileMap[gltfFileName] = URL.createObjectURL(
      new Blob([JSON.stringify(gltfJson)]),
    );
    
  • 使用Cesium加载模型
    viewer.entities.add({
      name: fileMap[gltfFileName],
      position: position,
      orientation: orientation,
      model: {
        uri: fileMap[gltfFileName],
        minimumPixelSize: 128,
        maximumScale: 20000,
      },
    });
    
  • 最终实现方法函数
    loadZipFile() {
      // 读取zip文件
      const zip = await readZipFile('satellite.zip');
      // 需要读取的所有文件
      const pendings = [];
      // 文件Map,文件名对应url
      const fileMap = {};
      // zip文件中的gltf文件的entry(JSZip中读取文件的方法)
      let gltfEntry = null;
      // zip文件中gltf文件名
      let gltfFileName = '';
      // 遍历zip中的所有文件,生成fileMap
      for (let file in zip.files) {
        const entry = zip.file(file);
        if (entry === null) continue;
        if (file.includes('.gltf')) {
          gltfEntry = entry;
          gltfFileName = file;
        }
        // push需要加载的文件
        pendings.push(
          entry.async('blob').then((blob) => {
            // 生成url并放进fileMap
            fileMap[file] = URL.createObjectURL(blob);
          }),
        );
      }
      // 使用JSZip封装好的Promise
      const promise = JSZip.external.Promise;
      // 等待加载文件
      await promise.all(pendings);
      // 加载gltf文件为string类型
      const res = await gltfEntry.async('string');
      // 转化为json
      const gltfJson = JSON.parse(res);
      // 修改buffers和纹理的uri
      gltfJson.buffers.forEach((item) => {
        item.uri = fileMap[item.uri];
      });
      gltfJson.images.forEach((item) => {
        item.uri = fileMap[item.uri];
      });
      // 更新fileMap中gltf文件url
      fileMap[gltfFileName] = URL.createObjectURL(
        new Blob([JSON.stringify(gltfJson)]),
      );
    
      // 加载模型
      viewer.entities.removeAll();
    
      const position = Cesium.Cartesian3.fromDegrees(
        -123.0744619,
        44.0503706,
        5000,
      );
      const heading = Cesium.Math.toRadians(135);
      const pitch = 0;
      const roll = 0;
      const hpr = new Cesium.HeadingPitchRoll(heading, pitch, roll);
      const orientation = Cesium.Transforms.headingPitchRollQuaternion(
        position,
        hpr,
      );
      const entity = viewer.entities.add({
        name: fileMap[gltfFileName],
        position: position,
        orientation: orientation,
        model: {
          uri: fileMap[gltfFileName],
          minimumPixelSize: 128,
          maximumScale: 20000,
        },
      });
      viewer.trackedEntity = entity;
    },
    

伍(预览)

效果预览