chenlei 1 anno fa
parent
commit
e5407fb95d

+ 2 - 0
src/models/TagModel.ts

@@ -8,6 +8,7 @@ import {
 } from "three-platformize";
 
 export interface TagModelParams {
+  name?: string;
   url: string;
   maskUrl: string;
   width: number;
@@ -72,6 +73,7 @@ export class TagModel {
     );
     this.mesh.position.copy(params.position);
     this.mesh.scale.set(params.scale, params.scale, params.scale);
+    this.mesh.name = params.name ?? "";
 
     this.show();
   }

+ 235 - 65
src/pages/tnd/index.tsx

@@ -12,14 +12,19 @@ import {
 } from "../../models";
 import {
   Clock,
+  Color,
   GammaEncoding,
   LinearFilter,
   Mesh,
   MeshStandardMaterial,
   PointLight,
+  Texture,
   Vector2,
   Vector3,
 } from "three-platformize";
+import { OutlinePass } from "three-platformize/examples/jsm/postprocessing/OutlinePass";
+import { RenderPass } from "three-platformize/examples/jsm/postprocessing/RenderPass";
+import { EffectComposer } from "three-platformize/examples/jsm/postprocessing/EffectComposer";
 import { Easing, Tween } from "@tweenjs/tween.js";
 import { cancelAnimationFrame, requestAnimationFrame } from "@tarojs/runtime";
 import { CanvasAdapter } from "../../components";
