vue vite+three在线编辑模型导入导出

要实现一个类似于数字孪生的场景 可以在线、新增、删除模型 、以及编辑模型的颜色、长宽高
然后还要实现 编辑完后 保存为json数据 记录模型数据 既可以导入也可以导出

一、1.0.0版本

1.新增

先拿建议的立方体来代替模型
点击新增按钮就新增一个立方体
在这里插入图片描述

2.编辑

点击编辑按钮可以修改坐标 长宽高 颜色等等信息
在这里插入图片描述

3.导出

点击导出按钮 可以导出为json数据格式
在这里插入图片描述

在这里插入图片描述

4.导入

选择导入刚才的json文件
在这里插入图片描述
有一个bug 就是导入后颜色丢失了 点击模型 信息面板的颜色显示正常 渲染颜色丢失
在这里插入图片描述


源码

<template>
  <div id="app" @click="onAppClick">
    <div id="info">
      <button @click.stop="addBuilding">新增</button>
      <button @click.stop="showEditor">编辑</button>
      <button @click.stop="exportModelData">导出</button>
      <input type="file" @change="importModelData" ref="fileInput" />
    </div>
    <div id="editor" v-if="editorVisible" @click.stop>
      <h3>Edit Building</h3>
      <label for="color">Color:</label>
      <input type="color" id="color" v-model="selectedObjectProps.color" /><br />
      <label for="posX">Position X:</label>
      <input
        type="number"
        id="posX"
        v-model="selectedObjectProps.posX"
        step="0.1"
      /><br />
      <label for="posY">Position Y:</label>
      <input
        type="number"
        id="posY"
        v-model="selectedObjectProps.posY"
        step="0.1"
      /><br />
      <label for="posZ">Position Z:</label>
      <input
        type="number"
        id="posZ"
        v-model="selectedObjectProps.posZ"
        step="0.1"
      /><br />
      <label for="scaleX">Scale X:</label>
      <input
        type="number"
        id="scaleX"
        v-model="selectedObjectProps.scaleX"
        step="0.1"
      /><br />
      <label for="scaleY">Scale Y:</label>
      <input
        type="number"
        id="scaleY"
        v-model="selectedObjectProps.scaleY"
        step="0.1"
      /><br />
      <label for="scaleZ">Scale Z:</label>
      <input
        type="number"
        id="scaleZ"
        v-model="selectedObjectProps.scaleZ"
        step="0.1"
      /><br />
      <label for="rotX">Rotation X:</label>
      <input
        type="number"
        id="rotX"
        v-model="selectedObjectProps.rotX"
        step="0.1"
      /><br />
      <label for="rotY">Rotation Y:</label>
      <input
        type="number"
        id="rotY"
        v-model="selectedObjectProps.rotY"
        step="0.1"
      /><br />
      <label for="rotZ">Rotation Z:</label>
      <input
        type="number"
        id="rotZ"
        v-model="selectedObjectProps.rotZ"
        step="0.1"
      /><br />
      <button @click="applyEdit">保存</button>
      <button @click="deleteBuilding">删除</button>
    </div>
    <div ref="canvasContainer" style="width: 100vw; height: 100vh"></div>
  </div>
</template>

<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";

