bill пре 1 месец
родитељ
комит
518c34a8db

+ 69 - 33
src/core/renderer-three/components/line-icon/index.vue

@@ -1,26 +1,29 @@
 <template></template>
 
 <script lang="ts" setup>
-import { getLineIconMat, getSnapLine, LineIconData } from "@/core/components/line-icon";
+import {
+  getLineIconEndpoints,
+  getSnapLine,
+  LineIconData,
+} from "@/core/components/line-icon";
 import { useRender, useStageProps, useTree } from "../../hook/use-stage";
-import { getModel } from "./resource";
-import { Box3, MathUtils, Matrix4, Object3D, Vector3 } from "three";
-import { computed, ref, watch } from "vue";
+import { fullMesh, getModel } from "./resource";
+import { Group, Matrix4 } from "three";
+import { computed, ref, watch, watchEffect } from "vue";
+import { lineCenter, lineVector, vector2IncludedAngle } from "@/utils/math";
+import { setMat } from "../../util";
 
 const props = defineProps<{ data: LineIconData }>();
 const render = useRender();
-const group = new Object3D();
-const model = ref<Object3D>();
+
+const group = new Group();
 watch(
   () => props.data.url,
   async (type, _, onCleanup) => {
     let typeModel = await getModel(type);
     if (typeModel && type === props.data.url) {
       typeModel = typeModel.clone();
-      model.value = typeModel;
       group.add(typeModel);
-      const box3 = new Box3();
-      box3.setFromObject(group);
       render();
       onCleanup(() => {
         group.remove(typeModel!);
@@ -35,39 +38,72 @@ const store = useStageProps().value.draw.store;
 const line = computed(
   () => store.getTypeItems("line")[0].lines.find((item) => item.id === props.data.lineId)!
 );
+const fullHeight = ["men_l", "yimen", "shuangkaimen", "luodichuang"];
+const fullThickness = ["men_l", "yimen", "shuangkaimen", "luodichuang"];
 
-const config = computed(() => {
-  const fullTypes = ["men_l", "piaochuang"];
-  const data = { ...props.data };
-  if (fullTypes.some((t) => data.url.includes(t))) {
-    data.type = "full";
-  }
-  const config = getLineIconMat(getSnapLine(store, data)!, data).decompose();
-  return config;
+const height = computed(() => {
+  const isFullHeight = fullHeight.some((t) => props.data.url.includes(t));
+  return isFullHeight ? sProps.value.height : sProps.value.height / 2;
+});
+const bottom = computed(
+  () => height.value / 2 + (sProps.value.height - height.value) / 2
+);
+const thickness = computed(() => {
+  const isFullThickness = fullThickness.some((t) => props.data.url.includes(t));
+  return isFullThickness ? line.value.strokeWidth : props.data.height;
 });
-const width = computed(() => Math.abs(props.data.endLen - props.data.startLen));
 const sProps = useStageProps();
-
 const mat = computed(() => {
-  return new Matrix4()
-    .makeTranslation(new Vector3(config.value.x, sProps.value.height / 2, config.value.y))
-    .multiply(new Matrix4().makeRotationY(MathUtils.degToRad(config.value.rotation)))
-    .multiply(
-      new Matrix4().makeScale(
-        width.value * config.value.scaleX,
-        sProps.value.height,
-        line.value.strokeWidth * config.value.scaleY
-      )
-    );
+  const data = props.data;
+  const snapLine = getSnapLine(store, data)!;
+  const points = getLineIconEndpoints(snapLine, data);
+  const lineRotate = vector2IncludedAngle(lineVector(points), { x: 1, y: 0 });
+  const center = lineCenter(points);
+
+  const width = Math.abs(props.data.endLen - props.data.startLen);
+  const mat = new Matrix4()
+    .makeTranslation(center.x, bottom.value, center.y)
+    .multiply(new Matrix4().makeRotationY(lineRotate));
+
+  if (props.data.openSide === "RIGHT") {
+    mat.multiply(new Matrix4().makeScale(1, 1, -1));
+  }
+  if (line.value.strokeWidth !== thickness.value) {
+    const outer = -thickness.value / 2 + line.value.strokeWidth / 2;
+    mat.multiply(new Matrix4().makeTranslation(0, 0, outer));
+  }
+
+  mat.multiply(new Matrix4().makeScale(width, height.value, thickness.value));
+  return mat;
+});
+
+watchEffect((onCleanup) => {
+  if (height.value === sProps.value.height) {
+    return;
+  }
+  const scale = bottom.value / sProps.value.height;
+  const topMesh = fullMesh.clone();
+  topMesh.scale.set(1, scale, 1);
+  topMesh.position.add({ x: 0, y: 0.75, z: 0 });
+
+  const bottomMesh = fullMesh.clone();
+  bottomMesh.scale.set(1, scale, 1);
+  bottomMesh.position.add({ x: 0, y: -0.75, z: 0 });
+
+  group.add(topMesh);
+  group.add(bottomMesh);
+  render();
+  onCleanup(() => {
+    group.remove(topMesh);
+    group.remove(bottomMesh);
+    render();
+  });
 });
 
 watch(
   mat,
   (mat) => {
-    group.matrixAutoUpdate = false;
-    group.matrix.copy(mat);
-    group.matrixWorldNeedsUpdate = true;
-
+    setMat(group, mat);
     render();
   },
   { immediate: true }

+ 62 - 10
src/core/renderer-three/components/line-icon/resource.ts

@@ -1,7 +1,19 @@
-import { Box3, Color, MeshStandardMaterial, Object3D, Vector3 } from "three";
-import { GLTFLoader } from "three/examples/jsm/Addons.js";
+import {
+  Box3,
+  BoxGeometry,
+  DoubleSide,
+  Mesh,
+  MeshPhongMaterial,
+  MeshPhysicalMaterial,
+  Object3D,
+  Vector3,
+} from "three";
+import { GLTFLoader, MTLLoader, OBJLoader } from "three/examples/jsm/Addons.js";
 
 const gltfLoader = new GLTFLoader().setPath("/static/models/");
+const objLoader = new OBJLoader().setPath("/static/models/");
+const mtlLoader = new MTLLoader().setPath("/static/models/");
+
 const normalized = async (model: Object3D) => {
   const parent = new Object3D();
   parent.add(model);
@@ -24,21 +36,55 @@ const normalized = async (model: Object3D) => {
 const resources: Record<string, () => Promise<Object3D>> = {
   "men_l.svg": async () => {
     const gltf = await gltfLoader.loadAsync("door_with_frame/scene.gltf");
+    gltf.scene.rotateY(Math.PI);
+    gltf.scene.scale.setX(-1);
     return await normalized(gltf.scene);
   },
   "piaochuang.svg": async () => {
-    const gltf = await gltfLoader.loadAsync("window/scene.gltf");
-    // gltf.scene.rotateY(Math.PI / 2);
+    const materials = await mtlLoader.loadAsync("bay_window/3d-model.mtl");
+
+    objLoader.setMaterials(materials);
+    const model = await objLoader.loadAsync("bay_window/3d-model.obj");
+    model.rotateY(Math.PI);
+
+    return await normalized(model);
+  },
+  "chuang.svg": async () => {
+    const gltf = await gltfLoader.loadAsync("window (3)/scene.gltf");
+    return await normalized(gltf.scene);
+  },
+  "yimen.svg": async () => {
+    const gltf = await gltfLoader.loadAsync("sliding_door/scene.gltf");
+    console.log(gltf.scene);
+    return await normalized(gltf.scene);
+  },
+  "shuangkaimen.svg": async () => {
+    const gltf = await gltfLoader.loadAsync(
+      "white_double_windowed_door/scene.gltf"
+    );
+    return await normalized(gltf.scene);
+  },
+  "luodichuang.svg": async () => {
+    const gltf = await gltfLoader.loadAsync("window2/scene.gltf");
     gltf.scene.traverse((node: any) => {
-      if (node.name === '02') {
-        node.parent.remove(node)
+      console.log(node.name);
+      if (node.name?.includes("glass_0")) {
+        node.material = new MeshPhysicalMaterial({
+          color: 0xeeeeee, // 浅灰色(可根据需求调整,如0xcccccc)
+          metalness: 0.1, // 轻微金属感(增强反射)
+          roughness: 0.05, // 表面光滑度(0-1,越小越光滑)
+          transmission: 0.9, // 透光率(模拟玻璃透光,需环境光遮蔽和光源支持)
+          opacity: 0.8, // 透明度(与transmission配合使用)
+          transparent: true, // 启用透明
+          side: DoubleSide, // 双面渲染(玻璃通常需要)
+          ior: 1.5, // 折射率(玻璃约为1.5)
+          envMapIntensity: 1, // 环境贴图反射强度(需设置scene.environment)
+          clearcoat: 0.1, // 可选:表面清漆层(增强反光)
+        });
       }
-    })
+    });
     return await normalized(gltf.scene);
   },
-  "chuang.svg": () => {
-    return getModel('piaochuang.svg') as Promise<Object3D>
-  }
 };
 
 export const getModel = (() => {
@@ -62,3 +108,9 @@ export const getModel = (() => {
     }
   };
 })();
+
+export const fullMesh = new Mesh(
+  new BoxGeometry(1, 1, 1),
+  new MeshPhongMaterial({ color: 0xffffff })
+);
+fullMesh.receiveShadow = fullMesh.castShadow = true;

+ 11 - 11
src/core/renderer-three/components/line/material.ts

@@ -9,17 +9,17 @@ export class StablePhongMaterial extends MeshPhongMaterial {
   constructor(options?: MeshPhongMaterialParameters) {
     super(options)
 
-    this.onBeforeCompile = shader => {
-      shader.uniforms.objectId = this.uniforms.objectId
-      shader.vertexShader = `
-        uniform float objectId;
-        ${shader.vertexShader}
-      `.replace(`#include <project_vertex>`, `
-          #include <project_vertex>
-          gl_Position.z += objectId * 0.0001;
-      `);
+    // this.onBeforeCompile = shader => {
+    //   shader.uniforms.objectId = this.uniforms.objectId
+    //   shader.vertexShader = `
+    //     uniform float objectId;
+    //     ${shader.vertexShader}
+    //   `.replace(`#include <project_vertex>`, `
+    //       #include <project_vertex>
+    //       gl_Position.z += objectId * 0.0001;
+    //   `);
 
-      this.userData.shader = shader
-    }
+    //   this.userData.shader = shader
+    // }
   }
 }

+ 3 - 4
src/core/renderer-three/components/line/single-line.vue

@@ -57,9 +57,7 @@ watch(
     });
 
     geometry.value = BufferGeometryUtils.mergeGeometries(polyGeos);
-    geometry.value
-      .rotateX(Math.PI / 2)
-      .translate(0, sProps.value.height + sProps.value.height * 0.01, 0);
+    geometry.value.rotateX(Math.PI / 2).translate(0, sProps.value.height, 0);
     polyGeos.forEach((geo) => geo.dispose());
   }),
   { immediate: true }
@@ -70,7 +68,8 @@ material.uniforms.objectId.value = props.data.lines.indexOf(props.line);
 
 const render = useRender();
 watchEffect(() => {
-  material.color = new Color(props.line.stroke);
+  // material.color = new Color(props.line.stroke);
+  material.color = new Color(0xffffff);
   render();
 });
 

+ 19 - 14
src/core/renderer-three/hook/use-stage.ts

@@ -33,13 +33,17 @@ export const useContainer = installThreeGlobalVar(() => ref<HTMLDivElement>());
 
 export const useRenderer = installThreeGlobalVar(() => {
   const container = useContainer();
-  const renderer = new WebGLRenderer({ antialias: true }) as WebGLRenderer & {
+  const renderer = new WebGLRenderer({
+    antialias: true,
+    precision: "highp", // 强制高精度着色器计算(可选:'highp'/'mediump'/'lowp')
+    powerPreference: "high-performance", // 优先使用高性能GPU模式
+  }) as WebGLRenderer & {
     bus: Emitter<{ sizeChange: void }>;
   };
 
   renderer.bus = mitt();
   renderer.shadowMap.enabled = true;
-  renderer.shadowMap.type = PCFSoftShadowMap
+  renderer.shadowMap.type = PCFSoftShadowMap;
 
   const init = (container: HTMLDivElement) => {
     container.appendChild(renderer.domElement);
@@ -67,10 +71,12 @@ export const useRenderer = installThreeGlobalVar(() => {
 });
 
 export type StageProps = {
-  draw: DrawExpose
-  height?: number
-}
-export const useStageProps = installThreeGlobalVar(() => ref() as Ref<Required<StageProps>>)
+  draw: DrawExpose;
+  height?: number;
+};
+export const useStageProps = installThreeGlobalVar(
+  () => ref() as Ref<Required<StageProps>>
+);
 
 export type Loop = (time: number) => void;
 export const useAnimationLoop = installThreeGlobalVar(() => {
@@ -109,18 +115,16 @@ export const useAnimationLoop = installThreeGlobalVar(() => {
 
 export const useCamera = installThreeGlobalVar(() => {
   const camera = new PerspectiveCamera(75, 1, 0.1, 500000);
-  camera.position.set(0, 2, 0)
-  camera.position.multiplyScalar(800)
-  // camera.position.z = 180;
-  // camera.position.y = 200;
+  camera.position.set(0, 2, 0);
+  camera.position.multiplyScalar(800);
   camera.lookAt(new Vector3(0, 1, 0));
   return camera;
 });
 
 export const useScene = installThreeGlobalVar(() => {
-  const scene = new Scene()
-  scene.background = new Color('white')
-  return scene
+  const scene = new Scene();
+  scene.background = new Color("white");
+  return scene;
 });
 
 export const useRender = installThreeGlobalVar(() => {
@@ -129,6 +133,7 @@ export const useRender = installThreeGlobalVar(() => {
   const camera = useCamera();
   const render = frameEebounce(() => renderer.render(scene, camera));
 
+  renderer.bus.on("sizeChange", render);
   const container = useContainer();
   watch(container, (container, _, onCleanup) => {
     if (container) {
@@ -187,4 +192,4 @@ export const useTree = () => {
 export const useDrawHook = <T extends () => any>(hook: T): ReturnType<T> => {
   const draw = useStageProps().value.draw;
   return draw.runHook(hook);
-}
+};

+ 7 - 0
src/core/renderer-three/util.ts

@@ -0,0 +1,7 @@
+import { Matrix4, Object3D } from "three";
+
+export const setMat = (obj: Object3D, mat: Matrix4) => {
+  obj.matrixAutoUpdate = false;
+  obj.matrix.copy(mat);
+  obj.matrixWorldNeedsUpdate = true;
+};