@@ -29,6 +34,8 @@ enum MODEL_STATE {
   DEFAULT = 0,
   ZOOM_UP = 1,
   ZOOM_UP_CLICK = 2,
+  CHECK_WEN = 3,
+  CHECK_HUO = 4,
 }
 
 interface TweenHandlerOptions<T extends object> {
@@ -49,8 +56,11 @@ const DENGZHAO_MIN_ROTATE = -1.2;
 const DENGZHAO_MAX_ROTATE = -2.55;
 const system = Taro.getSystemInfoSync();
 
+const meshMap = new Map<string, Texture>();
+
 const IndexPage: FC = observer(() => {
   const clock = useRef(new Clock());
+  const effectCompose = useRef<EffectComposer>();
   const bulbLight = useRef<PointLight>();
   const cameraPosition = useRef(new Vector3(0, 100, 360));
   /**
@@ -60,6 +70,7 @@ const IndexPage: FC = observer(() => {
   const [modelState, setModelState] = useState<MODEL_STATE>(
     MODEL_STATE.DEFAULT
   );
+  const _modelState = useRef<MODEL_STATE>(modelState);
   /**
    * 是否分解
    */
@@ -74,10 +85,21 @@ const IndexPage: FC = observer(() => {
     () => modelState === MODEL_STATE.ZOOM_UP_CLICK,
     [modelState]
   );
+  /**
+   * 是否展示花纹
+   */
+  const isWen = useMemo(
+    () => modelState === MODEL_STATE.CHECK_WEN,
+    [modelState]
+  );
   const hotSeparateAnimArr = useRef<ReticleModel[]>([]);
+  const hotspotAnimArr = useRef<ReticleModel[]>([]);
   const tagArr = useRef<TagModel[]>([]);
   const startMouse = useRef(new Vector2(0, 0));
   const mouseV2 = useRef(new Vector2(0, 0));
+  const wenCurStep = useRef(0);
+  const wenStep = useRef(0.01);
+  const outlinePass = useRef<OutlinePass>();
 
   useEffect(() => {
     setTimeout(async () => {
@@ -89,8 +111,13 @@ const IndexPage: FC = observer(() => {
     return renderModel.dispose;
   }, []);
 
+  useEffect(() => {
+    _modelState.current = modelState;
+  }, [modelState]);
+
   const init = async () => {
     await renderModel.init("#wgl");
+    initOutlinePass();
 
     // 创建点光源
     bulbLight.current = new PointLight(16772744, 2.5, 50, 2);
@@ -153,6 +180,8 @@ const IndexPage: FC = observer(() => {
       mesh.material.dispose();
       mesh.material = meshStandardMaterial;
       mesh.material.needsUpdate = true;
+
+      meshMap.set(mesh.name, meshStandardMaterial.map!);
     });
 
     setTimeout(() => {
@@ -170,6 +199,7 @@ const IndexPage: FC = observer(() => {
           renderModel.model?.scale.copy(new Vector3(e.x, e.x, e.x));
         },
         cb: () => {
+          handleAnimatorCallout(true);
           setTimeout(() => {
             renderModel.setAutoRotate(ROTATE_TYPE.TRUE);
           }, 800);
@@ -327,53 +357,6 @@ const IndexPage: FC = observer(() => {
   };
 
   /**
-   * 恢复模型
-   */
-  const revertModel = (cb?: Function) => {
-    const pos = renderModel.camera.position.clone();
-    const lookAt = renderModel.controls.target.clone();
-    const targetPos = new Vector3(0, 30, 372);
-    const targetLookAt = new Vector3(0, 0, 0);
-
-    tweenHandler({
-      curProps: {
-        x1: pos.x,
-        y1: pos.y,
-        z1: pos.z,
-        x2: lookAt.x,
-        y2: lookAt.y,
-        z2: lookAt.z,
-      },
-      targetProps: {
-        x1: targetPos.x,
-        y1: targetPos.y,
-        z1: targetPos.z,
-        x2: targetLookAt.x,
-        y2: targetLookAt.y,
-        z2: targetLookAt.z,
-      },
-      onUpdate: (e) => {
-        renderModel.camera.position.set(e.x1, e.y1, e.z1);
-        renderModel.controls.target.set(e.x2, e.y2, e.z2);
-        renderModel.controls.update();
-      },
-      cb,
-    });
-  };
-
-  const handleControlsDistance = (dis: number, maxDis = 470) => {
-    renderModel.controls.minDistance = dis;
-    renderModel.controls.maxDistance = maxDis;
-  };
-
-  /**
-   * 修改控制器垂直最大角度
-   */
-  const handleControlsAngle = (type: boolean) => {
-    renderModel.controls.maxPolarAngle = type ? 0.59 * Math.PI : Math.PI;
-  };
-
-  /**
    * 模型分离动画
    * @param type 是否分离
    */
@@ -407,15 +390,17 @@ const IndexPage: FC = observer(() => {
             loadCount++;
 
             if (loadCount === 5) {
-              initTag(true);
-              initReticleMeshs(true);
+              handleAnimatorCallout(false);
+              handleTag(true);
+              handleReticleMeshs(true);
               cb && cb();
             }
           },
         });
       } else {
-        initTag(false);
-        initReticleMeshs(false);
+        handleTag(false);
+        handleReticleMeshs(false);
+        handleAnimatorCallout(true);
         tweenHandler({
           curProps: item.position,
           targetProps: item._position,
@@ -425,7 +410,54 @@ const IndexPage: FC = observer(() => {
     });
   };
 
-  const initTag = (visible: boolean) => {
+  /**
+   * 恢复模型
+   */
+  const revertModel = (cb?: Function) => {
+    const pos = renderModel.camera.position.clone();
+    const lookAt = renderModel.controls.target.clone();
+    const targetPos = new Vector3(0, 30, 372);
+    const targetLookAt = new Vector3(0, 0, 0);
+
+    tweenHandler({
+      curProps: {
+        x1: pos.x,
+        y1: pos.y,
+        z1: pos.z,
+        x2: lookAt.x,
+        y2: lookAt.y,
+        z2: lookAt.z,
+      },
+      targetProps: {
+        x1: targetPos.x,
+        y1: targetPos.y,
+        z1: targetPos.z,
+        x2: targetLookAt.x,
+        y2: targetLookAt.y,
+        z2: targetLookAt.z,
+      },
+      onUpdate: (e) => {
+        renderModel.camera.position.set(e.x1, e.y1, e.z1);
+        renderModel.controls.target.set(e.x2, e.y2, e.z2);
+        renderModel.controls.update();
+      },
+      cb,
+    });
+  };
+
+  const handleControlsDistance = (dis: number, maxDis = 470) => {
+    renderModel.controls.minDistance = dis;
+    renderModel.controls.maxDistance = maxDis;
+  };
+
+  /**
+   * 修改控制器垂直最大角度
+   */
+  const handleControlsAngle = (type: boolean) => {
+    renderModel.controls.maxPolarAngle = type ? 0.59 * Math.PI : Math.PI;
+  };
+
+  const handleTag = (visible: boolean) => {
     if (!tagArr.current.length) {
       const temp: TagModel[] = [];
       const tagStack: TagModelParams[] = [
@@ -464,7 +496,7 @@ const IndexPage: FC = observer(() => {
     });
   };
 
-  const initReticleMeshs = (visible: boolean) => {
+  const handleReticleMeshs = (visible: boolean) => {
     if (!hotSeparateAnimArr.current.length) {
       const temp: ReticleModel[] = [];
       const vectorStack = [
@@ -497,6 +529,40 @@ const IndexPage: FC = observer(() => {
     });
   };
 
+  const handleAnimatorCallout = (visible: boolean) => {
+    if (!hotspotAnimArr.current.length) {
+      const hot = new ReticleModel({
+        name: "huo",
+        url: require("./resource/hot_anim.png"),
+        planeWidth: 250,
+        tilesHorizontal: 7,
+        tilesVertical: 1,
+        tileDisplayDuration: 200,
+        scale: 0.0022,
+        position: new Vector3(0, 0.2, 0.65),
+        camera: renderModel.camera,
+      });
+      const wen = new ReticleModel({
+        name: "wen",
+        url: require("./resource/wen_anim.png"),
+        planeWidth: 250,
+        tilesHorizontal: 7,
+        tilesVertical: 1,
+        tileDisplayDuration: 200,
+        scale: 0.0022,
+        position: new Vector3(0, -1, 0.65),
+        camera: renderModel.camera,
+      });
+
+      renderModel.model?.add(hot.reticleAnim, wen.reticleAnim);
+      hotspotAnimArr.current.push(hot, wen);
+    }
+
+    hotspotAnimArr.current.forEach((item) => {
+      visible ? item.show() : item.hide();
+    });
+  };
+
   const animate = () => {
     const time = clock.current.getDelta();
 
@@ -504,6 +570,28 @@ const IndexPage: FC = observer(() => {
       item.update(time);
     });
 
+    hotspotAnimArr.current.forEach((item) => {
+      item.update(time);
+    });
+
+    if (_modelState.current === MODEL_STATE.CHECK_WEN) {
+      wenCurStep.current += wenStep.current;
+
+      if (wenCurStep.current > 1) {
+        wenCurStep.current = 1;
+        wenStep.current = -0.01;
+      } else if (wenCurStep.current <= 0.1) {
+        wenCurStep.current = 0.1;
+        wenStep.current = 0.01;
+      }
+
+      renderModel.modelMaterialList.forEach((item) => {
+        item.material.opacity = wenCurStep.current;
+      });
+    }
+
+    effectCompose.current && effectCompose.current.render();
+
     requestAnimationFrame(animate);
   };
 
@@ -518,22 +606,72 @@ const IndexPage: FC = observer(() => {
       for (let i = 0; i < res.length; i++) {
         const name = res[i].object.name;
 
-        if (!name) continue;
+        if (!name || !res[i].object.visible || !res[i].object.parent?.visible)
+          continue;
 
         console.log("click model:", name);
 
-        if (
-          isSeparate &&
-          res[i].object.visible &&
-          res[i].object.parent?.visible
-        ) {
+        if (isSeparate) {
           handleModelZoomUp(name);
           break;
+        } else if (modelState === MODEL_STATE.DEFAULT) {
+          if (name === "wen") {
+            wenliHandler(true);
+            break;
+          }
         }
       }
     }
   };
 
+  const wenliHandler = (visible: boolean) => {
+    revertModel(() => {
+      if (visible) {
+        handleAnimatorCallout(false);
+        handleWenLi(true);
+        setModelState(MODEL_STATE.CHECK_WEN);
+      } else {
+        handleAnimatorCallout(true);
+        handleWenLi(false);
+        setModelState(MODEL_STATE.DEFAULT);
+      }
+    });
+  };
+
+  const handleWenLi = (visible: boolean) => {
+    const models = [
+      "tongniudengdizuo",
+      "dengzhao2",
+      "dengzhao1",
+      "dengpan",
+      "dengguan",
+    ];
+    renderModel.modelMaterialList.forEach((model) => {
+      if (!models.includes(model.name)) return;
+
+      if (visible) {
+        model.material.map = model.material.emissiveMap;
+      } else {
+        model.material.map = meshMap.get(model.name);
+        model.material.map.magFilter = LinearFilter;
+        model.material.map.minFilter = LinearFilter;
+        model.material.map.encoding = GammaEncoding;
+        model.material.opacity = 1;
+      }
+
+      model.material.dispose();
+      model.material.needsUpdate = true;
+    });
+
+    if (visible) {
+      outlinePass.current?.selectedObjects.push(
+        ...renderModel.modelMaterialList
+      );
+    } else {
+      outlinePass.current!.selectedObjects = [];
+    }
+  };
+
   /**
    * 单独展示某个模型
    */
@@ -570,8 +708,8 @@ const IndexPage: FC = observer(() => {
       });
     }
 
-    initTag(false);
-    initReticleMeshs(false);
+    handleTag(false);
+    handleReticleMeshs(false);
     tweenHandler({
       curProps: {
         x: renderModel.model!.position.x,
@@ -589,8 +727,8 @@ const IndexPage: FC = observer(() => {
           renderModel.modelMaterialList.forEach((model) => {
             model.visible = true;
           });
-          initTag(true);
-          initReticleMeshs(true);
+          handleTag(true);
+          handleReticleMeshs(true);
         }
 
         cb && cb();
@@ -608,11 +746,43 @@ const IndexPage: FC = observer(() => {
    */
   const backForSeparate = () => {
     modelSingleClick("", () => {
+      renderModel.setAutoRotate(ROTATE_TYPE.TRUE);
       renderModel.setControlsStatus(true, true, false, true, false);
       setModelState(MODEL_STATE.ZOOM_UP);
     });
   };
 
+  const initOutlinePass = () => {
+    // 创建一个EffectComposer(效果组合器)对象,然后在该对象上添加后期处理通道
+    effectCompose.current = new EffectComposer(renderModel.renderer);
+    // 新建一个场景通道  为了覆盖到原来的场景上
+    const renderPass = new RenderPass(renderModel.scene, renderModel.camera);
+    effectCompose.current.addPass(renderPass);
+    // 创建物体边缘发光通道
+    outlinePass.current = new OutlinePass(
+      new Vector2(renderModel.rect?.width, renderModel.rect?.height),
+      renderModel.scene,
+      renderModel.camera
+    );
+    outlinePass.current.usePatternTexture = false; // 是否使用父级的材质
+    outlinePass.current.edgeStrength = 2; // 边框的亮度
+    outlinePass.current.edgeGlow = 1; // 光晕
+    outlinePass.current.pulsePeriod = 0; // 呼吸闪烁的速度
+    outlinePass.current.visibleEdgeColor = new Color(1, 1, 1); // 呼吸显示的颜色
+    outlinePass.current.hiddenEdgeColor = new Color(0.1, 0.04, 0.02); // 呼吸消失的颜色
+    outlinePass.current.edgeThickness = 1; // 边框宽度
+    outlinePass.current.downSampleRatio = 2; // 边框弯曲度
+    effectCompose.current.addPass(outlinePass.current);
+  };
+
+  const handleBack = () => {
+    if (isSingleModel) {
+      backForSeparate();
+    } else if (isWen) {
+      wenliHandler(false);
+    }
+  };
+
   return (
     <View className="page">
       <CanvasAdapter
@@ -628,8 +798,8 @@ const IndexPage: FC = observer(() => {
       />
 
       <View className="toolbar">
-        {isSingleModel ? (
-          <Button className="btn" onClick={backForSeparate}>
+        {isSingleModel || isWen ? (
+          <Button className="btn" onClick={handleBack}>
             返回
           </Button>
         ) : (

BIN
src/pages/tnd/resource/hot_anim.png


BIN
src/pages/tnd/resource/wen_anim.png