export default {
  data() {
    return {
      editorVisible: false,
      selectedObject: null,
      selectedObjectProps: {
        color: "#00ff00",
        posX: 0,
        posY: 0,
        posZ: 0,
        scaleX: 1,
        scaleY: 1,
        scaleZ: 1,
        rotX: 0,
        rotY: 0,
        rotZ: 0,
      },
      raycaster: null,
    };
  },
  mounted() {
    this.init();
    this.animate();
    window.addEventListener("resize", this.onWindowResize, false);
    this.loadModelData(); // Load saved model data on page load
  },
  methods: {
    init() {
      console.log("Initializing Three.js");

      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0xcccccc);
      this.camera = new THREE.PerspectiveCamera(
        60,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      this.camera.position.set(0, 10, 20);
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.$refs.canvasContainer.appendChild(this.renderer.domElement);
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);

      const light = new THREE.DirectionalLight(0xffffff, 1);
      light.position.set(5, 10, 7.5);
      this.scene.add(light);

      this.raycaster = new THREE.Raycaster();

      const geometry = new THREE.BoxGeometry(1, 1, 1);
      const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
      this.cube = new THREE.Mesh(geometry, material);
      this.scene.add(this.cube);
    },
    onWindowResize() {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    },
    onAppClick(event) {
      const mouse = new THREE.Vector2();
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      this.raycaster.setFromCamera(mouse, this.camera);
      const intersects = this.raycaster.intersectObjects(this.scene.children, true);
      if (intersects.length > 0) {
        this.selectedObject = intersects[0].object;
        console.log("Object selected:", this.selectedObject);
        this.showEditor();
      }
    },
    addBuilding() {
      const geometry = new THREE.BoxGeometry(1, 1, 1);
      const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
      const building = new THREE.Mesh(geometry, material);
      building.position.set(Math.random() * 10 - 5, 0.5, Math.random() * 10 - 5);
      this.scene.add(building);
    },
    showEditor() {
      if (this.selectedObject) {
        this.editorVisible = true;
        this.updateEditor(this.selectedObject);
      }
    },
    updateEditor(object) {
      this.selectedObjectProps.color = `#${object.material.color.getHexString()}`;
      this.selectedObjectProps.posX = object.position.x;
      this.selectedObjectProps.posY = object.position.y;
      this.selectedObjectProps.posZ = object.position.z;
      this.selectedObjectProps.scaleX = object.scale.x;
      this.selectedObjectProps.scaleY = object.scale.y;
      this.selectedObjectProps.scaleZ = object.scale.z;
      this.selectedObjectProps.rotX = object.rotation.x;
      this.selectedObjectProps.rotY = object.rotation.y;
      this.selectedObjectProps.rotZ = object.rotation.z;
    },
    applyEdit() {
      if (this.selectedObject) {
        const color = this.selectedObjectProps.color;
        this.selectedObject.material.color.set(color);
        this.selectedObject.position.set(
          parseFloat(this.selectedObjectProps.posX),
          parseFloat(this.selectedObjectProps.posY),
          parseFloat(this.selectedObjectProps.posZ)
        );
        this.selectedObject.scale.set(
          parseFloat(this.selectedObjectProps.scaleX),
          parseFloat(this.selectedObjectProps.scaleY),
          parseFloat(this.selectedObjectProps.scaleZ)
        );
        this.selectedObject.rotation.set(
          parseFloat(this.selectedObjectProps.rotX),
          parseFloat(this.selectedObjectProps.rotY),
          parseFloat(this.selectedObjectProps.rotZ)
        );
      }
    },
    deleteBuilding() {
      if (this.selectedObject) {
        this.scene.remove(this.selectedObject);
        this.selectedObject = null;
        this.editorVisible = false;
      }
    },
    animate() {
      requestAnimationFrame(this.animate);
      this.renderer.render(this.scene, this.camera);
      this.controls.update();
    },
    exportModelData() {
      const modelData = {
        objects: this.scene.children
          .filter((obj) => obj instanceof THREE.Mesh) // 过滤出是 Mesh 对象的物体
          .map((obj) => ({
            position: obj.position.toArray(),
            scale: obj.scale.toArray(),
            rotation: obj.rotation.toArray(),
            color: `#${obj.material.color.getHexString()}`,
          })),
      };
      const jsonData = JSON.stringify(modelData);
      const blob = new Blob([jsonData], { type: "application/json" });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.style.display = "none";
      a.href = url;
      a.download = "model_data.json";
      document.body.appendChild(a);
      a.click();
      URL.revokeObjectURL(url);
      document.body.removeChild(a);
    },
    importModelData(event) {
      const file = event.target.files[0];
      if (file) {
        const reader = new FileReader();
        reader.onload = () => {
          try {
            const data = JSON.parse(reader.result);
            console.log("Imported data:", data); // 输出导入的完整数据,确保格式和内容正确

            this.clearScene();
            data.objects.forEach((objData, index) => {
              const geometry = new THREE.BoxGeometry();

              // 设置默认颜色为红色
              const color = new THREE.Color(0xff0000); // 红色

              // 如果数据中有颜色字段并且是合法的颜色值,则使用数据中的颜色
              if (objData.color && typeof objData.color === "string") {
                try {
                  color.set(objData.color);
                } catch (error) {
                  console.error(`Error parsing color for object ${index}:`, error);
                }
              } else {
                console.warn(`Invalid color value for object ${index}:`, objData.color);
              }

              const material = new THREE.MeshStandardMaterial({
                color: color,
                metalness: 0.5, // 示例中的金属度设置为0.5,可以根据需求调整
                roughness: 0.8, // 示例中的粗糙度设置为0.8,可以根据需求调整
              });
              const object = new THREE.Mesh(geometry, material);
              object.position.fromArray(objData.position);
              object.scale.fromArray(objData.scale);
              object.rotation.fromArray(objData.rotation);
              this.scene.add(object);
            });
          } catch (error) {
            console.error("Error importing model data:", error);
          }
        };
        reader.readAsText(file);
      }
    },
    clearScene() {
      while (this.scene.children.length > 0) {
        this.scene.remove(this.scene.children[0]);
      }
    },
    saveModelData() {
      const modelData = {
        objects: this.scene.children.map((obj) => ({
          position: obj.position.toArray(),
          scale: obj.scale.toArray(),
          rotation: obj.rotation.toArray(),
          color: `#${obj.material.color.getHexString()}`,
        })),
      };
      localStorage.setItem("modelData", JSON.stringify(modelData));
    },
    loadModelData() {
      const savedData = localStorage.getItem("modelData");
      if (savedData) {
        try {
          const data = JSON.parse(savedData);
          this.clearScene();
          data.objects.forEach((objData) => {
            const geometry = new THREE.BoxGeometry();
            const material = new THREE.MeshStandardMaterial({
              color: parseInt(objData.color.replace("#", "0x"), 16),
            });
            const object = new THREE.Mesh(geometry, material);
            object.position.fromArray(objData.position);
            object.scale.fromArray(objData.scale);
            object.rotation.fromArray(objData.rotation);
            this.scene.add(object);
          });
        } catch (error) {
          console.error("Error loading model data from localStorage:", error);
        }
      }
    },
  },
};
</script>

<style>
body {
  margin: 0;
  overflow: hidden;
}

canvas {
  display: block;
}

#info {
  position: absolute;
  top: 10px;
  left: 10px;
  background: rgba(255, 255, 255, 0.8);
  padding: 10px;
}

#editor {
  position: absolute;
  top: 100px;
  left: 10px;
  background: rgba(255, 255, 255, 0.8);
  padding: 10px;
}
</style>

二、2.0.0版本

在这里插入图片描述

1. 修复模型垂直方向放置时 模型会重合

4. 修复了导出导入功能 现在是1:1导出导入

5. 新增一个地面 视角看不到地下 设置了禁止编辑地面 地面设置为圆形

6. 新增功能 可选择基本圆形 方形 圆柱形等模型以及可放置自己的模型文件

7. 优化面板样式

<template>
  <div id="app" @click="onAppClick">
    <div id="info">
      <button @click.stop="toggleBuildingMode">
        {{ buildingMode ? "关闭建造模式" : "开启建造模式" }}
      </button>
      <button @click.stop="showEditor">编辑所选模型</button>
      <button @click.stop="exportModelData">导出模型数据</button>
      <input type="file" @change="importModelData" ref="fileInput" />
      <input type="file" @change="importCustomModel" ref="customModelInput" />
      <label for="modelType">模型类型:</label>
      <select v-model="selectedModelType">
        <option value="box">立方体</option>
        <option value="sphere">球体</option>
        <option value="cylinder">圆柱体</option>
        <option value="custom">自定义模型</option>
      </select>
    </div>
    <div id="editor" v-if="editorVisible" @click.stop>
      <h3>编辑模型</h3>
      <div class="form-group">
        <label for="color">颜色:</label>
        <input type="color" id="color" v-model="selectedObjectProps.color" /><br />
      </div>
      <div class="form-group">
        <label for="posX">位置 X:</label>
        <input type="number" id="posX" v-model="selectedObjectProps.posX" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="posY">位置 Y:</label>
        <input type="number" id="posY" v-model="selectedObjectProps.posY" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="posZ">位置 Z:</label>
        <input type="number" id="posZ" v-model="selectedObjectProps.posZ" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="scaleX">缩放 X:</label>
        <input type="number" id="scaleX" v-model="selectedObjectProps.scaleX" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="scaleY">缩放 Y:</label>
        <input type="number" id="scaleY" v-model="selectedObjectProps.scaleY" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="scaleZ">缩放 Z:</label>
        <input type="number" id="scaleZ" v-model="selectedObjectProps.scaleZ" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="rotX">旋转 X:</label>
        <input type="number" id="rotX" v-model="selectedObjectProps.rotX" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="rotY">旋转 Y:</label>
        <input type="number" id="rotY" v-model="selectedObjectProps.rotY" step="0.1" /><br />
      </div>
      <div class="form-group">
        <label for="rotZ">旋转 Z:</label>
        <input type="number" id="rotZ" v-model="selectedObjectProps.rotZ" step="0.1" /><br />
      </div>
      <button @click="applyEdit">应用</button>
      <button @click="deleteBuilding">删除</button>
    </div>
    <div ref="canvasContainer" style="width: 100vw; height: 100vh"></div>
  </div>
