ThreeJS组件在VueJS 2中工作,但在3中不工作

2022-04-07 00:00:00 vue.js vuejs2 three.js vuejs3

我正在将我的应用程序升级到VueJS 3。我读到你可以保留相同的组件。但我现在在控制台中有一个错误,尽管我没有更改任何东西。以下是我的组件:

<template>
  <v-container>
    <div
      @click="onClick"
      @mousemove="onMouseMove"
      id="menu3D"
      style="background-color: transparent; position: fixed; left: 20px; width:15%; height:100%;">
    </div>
    <v-row class="text-center">

      <v-col
        class="mb-5"
        cols="12"
      >
        <h2 class="headline font-weight-bold mb-3">
          Accueil
        </h2>

        <v-row justify="center">

          <p>
            Client: {{ JSON.stringify(client)}}
          </p>
          <p>
            Mouse: {{ JSON.stringify(mouse)}}
          </p>
          <p>
            Container: {{ JSON.stringify(container)}}
          </p>
        </v-row>
      </v-col>
    </v-row>
  </v-container>
</template>

<script>

import * as Three from 'three';

export default {
  name: 'Accueil',
  mounted() {
    this.init();
  },
  methods: {
    init() {
      this.createScene();
      this.createCamera();
      this.userData.formes.forEach((x) => this.createShape(x));
      this.addSpotlight(16777215);
      this.addAmbientLight();
      this.animate();
      window.addEventListener('resize', this.onResize);
    },
    onResize() {
      const container = document.getElementById('menu3D');
      this.renderer.setSize(container.clientWidth, container.clientHeight);
      this.camera.aspect = container.clientWidth / container.clientHeight;
      this.camera.updateProjectionMatrix();
    },
    createScene() {
      this.renderer = new Three.WebGLRenderer({
        antialias: true,
        alpha: true,
      });
      const container = document.getElementById('menu3D');
      this.renderer.setSize(container.clientWidth, container.clientHeight);
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setClearColor(0xffffff, 0);
      container.appendChild(this.renderer.domElement);
    },

    createCamera() {
      const container = document.getElementById('menu3D');
      this.camera = new Three.PerspectiveCamera(50,
        container.clientWidth / container.clientHeight, 0.01, 1000);
      this.camera.position.set(0, 5, 20);
      this.camera.zoom = 1;
    },

    createShape(shape) {
      const material = new Three.MeshStandardMaterial({
        color: '#0000ff',
        roughness: 1,
        metalness: 0.5,
        emissive: 0,
        depthFunc: 3,
        depthTest: true,
        depthWrite: true,
        stencilWrite: false,
        stencilWriteMask: 255,
        stencilFunc: 519,
        stencilRef: 0,
        stencilFuncMask: 255,
        stencilFail: 7680,
        stencilZFail: 7680,
        stencilZPass: 7680,
      });
      switch (shape.nom) {
        case 'Box': {
          this.geometry = new Three.BoxBufferGeometry(1.8, 1.8, 1.8);
          break;
        }
        case 'Sphere': {
          this.geometry = new Three.SphereBufferGeometry(1, 8, 6, 0, 6.283185, 0, 3.141593);
          break;
        }
        case 'Dodecahedron': {
          this.geometry = new Three.DodecahedronBufferGeometry(1.2, 0);
          break;
        }
        case 'Icosahedron': {
          this.geometry = new Three.IcosahedronBufferGeometry(1.5, 0);
          break;
        }
        default: {
          return false;
        }
      }
      this.mesh = new Three.Mesh(this.geometry, material);
      this.mesh.name = shape.nom;
      this.mesh.userData = shape.userData;
      this.mesh.receiveShadow = true;
      this.mesh.castShadow = true;
      this.mesh.position.set(0, shape.userData.position.y, 0);
      this.scene.add(this.mesh);
      return true;
    },

    addSpotlight(color) {
      const light = new Three.SpotLight(color, 2, 1000);
      light.position.set(0, 0, 30);
      this.scene.add(light);
    },

    addAmbientLight() {
      const light = new Three.AmbientLight('#fff', 0.5);
      this.scene.add(light);
    },

    verifForme(e) {
      const t = this;
      const elt = t.scene.getObjectByName(e);
      t.intersects = t.raycaster.intersectObject(elt);
      if (t.intersects.length !== 0) {
        // s'il ne figure pas dans le tableau, on le met en premier
        if (t.userData.souris.indexOf(e) < 0) {
          t.userData.souris.unshift(e);
          console.log(`${t.userData.souris[0]} survolé!`);
        }
        if (t.userData.souris[0] === e) {
          const obj = t.intersects[0].object;
          obj.material.color.set(`#${elt.userData.couleurs[1]}`);
          obj.scale.set(obj.scale.x < 1.4
            ? obj.scale.x + t.VITESSE_ZOOM
            : obj.scale.x, obj.scale.y < 1.4
            ? obj.scale.y + t.VITESSE_ZOOM
            : obj.scale.y, obj.scale.z < 1.4
            ? obj.scale.z + t.VITESSE_ZOOM
            : obj.scale.z);
          obj.rotation.y += t.VITESSE_ROTATION / t.RALENTISSEMENT;
          t.replacer(obj, obj.userData.position.y + obj.userData.decalage);
        } else {
          t.retrecir(e, elt);
        }
      } else {
        if (t.userData.souris.indexOf(e) >= 0) {
          t.userData.souris = t.userData.souris.filter((forme) => forme !== e);
        }
        t.retrecir(e, elt);
      }
    },

    onClick(event) {
      event.preventDefault();
      if (this.userData.souris.length > 0) {
        console.log(`${this.userData.souris[0]} cliqué!`);
      } else {
        console.log('clic dans le vide!');
      }
    },

    onMouseMove(event) {
      const container = document.getElementById('menu3D');
      this.mouse.x = (event.offsetX / container.clientWidth) * 2 - 1;
      this.mouse.y = -(event.offsetY / container.clientHeight) * 2 + 1;
      this.client.clientX = event.clientX;
      this.client.clientY = event.clientY;
      this.container.width = container.clientWidth;
      this.container.height = container.clientHeight;
      // console.log(JSON.stringify(this.mouse))
    },

    replacer(e, py) {
      // la ligne suivante est pour éviter les tremblements
      if (Math.abs(e.position.y - py) < 0.05) { return true; }
      let rhesus = 10 * this.VITESSE_DEPLACEMENT;
      if (this.userData.souris[0] !== e.name) { rhesus *= 3; }
      // console.log(e.name+': '+this.userData.souris[0]+' - '+rhesus)
      if (e.position.y > py) { rhesus = -1; }
      e.position.set(0, Math.trunc(10 * e.position.y + rhesus) / 10, 0);
      return true;
    },

    retrecir(n, e) {
      // on vérifie si le truc cliqué est dessus
      let dec = 0;
      const elt = this;
      if ((elt.userData.souris.length > 0)
        && (elt.userData.formes.map((x) => x.nom).indexOf(n)
        < elt.userData.formes.map((x) => x.nom).indexOf(elt.userData.souris[0]))) {
        dec = Math.trunc(10
          * e.parent.getObjectByName(elt.userData.souris[0]).userData.decalage
          * 2.1) / 10;
      }
      e.material.color.set(`#${e.userData.couleurs[0]}`);
      e.rotation.y += elt.VITESSE_ROTATION;
      e.scale.set(e.scale.x > 1
        ? e.scale.x - elt.VITESSE_ZOOM : e.scale.x,
      e.scale.y > 1
        ? e.scale.y - elt.VITESSE_ZOOM : e.scale.y,
      e.scale.z > 1
        ? e.scale.z - elt.VITESSE_ZOOM : e.scale.z);
      const newY = e.userData.position.y + dec;
      if (e.position.y !== newY) {
        elt.replacer(e, newY);
      }
    },

    animate() {
      const elt = this;
      requestAnimationFrame(this.animate);
      this.raycaster.setFromCamera(this.mouse, this.camera);
      this.userData.formes.map((x) => x.nom).forEach((x) => elt.verifForme(x));
      if (this.userData.souris.length > 0) {
        document.body.style.cursor = 'pointer';
      } else { document.body.style.cursor = 'default'; }
      this.camera.updateProjectionMatrix();
      this.renderer.render(this.scene, this.camera);
    },
  },
  data: () => ({
    container: { height: 0, width: 0 },
    client: { clientX: 0, clientY: 0 },

    scene: new Three.Scene(),
    camera: null,
    renderer: Three.WebGLRenderer,
    mesh: new Three.Mesh(),
    factor: 0,
    mouse: new Three.Vector2(1, 1),
    raycaster: new Three.Raycaster(),
    intersects: [],
    VITESSE_ROTATION: 0.05,
    VITESSE_DEPLACEMENT: 0.1,
    VITESSE_ZOOM: 0.05,
    RALENTISSEMENT: 3,
    userData: {
      souris: [],
      formes: [
        {
          nom: 'Box',
          userData: {
            position: {
              x: 0,
              y: 7.8,
              z: 0,
            },
            couleurs: [
              'aaaaaa',
              '095256',
            ],
            decalage: 0.5,
          },
        },
        {
          nom: 'Icosahedron',
          userData: {
            position: {
              x: 0,
              y: 5.5,
              z: 0,
            },
            couleurs: [
              'aaaaaa',
              '087F8C',
            ],
            decalage: 0.5,
          },
        },
        {
          nom: 'Dodecahedron',
          userData: {
            position: {
              x: 0,
              y: 3.1,
              z: 0,
            },
            couleurs: [
              'aaaaaa',
              '5AAA95',
            ],
            decalage: 0.4,
          },
        },
        {
          nom: 'Sphere',
          userData: {
            position: {
              x: 0,
              y: 1,
              z: 0,
            },
            couleurs: [
              'aaaaaa',
              '86A873',
            ],
            decalage: 0.2,
          },
        },
      ],
    },
  }),
};
</script>