</template>

<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

export default {
  data() {
    return {
      editorVisible: false,
      selectedObject: null,
      selectedObjectProps: {
        color: "#000",
        posX: 0,
        posY: 0,
        posZ: 0,
        scaleX: 1,
        scaleY: 1,
        scaleZ: 1,
        rotX: 0,
        rotY: 0,
        rotZ: 0,
      },
      raycaster: null,
      buildingMode: false,
      selectedModelType: "box",
      customModel: null,
    };
  },
  mounted() {
    this.init();
    this.animate();
    window.addEventListener("resize", this.onWindowResize, false);
  },
  methods: {
    animate() {
      requestAnimationFrame(this.animate);
      this.renderer.render(this.scene, this.camera);
      this.controls.update();
    },
    init() {
      console.log("Initializing Three.js");

      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color('0xcccccc');
      this.camera = new THREE.PerspectiveCamera(
        60,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      this.camera.position.set(0, 10, 20);
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.$refs.canvasContainer.appendChild(this.renderer.domElement);
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);

      this.controls.minDistance = 10;
      this.controls.maxDistance = 50;
      this.controls.maxPolarAngle = Math.PI / 2;

      const planeGeometry = new THREE.CircleGeometry(100, 32);
      const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x999999 });
      const plane = new THREE.Mesh(planeGeometry, planeMaterial);
      plane.rotation.x = -Math.PI / 2;
      plane.userData.isGround = true;
      this.scene.add(plane);

      const light = new THREE.DirectionalLight(0xffffff, 1);
      light.position.set(5, 10, 7.5);
      this.scene.add(light);

      this.raycaster = new THREE.Raycaster();
    },
    onWindowResize() {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    },
    onAppClick(event) {
      const mouse = new THREE.Vector2();
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      this.raycaster.setFromCamera(mouse, this.camera);
      const intersects = this.raycaster.intersectObjects(this.scene.children, true);

      if (this.buildingMode && intersects.length > 0) {
        const intersect = intersects[0];
        const point = intersect.point;
        if (intersect.object.userData.isGround) {
          if (this.isOverlapping(point.x, point.z)) {
            this.stackBuilding(point.x, point.z);
          } else {
            this.addBuilding(point.x, 0, point.z);
          }
        } else {
          const stackHeight = intersect.object.position.y + intersect.object.scale.y;
          this.addBuilding(intersect.object.position.x, stackHeight, intersect.object.position.z);
        }
      } else if (intersects.length > 0) {
        this.selectedObject = intersects[0].object;
        console.log("Object selected:", this.selectedObject);
        this.showEditor();
      }
    },
    isOverlapping(x, z) {
      const threshold = 1;
      for (let obj of this.scene.children) {
        if (
          Math.abs(obj.position.x - x) < threshold &&
          Math.abs(obj.position.z - z) < threshold &&
          !obj.userData.isGround
        ) {
          return true;
        }
      }
      return false;
    },
    stackBuilding(x, z) {
      let maxY = 0;
      this.scene.children.forEach((obj) => {
        if (
          Math.abs(obj.position.x - x) < 1 &&
          Math.abs(obj.position.z - z) < 1 &&
          !obj.userData.isGround &&
          obj.position.y + obj.scale.y > maxY
        ) {
          maxY = obj.position.y + obj.scale.y;
        }
      });
      this.addBuilding(x, maxY, z);
    },
    addBuilding(x, y, z) {
      let geometry;
      switch (this.selectedModelType) {
        case "sphere":
          geometry = new THREE.SphereGeometry(0.5, 32, 32);
          break;
        case "cylinder":
          geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
          break;
        case "custom":
          if (this.customModel) {
            this.loadCustomModel(x, y, z);
            return;
          }
          break;
        case "box":
        default:
          geometry = new THREE.BoxGeometry(1, 1, 1);
          break;
      }

      if (geometry) {
        const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
        const building = new THREE.Mesh(geometry, material);
        building.position.set(x, y, z);
        this.scene.add(building);
      }
    },
    loadCustomModel(x, y, z) {
      const loader = new GLTFLoader();
      loader.load(
        this.customModel,
        (gltf) => {
          const object = gltf.scene;
          object.position.set(x, y, z);
          this.scene.add(object);
        },
        undefined,
        (error) => {
          console.error("An error happened while loading the custom model", error);
        }
      );
    },
    importCustomModel(event) {
      const file = event.target.files[0];
      this.customModel = URL.createObjectURL(file);
    },
    showEditor() {
      if (this.selectedObject) {
        this.selectedObjectProps.color = "#" + this.selectedObject.material.color.getHexString();
        this.selectedObjectProps.posX = this.selectedObject.position.x;
        this.selectedObjectProps.posY = this.selectedObject.position.y;
        this.selectedObjectProps.posZ = this.selectedObject.position.z;
        this.selectedObjectProps.scaleX = this.selectedObject.scale.x;
        this.selectedObjectProps.scaleY = this.selectedObject.scale.y;
        this.selectedObjectProps.scaleZ = this.selectedObject.scale.z;
        this.selectedObjectProps.rotX = this.selectedObject.rotation.x;
        this.selectedObjectProps.rotY = this.selectedObject.rotation.y;
        this.selectedObjectProps.rotZ = this.selectedObject.rotation.z;
      }
      this.editorVisible = true;
    },
    applyEdit() {
      if (this.selectedObject) {
        this.selectedObject.material.color.set(this.selectedObjectProps.color);
        this.selectedObject.position.set(
          this.selectedObjectProps.posX,
          this.selectedObjectProps.posY,
          this.selectedObjectProps.posZ
        );
        this.selectedObject.scale.set(
          this.selectedObjectProps.scaleX,
          this.selectedObjectProps.scaleY,
          this.selectedObjectProps.scaleZ
        );
        this.selectedObject.rotation.set(
          this.selectedObjectProps.rotX,
          this.selectedObjectProps.rotY,
          this.selectedObjectProps.rotZ
        );
      }
      this.editorVisible = false;
    },
    deleteBuilding() {
      if (this.selectedObject) {
        this.scene.remove(this.selectedObject);
        this.selectedObject.geometry.dispose();
        this.selectedObject.material.dispose();
        this.selectedObject = null;
        this.editorVisible = false;
      }
    },
    toggleBuildingMode() {
      this.buildingMode = !this.buildingMode;
    },
    exportModelData() {
      const modelData = this.scene.children
        .filter((obj) => obj.type === "Mesh" && !obj.userData.isGround)
        .map((obj) => ({
          type: obj.geometry.type,
          position: obj.position,
          rotation: obj.rotation,
          scale: obj.scale,
          color: obj.material.color.getHex(),
        }));
      const blob = new Blob([JSON.stringify(modelData)], { type: "application/json" });
      const link = document.createElement("a");
      link.href = URL.createObjectURL(blob);
      link.download = "modelData.json";
      link.click();
    },
    importModelData(event) {
      const file = event.target.files[0];
      const reader = new FileReader();
      reader.onload = (e) => {
        const modelData = JSON.parse(e.target.result);
        this.loadModelData(modelData);
      };
      reader.readAsText(file);
    },
    loadModelData(modelData = null) {
      if (!modelData) {
        return;
      }
      modelData.forEach((data) => {
        let geometry;
        switch (data.type) {
          case "SphereGeometry":
            geometry = new THREE.SphereGeometry(0.5, 32, 32);
            break;
          case "CylinderGeometry":
            geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
            break;
          case "BoxGeometry":
          default:
            geometry = new THREE.BoxGeometry(1, 1, 1);
            break;
        }
        const material = new THREE.MeshStandardMaterial({ color: data.color });
        const object = new THREE.Mesh(geometry, material);
        object.position.copy(data.position);
        object.rotation.copy(data.rotation);
        object.scale.copy(data.scale);
        this.scene.add(object);
      });
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialias;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#info {
  position: absolute;
  top: 10px;
  left: 10px;
  background: rgba(255, 255, 255, 0.8);
  padding: 10px;
  border-radius: 5px;
}

#editor {
  position: absolute;
  top: 50px;
  right: 10px;
  background: rgba(255, 255, 255, 0.9);
  padding: 10px;
  border-radius: 5px;
  z-index: 1000;
  width: 200px;
}

#editor .form-group {
  margin-bottom: 10px;
}

#editor label {
  display: block;
  margin-bottom: 5px;
}

#editor input {
  width: 100%;
}
</style>

二、2.0.1版本

在这里插入图片描述

1. 修复了删除模型无效

2. 修复了设置地面网格后删除自定义模型无效

3. 加入简易城市json模型

<template>
  <div id="app" @click="onAppClick">
    <div id="info">
      <button @click.stop="toggleBuildingMode">
        {{ buildingMode ? "关闭建造模式" : "开启建造模式" }}
      </button>
      <button @click.stop="showEditor">编辑所选模型</button>
      <button @click.stop="exportModelData">导出模型数据</button>
      <input type="file" @change="importModelData" ref="fileInput" />
      <input type="file" @change="importCustomModel" ref="customModelInput" />
      <label for="modelType">模型类型:</label>
      <select v-model="selectedModelType">
        <option value="box">立方体</option>
        <option value="sphere">球体</option>
        <option value="cylinder">圆柱体</option>
        <option value="custom">自定义模型</option>
        <option value="city">城市模型</option>
      </select>
    </div>
    <div id="editor" v-if="editorVisible" @click.stop>
      <h3>编辑模型</h3>
      <div class="form-group">
        <label for="color">颜色:</label>
        <input type="color" id="color" v-model="selectedObjectProps.color" /><br />
      </div>
      <div class="form-group">
        <label for="posX">位置 X:</label>
        <input
          type="number"
          id="posX"
          v-model="selectedObjectProps.posX"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="posY">位置 Y:</label>
        <input
          type="number"
          id="posY"
          v-model="selectedObjectProps.posY"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="posZ">位置 Z:</label>
        <input
          type="number"
          id="posZ"
          v-model="selectedObjectProps.posZ"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="scaleX">缩放 X:</label>
        <input
          type="number"
          id="scaleX"
          v-model="selectedObjectProps.scaleX"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="scaleY">缩放 Y:</label>
        <input
          type="number"
          id="scaleY"
          v-model="selectedObjectProps.scaleY"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="scaleZ">缩放 Z:</label>
        <input
          type="number"
          id="scaleZ"
          v-model="selectedObjectProps.scaleZ"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="rotX">旋转 X:</label>
        <input
          type="number"
          id="rotX"
          v-model="selectedObjectProps.rotX"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="rotY">旋转 Y:</label>
        <input
          type="number"
          id="rotY"
          v-model="selectedObjectProps.rotY"
          step="0.1"
        /><br />
      </div>
      <div class="form-group">
        <label for="rotZ">旋转 Z:</label>
        <input
          type="number"
          id="rotZ"
          v-model="selectedObjectProps.rotZ"
          step="0.1"
        /><br />
      </div>
      <button @click="applyEdit">应用</button>
      <button @click="deleteBuilding">删除</button>
    </div>
    <div ref="canvasContainer" style="width: 100vw; height: 100vh"></div>
  </div>
</template>

<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";