以下是我在VueJS 3的控制台中遇到的错误:

three.module.js?5a89:24471 Uncaught TypeError: 'get' on proxy: property 'modelViewMatrix' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '#<Matrix4>' but got '[object Object]')
    at renderObject (three.module.js?5a89:24471)
    at renderObjects (three.module.js?5a89:24458)
    at Proxy.WebGLRenderer.render (three.module.js?5a89:24258)
    at animate (HelloWorld.vue?fdab:192)

如果有任何人有线索,请提前感谢...


解决方案

它适用于VUE 2

它在Vue 2上运行良好的原因在于Vue 2使用了基于Object.definePropertyAPI的不同反应系统。

Three.jsa lot使用相同的API向其数据结构添加一些不可写和不可配置的属性

当将具有此类属性的对象传递给Vue时(例如,通过在data中声明它),Vue只是跳过了此类属性,导致存储值/对象是非反应性的(因为Vue在呈现组件模板时无法检测属性访问)

Vue 3代理

Vue 3正在使用基于ES6proxies的新反应系统。

这是一个非常新的问题,即使已经投入了大量的精力来开发和测试它,随着人们开始迁移,这样的问题也会出现(我完全同意@Serg-Vue 3仍然是新的,除非您有技能和时间生活在边缘&您应该在从Vue 2迁移之前等待一段时间)