export default {
  data() {
    return {
      editorVisible: false,
      selectedObject: null,
      selectedObjectProps: {
        color: "#000",
        posX: 0,
        posY: 0,
        posZ: 0,
        scaleX: 1,
        scaleY: 1,
        scaleZ: 1,
        rotX: 0,
        rotY: 0,
        rotZ: 0,
      },
      raycaster: null,
      buildingMode: false,
      selectedModelType: "box",
      customModel: null,
    };
  },
  mounted() {
    this.init();
    this.animate();
    window.addEventListener("resize", this.onWindowResize, false);
  },
  methods: {
    animate() {
      requestAnimationFrame(this.animate);
      this.renderer.render(this.scene, this.camera);
      this.controls.update();
    },
    init() {
      console.log("Initializing Three.js");

      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0xcccccc);
      this.camera = new THREE.PerspectiveCamera(
        60,
        window.innerWidth / window.innerHeight,
        0.1,
        1000
      );
      this.camera.position.set(0, 10, 20);

      // 创建渲染器
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      this.$refs.canvasContainer.appendChild(this.renderer.domElement);
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);

      this.controls.minDistance = 10;
      this.controls.maxDistance = 50;
      this.controls.maxPolarAngle = Math.PI / 2;

      // 创建平面地面
      const planeSize = 1000;
      const divisions = 100;
      const planeGeometry = new THREE.PlaneGeometry(
        planeSize,
        planeSize,
        divisions,
        divisions
      );
      const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x808080 });
      const plane = new THREE.Mesh(planeGeometry, planeMaterial);
      plane.rotation.x = -Math.PI / 2;
      plane.position.y = -0.5;
      plane.userData.isGround = true;
      this.scene.add(plane);

      // 添加网格线效果
      const grid = new THREE.GridHelper(planeSize, divisions, 0xffffff, 0xffffff);
      grid.material.opacity = 0.2;
      grid.material.transparent = true;
      this.scene.add(grid);

      // 添加光源
      const light = new THREE.DirectionalLight(0xffffff, 1);
      light.position.set(5, 10, 7.5);
      this.scene.add(light);

      // 初始化射线投射器
      this.raycaster = new THREE.Raycaster();

      // 监听窗口变化
      window.addEventListener("resize", this.onWindowResize, false);
    },

    onWindowResize() {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    },
    onAppClick(event) {
      const mouse = new THREE.Vector2();
      mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      this.raycaster.setFromCamera(mouse, this.camera);
      console.log(this.scene.children);
      // 过滤掉不需要交互的对象
      const interactiveObjects = this.scene.children.filter((obj) => {
        return obj.userData.isGround || obj.type === "Mesh" || obj.type === "Group"; // 这里可以根据实际情况添加过滤条件
      });
      const intersects = this.raycaster.intersectObjects(interactiveObjects, true);
      if (this.buildingMode && intersects.length > 0) {
        const intersect = intersects[0];
        const point = intersect.point;
        if (intersect.object.userData.isGround) {
          if (this.isOverlapping(point.x, point.z)) {
            this.stackBuilding(point.x, point.z);
            console.log("Stacking building at", point.x, point.z);
          } else {
            this.addBuilding(point.x, 0, point.z);
            console.log("Adding building at", point.x, 0, point.z);
          }
        } else {
          const stackHeight = intersect.object.position.y + intersect.object.scale.y;
          this.addBuilding(
            intersect.object.position.x,
            stackHeight,
            intersect.object.position.z
          );
          console.log(
            "Adding building on top of another object at",
            intersect.object.position.x,
            stackHeight,
            intersect.object.position.z
          );
        }
      } else if (intersects.length > 0) {
        this.selectedObject = intersects[0].object;
        console.log("Object selected:", this.selectedObject);
        this.showEditor();
      } else {
        console.log("Clicked on empty space");
      }
    },

    isOverlapping(x, z) {
      const threshold = 1;
      for (let obj of this.scene.children) {
        if (
          Math.abs(obj.position.x - x) < threshold &&
          Math.abs(obj.position.z - z) < threshold &&
          !obj.userData.isGround
        ) {
          return true;
        }
      }
      return false;
    },
    stackBuilding(x, z) {
      let maxY = 0;
      this.scene.children.forEach((obj) => {
        if (
          Math.abs(obj.position.x - x) < 1 &&
          Math.abs(obj.position.z - z) < 1 &&
          !obj.userData.isGround &&
          obj.position.y + obj.scale.y > maxY
        ) {
          maxY = obj.position.y + obj.scale.y;
        }
      });
      this.addBuilding(x, maxY, z);
    },
    addBuilding(x, y, z) {
      let geometry;
      switch (this.selectedModelType) {
        case "sphere":
          geometry = new THREE.SphereGeometry(0.5, 32, 32);
          break;
        case "cylinder":
          geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
          break;
        case "custom":
          if (this.customModel) {
            this.loadCustomModel(x, y, z);
            return;
          }
          break;
        case "city":
          this.loadCityModel(x, y, z);
          return;
        case "box":
        default:
          geometry = new THREE.BoxGeometry(1, 1, 1);
          break;
      }

      if (geometry) {
        const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
        const building = new THREE.Mesh(geometry, material);
        building.position.set(x, y, z);
        this.scene.add(building);
      }
    },
    // 下
    // 扩展加载和放置摄像头模型的方法
    addCamera(x, y, z) {
      const cameraGeometry = new THREE.BoxGeometry(0.2, 0.2, 0.1); // 摄像头的尺寸
      const cameraMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 }); // 红色材质表示摄像头
      const camera = new THREE.Mesh(cameraGeometry, cameraMaterial);
      camera.position.set(x, y, z);
      this.scene.add(camera);
    },

    // 创建摄像头方法
    createCamera(cameraData, x, y, z) {
      cameraData?.forEach((data) => {
        const position = new THREE.Vector3(
          data.position.x + x,
          data.position.y + y,
          data.position.z + z
        );
        this.addCamera(position.x, position.y, position.z);
      });
    },

    // 修改加载城市模型方法,包含摄像头的加载

    parseCityData(cityData) {
      const buildingGroup = new THREE.Group();
      buildingGroup.name = cityData.buildingName;

      cityData.floors.forEach((floor) => {
        const floorGroup = new THREE.Group();
        floorGroup.position.set(floor.position.x, floor.position.y, floor.position.z);

        floor.rooms.forEach((room) => {
          const roomGroup = new THREE.Group();
          roomGroup.position.set(room.position.x, room.position.y, room.position.z);

          room.objects.forEach((object) => {
            let mesh;
            const material = new THREE.MeshStandardMaterial({ color: object.color });
            switch (object.type) {
              case "floor":
                const floorGeometry = new THREE.BoxGeometry(
                  object.width,
                  object.height,
                  object.depth
                );
                mesh = new THREE.Mesh(floorGeometry, material);
                mesh.position.set(
                  object.position.x,
                  object.position.y,
                  object.position.z
                );
                roomGroup.add(mesh);
                break;
              case "wall":
                const wallGeometry = new THREE.BoxGeometry(
                  object.width,
                  object.height,
                  object.depth
                );
                mesh = new THREE.Mesh(wallGeometry, material);
                mesh.position.set(
                  object.position.x,
                  object.position.y,
                  object.position.z
                );
                roomGroup.add(mesh);
                break;
            }
          });

          // 处理门和窗户的布尔运算
          let wallMeshes = roomGroup.children.filter(
            (obj) => obj.material && obj.material.color.getHex() === 0xffffff
          );

          room.objects.forEach((object) => {
            let holeMesh;
            switch (object.type) {
              case "door":
                const doorGeometry = new THREE.BoxGeometry(1, 2, 0.1);
                holeMesh = new THREE.Mesh(doorGeometry);
                holeMesh.position.set(
                  object.position.x,
                  object.position.y + 1,
                  object.position.z
                );
                wallMeshes.forEach((wallMesh) => {
                  let csgWall = THREE.CSG.fromMesh(wallMesh);
                  let csgHole = THREE.CSG.fromMesh(holeMesh);
                  let csgResult = csgWall.subtract(csgHole);
                  let newWallMesh = THREE.CSG.toMesh(csgResult, wallMesh.matrix);
                  roomGroup.remove(wallMesh);
                  roomGroup.add(newWallMesh);
                });
                const doorMaterial = new THREE.MeshStandardMaterial({
                  color: object.color,
                });
                const doorMesh = new THREE.Mesh(doorGeometry, doorMaterial);
                doorMesh.position.set(
                  object.position.x,
                  object.position.y + 1,
                  object.position.z
                );
                roomGroup.add(doorMesh);
                break;
              case "window":
                const windowGeometry = new THREE.BoxGeometry(1.5, 1.5, 0.1);
                holeMesh = new THREE.Mesh(windowGeometry);
                holeMesh.position.set(
                  object.position.x,
                  object.position.y + 1.5,
                  object.position.z
                );
                wallMeshes.forEach((wallMesh) => {
                  let csgWall = THREE.CSG.fromMesh(wallMesh);
                  let csgHole = THREE.CSG.fromMesh(holeMesh);
                  let csgResult = csgWall.subtract(csgHole);
                  let newWallMesh = THREE.CSG.toMesh(csgResult, wallMesh.matrix);
                  roomGroup.remove(wallMesh);
                  roomGroup.add(newWallMesh);
                });
                const windowMaterial = new THREE.MeshStandardMaterial({
                  color: object.color,
                });
                const windowMesh = new THREE.Mesh(windowGeometry, windowMaterial);
                windowMesh.position.set(
                  object.position.x,
                  object.position.y + 1.5,
                  object.position.z
                );
                roomGroup.add(windowMesh);
                break;
              case "desk":
                const deskGeometry = new THREE.BoxGeometry(2, 1, 1);
                const deskMesh = new THREE.Mesh(deskGeometry, material);
                deskMesh.position.set(
                  object.position.x,
                  object.position.y + 0.5,
                  object.position.z
                );
                roomGroup.add(deskMesh);
                break;
              case "chair":
                const chairGeometry = new THREE.BoxGeometry(1, 1, 1);
                const chairMesh = new THREE.Mesh(chairGeometry, material);
                chairMesh.position.set(
                  object.position.x,
                  object.position.y + 0.5,
                  object.position.z
                );
                roomGroup.add(chairMesh);
                break;
              case "computer":
                const computerGeometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
                const computerMesh = new THREE.Mesh(computerGeometry, material);
                computerMesh.position.set(
                  object.position.x,
                  object.position.y + 0.25,
                  object.position.z
                );
                roomGroup.add(computerMesh);
                break;
              case "camera":
                const cameraGeometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
                const cameraMesh = new THREE.Mesh(cameraGeometry, material);
                cameraMesh.position.set(
                  object.position.x,
                  object.position.y + 0.1,
                  object.position.z
                );
                roomGroup.add(cameraMesh);
                break;
            }
          });

          floorGroup.add(roomGroup);
        });

        buildingGroup.add(floorGroup);
      });

      this.scene.add(buildingGroup);
    },
    loadCityModel(x, y, z) {
      const cityModelUrl = "./cityModel.json";
      fetch(cityModelUrl)
        .then((response) => {
          if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
          }
          return response.json();
        })
        .then((data) => {
          this.createBuilding(data, x, y, z);
          this.createCamera(data.cameras, x, y, z); // 加载摄像头模型
        })
        .catch((error) => {
          console.error("Failed to load city model:", error);
          alert(`Failed to load city model: ${error.message}`);
        });
    },

    createBuilding(buildingData, x, y, z) {
      buildingData.floors.forEach((floor) => {
        floor.rooms.forEach((room) => {
          room.objects.forEach((objectData) => {
            let geometry, material;

            switch (objectData.type) {
              case "floor":
                geometry = new THREE.BoxGeometry(
                  objectData.width,
                  objectData.height,
                  objectData.depth
                );
                material = new THREE.MeshStandardMaterial({ color: objectData.color });
                break;
              case "wall":
                geometry = new THREE.BoxGeometry(
                  objectData.width,
                  objectData.height,
                  objectData.depth
                );
                material = new THREE.MeshStandardMaterial({ color: objectData.color });
                break;
              case "door":
                geometry = new THREE.BoxGeometry(2, 4, 0.1); // 假设门尺寸为2x4
                material = new THREE.MeshStandardMaterial({ color: objectData.color });

                // 计算门的位置,需要考虑房间和楼层的偏移
                const doorPosition = new THREE.Vector3(
                  objectData.position.x + room.position.x + floor.position.x + x,
                  objectData.position.y + room.position.y + floor.position.y + y,
                  objectData.position.z + room.position.z + floor.position.z + z
                );
                this.addDoor(geometry, material, doorPosition);
                break;
              case "window":
                geometry = new THREE.BoxGeometry(2, 2, 0.1); // 假设窗户尺寸为2x2
                material = new THREE.MeshStandardMaterial({
                  color: objectData.color,
                  transparent: true,
                  opacity: 0.5,
                });

                // 计算窗户的位置,需要考虑房间和楼层的偏移
                const windowPosition = new THREE.Vector3(
                  objectData.position.x + room.position.x + floor.position.x + x,
                  objectData.position.y + room.position.y + floor.position.y + y,
                  objectData.position.z + room.position.z + floor.position.z + z
                );
                this.addWindow(geometry, material, windowPosition);
                break;
              case "desk":
                geometry = new THREE.BoxGeometry(1, 0.5, 0.5); // 假设桌子尺寸为1x0.5x0.5
                material = new THREE.MeshStandardMaterial({ color: objectData.color });
                break;
              case "chair":
                geometry = new THREE.BoxGeometry(0.5, 1, 0.5); // 假设椅子尺寸为0.5x1x0.5
                material = new THREE.MeshStandardMaterial({ color: objectData.color });
                break;
              case "computer":
                geometry = new THREE.BoxGeometry(0.3, 0.3, 0.3); // 假设电脑尺寸为0.3x0.3x0.3
                material = new THREE.MeshStandardMaterial({ color: objectData.color });
                break;
              case "camera":
                const cameraGeometry = new THREE.CylinderGeometry(0.2, 0.2, 0.5, 32);
                const cameraMaterial = new THREE.MeshStandardMaterial({
                  color: objectData.color,
                });
                const cameraMesh = new THREE.Mesh(cameraGeometry, cameraMaterial);
                cameraMesh.position.set(
                  objectData.position.x +
                    room.position.x +
                    floor.position.x +
                    buildingData.position.x +
                    x,
                  objectData.position.y +
                    room.position.y +
                    floor.position.y +
                    buildingData.position.y +
                    y,
                  objectData.position.z +
                    room.position.z +
                    floor.position.z +
                    buildingData.position.z +
                    z
                );
                this.scene.add(cameraMesh);
                break;

              default:
                throw new Error(`Unsupported object type: ${objectData.type}`);
            }

            if (
              geometry &&
              material &&
              objectData.type !== "door" &&
              objectData.type !== "window"
            ) {
              const object = new THREE.Mesh(geometry, material);
              object.position.set(
                objectData.position.x +
                  room.position.x +
                  floor.position.x +
                  buildingData.position.x +
                  x,
                objectData.position.y +
                  room.position.y +
                  floor.position.y +
                  buildingData.position.y +
                  y,
                objectData.position.z +
                  room.position.z +
                  floor.position.z +
                  buildingData.position.z +
                  z
              );

              this.scene.add(object);
            }
          });
        });
      });
    },

    addDoor(geometry, material, position) {
      // 调整门的位置,确保门中心在所需位置
      position.y += geometry.parameters.height / 2; // 将门的中心移到地板上

      const door = new THREE.Mesh(geometry, material);
      door.position.copy(position);
      this.scene.add(door);
    },

    addWindow(geometry, material, position) {
      // 创建透明材质
      material.transparent = true;
      material.opacity = 0.5; // 设置透明度,0为完全透明,1为完全不透明

      const windowMesh = new THREE.Mesh(geometry, material);
      windowMesh.position.copy(position);
      this.scene.add(windowMesh);
    },

    // 上
    loadCustomModel(x, y, z) {
      const loader = new GLTFLoader();
      loader.load(
        this.customModel,
        (gltf) => {
          const object = gltf.scene;
          object.position.set(x, y, z);
          this.scene.add(object);
        },
        undefined,
        (error) => {
          console.error("An error happened while loading the custom model", error);
        }
      );
    },
    importCustomModel(event) {
      const file = event.target.files[0];
      this.customModel = URL.createObjectURL(file);
    },
    showEditor() {
      if (this.selectedObject) {
        this.selectedObjectProps.color =
          "#" + this.selectedObject.material.color.getHexString();
        this.selectedObjectProps.posX = this.selectedObject.position.x;
        this.selectedObjectProps.posY = this.selectedObject.position.y;
        this.selectedObjectProps.posZ = this.selectedObject.position.z;
        this.selectedObjectProps.scaleX = this.selectedObject.scale.x;
        this.selectedObjectProps.scaleY = this.selectedObject.scale.y;
        this.selectedObjectProps.scaleZ = this.selectedObject.scale.z;
        this.selectedObjectProps.rotX = this.selectedObject.rotation.x;
        this.selectedObjectProps.rotY = this.selectedObject.rotation.y;
        this.selectedObjectProps.rotZ = this.selectedObject.rotation.z;
      }
      this.editorVisible = true;
    },
    applyEdit() {
      if (this.selectedObject) {
        this.selectedObject.material.color.set(this.selectedObjectProps.color);
        this.selectedObject.position.set(
          this.selectedObjectProps.posX,
          this.selectedObjectProps.posY,
          this.selectedObjectProps.posZ
        );
        this.selectedObject.scale.set(
          this.selectedObjectProps.scaleX,
          this.selectedObjectProps.scaleY,
          this.selectedObjectProps.scaleZ
        );
        this.selectedObject.rotation.set(
          this.selectedObjectProps.rotX,
          this.selectedObjectProps.rotY,
          this.selectedObjectProps.rotZ
        );
      }
      this.editorVisible = false;
    },
    deleteBuilding() {
      if (!this.selectedObject) {
        console.log("No object selected for deletion.");
      }
      const selectedObjectId = this.selectedObject.id;
      const rootParent = this.findRootParent(this.selectedObject);
      const rootParentId = rootParent.id;
      // 查找当前复杂模型
      const currentModelIndex = this.scene.children.findIndex(
        (item) => item.id === rootParentId
      );

      if (currentModelIndex === -1) {
        console.error("Unable to find selected object in scene.");
        return;
      }

      // 移除当前选中对象的模型
      const currentModel = this.scene.children[currentModelIndex];
      this.scene.remove(currentModel);

      // 释放几何体
      if (currentModel.geometry) {
        currentModel.geometry.dispose();
      }

      // 释放材质
      if (Array.isArray(currentModel.material)) {
        currentModel.material.forEach((material) => material.dispose());
      } else if (currentModel.material) {
        currentModel.material.dispose();
      }

      // 从对象中移除引用
      this.selectedObject = null;

      // 更新场景渲染和控制器
      this.renderer.render(this.scene, this.camera);
      this.controls.update();

      console.log("Selected object deleted:", this.selectedObject);
    },
    findRootParent(obj) {
      if (!obj.parent || obj.parent instanceof THREE.Scene) {
        // 如果当前对象没有parent属性,或者parent是场景(THREE.Scene)对象,说明已经是根节点了
        return obj;
      } else {
        // 递归调用,继续向上查找
        return this.findRootParent(obj.parent);
      }
    },
    toggleBuildingMode() {
      this.buildingMode = !this.buildingMode;
    },
    exportModelData() {
      const modelData = this.scene.children
        .filter((obj) => obj.type === "Mesh" && !obj.userData.isGround)
        .map((obj) => ({
          type: obj.geometry.type,
          position: obj.position,
          rotation: obj.rotation,
          scale: obj.scale,
          color: obj.material.color.getHex(),
        }));
      const blob = new Blob([JSON.stringify(modelData)], { type: "application/json" });
      const link = document.createElement("a");
      link.href = URL.createObjectURL(blob);
      link.download = "modelData.json";
      link.click();
    },
    importModelData(event) {
      const file = event.target.files[0];
      const reader = new FileReader();
      reader.onload = (e) => {
        const modelData = JSON.parse(e.target.result);
        this.loadModelData(modelData);
      };
      reader.readAsText(file);
    },
    loadModelData(modelData = null) {
      if (!modelData) {
        return;
      }
      modelData.forEach((data) => {
        let geometry;
        switch (data.type) {
          case "SphereGeometry":
            geometry = new THREE.SphereGeometry(0.5, 32, 32);
            break;
          case "CylinderGeometry":
            geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32);
            break;
          case "BoxGeometry":
          default:
            geometry = new THREE.BoxGeometry(1, 1, 1);
            break;
        }
        const material = new THREE.MeshStandardMaterial({ color: data.color });
        const object = new THREE.Mesh(geometry, material);
        object.position.copy(data.position);
        object.rotation.copy(data.rotation);
        object.scale.copy(data.scale);
        this.scene.add(object);
      });
    },
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialias;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