这个新的反应性系统不能很好地处理对象上的不可写不可配置属性-您可以在this sandbox

中找到最小的可重现示例
  1. 我知道这是个错误,is reported发送到Vue@Next Repo
  2. 沙箱使用合成API,但这并不重要,因为使用reactive()与在data()函数中声明变量相同(Vue会自动为您完成)

解决方法

如前所述,问题出在反应系统上。我不是Three.js方面的专家,但据我所知,将这三个数据结构放到Vue反应系统中没有多大意义-所有反应的要点都是检测数据更改并在需要时重新呈现模板。Three有自己的渲染系统,通常使用单个<canvas>HTML元素,因此在三个数据结构更改时触发Vue重新渲染是没有意义的...

有多种方式可以选择退出Vue Reactive:

  1. 在对象上使用Object.freeze()。在这种情况下不是很有用,但很高兴知道
  2. 不要在data()中声明变量并在created()/mounted()钩子中赋值(示例如下)。如果需要在多个方法中访问它们,可以将它们赋值到组件本身(this),如果不需要它们,可以将它们作为局部变量(const/let)
  3. 使用组合API时,不要对三个数据结构使用reactive()
注意:即使他们承认这是一个错误,修复它的唯一方法是让它持有的属性和对象保持非反应性(不在该对象周围放置代理),因此结果将与完全退出反应性相同。但使用此解决方法也可以让您的应用程序运行更快、占用内存更少,因为所有的反应都是not really that cheap

示例-创建非反应性组件属性

export default {
  data() {
    return {
    };
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      this.scene = new THREE.Scene();
      this.camera = new THREE.OrthographicCamera(...);

      this.renderer = new THREE.WebGLRenderer({ ... })
      this.geometry = new THREE.PlaneBufferGeometry(  );
      const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });

      this.plane = new THREE.Mesh(this.geometry, material);

      this.scene.add(this.plane);

      this.renderer.render(this.scene, this.camera);
    },
}

相关文章