#info {
  width: 100%;
  position: absolute;
  top: 10px;
  left: 10px;
  background: rgba(255, 255, 255, 0.8);
  padding: 10px;
  border-radius: 5px;
}

#editor {
  position: absolute;
  top: 50px;
  right: 10px;
  background: rgba(255, 255, 255, 0.9);
  padding: 10px;
  border-radius: 5px;
  z-index: 1000;
  width: 200px;
}

#editor .form-group {
  margin-bottom: 10px;
}

#editor label {
  display: block;
  margin-bottom: 5px;
}

#editor input {
  width: 100%;
}
</style>

json文件

{
    "buildingName": "大楼名称",
    "position": {
        "x": 0,
        "y": 0,
        "z": 0
    },
    "floors": [
        {
            "floorNumber": 1,
            "position": {
                "x": 0,
                "y": 0,
                "z": 0
            },
            "rooms": [
                {
                    "roomNumber": 101,
                    "position": {
                        "x": 0,
                        "y": 0,
                        "z": 0
                    },
                    "objects": [
                        {
                            "type": "floor",
                            "width": 10,
                            "depth": 10,
                            "height": 0.1,
                            "position": {
                                "x": 0,
                                "y": 0,
                                "z": 0
                            },
                            "color": "#808080"
                        },
                        {
                            "type": "wall",
                            "width": 10,
                            "height": 5,
                            "depth": 0.1,
                            "position": {
                                "x": 0,
                                "y": 2.5,
                                "z": 5
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 10,
                            "height": 5,
                            "depth": 0.1,
                            "position": {
                                "x": 0,
                                "y": 2.5,
                                "z": -5
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 0.1,
                            "height": 5,
                            "depth": 10,
                            "position": {
                                "x": 5,
                                "y": 2.5,
                                "z": 0
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 0.1,
                            "height": 5,
                            "depth": 10,
                            "position": {
                                "x": -5,
                                "y": 2.5,
                                "z": 0
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "door",
                            "position": {
                                "x": 2,
                                "y": 0,
                                "z": 5.1
                            },
                            "color": "#6e4b3f"
                        },
                        {
                            "type": "window",
                            "position": {
                                "x": -2,
                                "y": 2,
                                "z": 5.1
                            },
                            "color": "#87ceeb"
                        },
                        {
                            "type": "desk",
                            "position": {
                                "x": 1,
                                "y": 0,
                                "z": 1
                            },
                            "color": "#c0c0c0"
                        },
                        {
                            "type": "chair",
                            "position": {
                                "x": -1,
                                "y": 0,
                                "z": 1
                            },
                            "color": "#808080"
                        },
                        {
                            "type": "computer",
                            "position": {
                                "x": 0,
                                "y": 0.5,
                                "z": 1
                            },
                            "color": "#0000ff"
                        },
                        {
                            "type": "camera",
                            "position": {
                                "x": 0,
                                "y": 2,
                                "z": -5
                            },
                            "color": "#ff0000"
                        }
                    ]
                }
            ]
        },
        {
            "floorNumber": 1,
            "position": {
                "x": 0,
                "y": 5,
                "z": 0
            },
            "rooms": [
                {
                    "roomNumber": 12,
                    "position": {
                        "x": 0,
                        "y": 0,
                        "z": 0
                    },
                    "objects": [
                        {
                            "type": "floor",
                            "width": 10,
                            "depth": 10,
                            "height": 0.1,
                            "position": {
                                "x": 0,
                                "y": 0,
                                "z": 0
                            },
                            "color": "#808080"
                        },
                        {
                            "type": "wall",
                            "width": 10,
                            "height": 5,
                            "depth": 0.1,
                            "position": {
                                "x": 0,
                                "y": 2.5,
                                "z": 5
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 10,
                            "height": 5,
                            "depth": 0.1,
                            "position": {
                                "x": 0,
                                "y": 2.5,
                                "z": -5
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 0.1,
                            "height": 5,
                            "depth": 10,
                            "position": {
                                "x": 5,
                                "y": 2.5,
                                "z": 0
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 0.1,
                            "height": 5,
                            "depth": 10,
                            "position": {
                                "x": -5,
                                "y": 2.5,
                                "z": 0
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "door",
                            "position": {
                                "x": 2,
                                "y": 0,
                                "z": 5.1
                            },
                            "color": "#6e4b3f"
                        },
                        {
                            "type": "window",
                            "position": {
                                "x": -2,
                                "y": 2,
                                "z": 5.1
                            },
                            "color": "#87ceeb"
                        },
                        {
                            "type": "desk",
                            "position": {
                                "x": 1,
                                "y": 0,
                                "z": 1
                            },
                            "color": "#c0c0c0"
                        },
                        {
                            "type": "chair",
                            "position": {
                                "x": -1,
                                "y": 0,
                                "z": 1
                            },
                            "color": "#808080"
                        },
                        {
                            "type": "computer",
                            "position": {
                                "x": 0,
                                "y": 0.5,
                                "z": 1
                            },
                            "color": "#0000ff"
                        },
                        {
                            "type": "camera",
                            "position": {
                                "x": 0,
                                "y": 2,
                                "z": -5
                            },
                            "color": "#ff0000"
                        }
                    ]
                }
            ]
        },
        {
            "floorNumber": 1,
            "position": {
                "x": 0,
                "y": 10,
                "z": 0
            },
            "rooms": [
                {
                    "roomNumber": 12,
                    "position": {
                        "x": 0,
                        "y": 0,
                        "z": 0
                    },
                    "objects": [
                        {
                            "type": "floor",
                            "width": 10,
                            "depth": 10,
                            "height": 0.1,
                            "position": {
                                "x": 0,
                                "y": 0,
                                "z": 0
                            },
                            "color": "#808080"
                        },
                        {
                            "type": "wall",
                            "width": 10,
                            "height": 5,
                            "depth": 0.1,
                            "position": {
                                "x": 0,
                                "y": 2.5,
                                "z": 5
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 10,
                            "height": 5,
                            "depth": 0.1,
                            "position": {
                                "x": 0,
                                "y": 2.5,
                                "z": -5
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 0.1,
                            "height": 5,
                            "depth": 10,
                            "position": {
                                "x": 5,
                                "y": 2.5,
                                "z": 0
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "wall",
                            "width": 0.1,
                            "height": 5,
                            "depth": 10,
                            "position": {
                                "x": -5,
                                "y": 2.5,
                                "z": 0
                            },
                            "color": "#ffffff"
                        },
                        {
                            "type": "door",
                            "position": {
                                "x": 2,
                                "y": 0,
                                "z": 5.1
                            },
                            "color": "#6e4b3f"
                        },
                        {
                            "type": "window",
                            "position": {
                                "x": -2,
                                "y": 2,
                                "z": 5.1
                            },
                            "color": "#87ceeb"
                        },
                        {
                            "type": "desk",
                            "position": {
                                "x": 1,
                                "y": 0,
                                "z": 1
                            },
                            "color": "#c0c0c0"
                        },
                        {
                            "type": "chair",
                            "position": {
                                "x": -1,
                                "y": 0,
                                "z": 1
                            },
                            "color": "#808080"
                        },
                        {
                            "type": "computer",
                            "position": {
                                "x": 0,
                                "y": 0.5,
                                "z": 1
                            },
                            "color": "#0000ff"
                        },
                        {
                            "type": "camera",
                            "position": {
                                "x": 0,
                                "y": 2,
                                "z": -5
                            },
                            "color": "#ff0000"
                        }
                    ]
                }
            ]
        }
    ]
}

总结

研究路线应该错了 本章内容到此结束

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苦瓜大大王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值