# 实体常用模块

## 事件模块EventModule

事件模块[EventModule](https://vjmap.com/map3d/api/classes/EventModule.html)主要用来响应实体的`hover`和`click`事件。

### 增加事件模块
```js
// 增加一个事件模块，用来高亮提示
entity.add(vjmap3d.EventModule, {
    clickHighlight: true,
    highlightOpacity: 0.2,
    hoverSelect: true,
    hoverHtmlText: "我是MeshEntity",
    popupOptions: {
        anchor: "bottom"
    },
    clickCallback: (ent, isClick) => {
        if (!isClick) return;
        app.logInfo(`您点击了我`)
    }
});
```

### 移除事件模块
```js
entity.removeModule(vjmap3d.EventModule);
```
<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    render: { // 渲染参数
        alpha: true,
        antialias: true
    },
    scene: {  // 场景设置
        gridHelper: { visible: true } // 是否显示坐标网格
    }
})
//
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
    color: "#0f0",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
// 增加一个事件模块，用来高亮提示
entity.add(vjmap3d.EventModule, {
    clickHighlight: true,
    highlightOpacity: 0.2,
    hoverSelect: true,
    hoverHtmlText: "我是MeshEntity",
    popupOptions: {
        anchor: "bottom"
    },
    clickCallback: (ent, isClick) => {
        if (!isClick) return;
        app.logInfo(`您点击了我`)
    }
});
let control = new vjmap3d.ButtonGroupControl({
    buttons: [
        {
            id: "id1",
            html: "移除事件模块",
            title: "移除事件模块",
            style: {
                width: "130px"
            },
            onclick: async () => {
               entity.removeModule(vjmap3d.EventModule);
               app.logInfo(`移除事件后，将不会响应事件`)
            }
        }
    ]
});
app.addControl(control, "top-right");
</pre>
</mapFrame>

## 动画模块AnimatorModule
动画模块[AnimatorModule](https://vjmap.com/map3d/api/classes/AnimatorModule.html)主要用来对实体进行动画操作。

加载有动画的模型做为实体对象时，会自动给实体添加`动画模块AnimatorModule`。

```js
let ent = await vjmap3d.ResManager.loadModel(vjmap3d.ResManager.svrUrl("models/Stork.glb"), {
    splitSubEntity: true,
    scale: 0.1,
    anchor: "front-bottom-right"
});
ent.addTo(app);
let mod = ent.getModule(vjmap3d.AnimatorModule);
if (mod) {
 let animator = mod.getAnimator();
 animator.play(0) // 开启动画
 // animator.stop(0)  // 停止动画
}
```

> ⚠️ `Animator` 实例上 `isLoop()`/`setPosition(time)`/`removeClip`/`playAnimation` 均为 `undefined`（d.ts 声明存在但运行时没有——`removeClip` 宿主是另一个 `Animation` 内部类、`playAnimation` 宿主是 `SpriteAnimatorEntity`，都不在 `Animator`）——可用的剪辑/循环控制是 `addAnimationClip(clip)→AnimationClip`/`getAnimationClips()`/`setLoop`/`reset`/`stopAllAction`/`setTime`/`play(idx)`，信号袋只有 `signal.onLoop`/`signal.onFinished`。另外 `new Animator(options)` 的 `options.object` **必填**（Object3D/Entity），空 options 会抛 `Cannot read properties of undefined (reading 'isEntity')`。

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
     control:{
        initState: { // 相机初始位置 
            cameraTarget: new THREE.Vector3(-15, 0, -10),
            cameraPosition: new THREE.Vector3(-30, 30, -10)
      }
    },
    ui: {
        configPane: {
            title: "设置",
            style: { width: "240px", overflow: "hidden"}
        }
    }
})
let ent = await vjmap3d.ResManager.loadModel(vjmap3d.ResManager.svrUrl("models/Stork.glb"), {
    splitSubEntity: true,
    scale: 0.1,
    anchor: "front-bottom-right"
});
ent.addTo(app);
let mod = ent.getModule(vjmap3d.AnimatorModule);
if (mod) {
 let animator = mod.getAnimator();
 animator.play(0) // 开启动画
 // animator.stop(0)  // 停止动画
}
</pre>
</mapFrame>

增加新的动画

如：增加一个闪烁动画
```js
let mod = ent.add(vjmap3d.AnimatorModule);
let animator = mod.getAnimator();
let clip = animator.addVisibilityAnimation();
animator.play(clip);
```

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {  // 场景设置
        gridHelper: { visible: true } // 是否显示坐标网格
    }
})
//
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
    color: "#0f0",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
let mod = entity.add(vjmap3d.AnimatorModule);
let animator = mod.getAnimator();
let clip = animator.addVisibilityAnimation();
animator.play(clip);
</pre>
</mapFrame>

## 路径动画模块PathAnimateModule

路径动画模块[PathAnimateModule](https://vjmap.com/map3d/api/classes/PathAnimateModule.html)是实体沿指定路径行走的动画模块。

```js
let pathAnmiMod = ent.add(vjmap3d.PathAnimateModule)
pathAnmiMod.start({
    paths: line.data.coordinates,
    cameraFollow: true, 
    animation: {
        duration: 10000,
        yoyoForever: true
    },
    startAimatorClipName: 'Walk',
    stopAimatorClipName: ""
})
```

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
    scene: {  // 场景设置
        gridHelper: { visible: true } // 是否显示坐标网格
    }
})
let ent = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/soldier.glb", {
    splitSubEntity: true
});
app.logInfo("请在图上绘制一条要行走的路径线，双击结束", 60000)
let line = await app.actionDrawLineSting({
    isAddToApp: true
});
if (line.isCancel) return
ent.addTo(app);
let pathAnmiMod = ent.add(vjmap3d.PathAnimateModule)
pathAnmiMod.start({
    paths: line.data.coordinates,
    cameraFollow: true, 
    animation: {
        duration: 10000,
        yoyoForever: true
    },
    startAimatorClipName: 'Walk',
    stopAimatorClipName: ""
})
let miniMapControl = true; // 是否显示小地图
if (miniMapControl) {
    let miniMapControl = new vjmap3d.MiniMapControl({
        followTarget: ent.node,
        // mapSyncRotateZ: true,
        mapSize: 10,
    });
    app.addControl(miniMapControl)
}
</pre>
</mapFrame>

### 每段路径设置不同动画和速度

```js
let pathSegs = [
    {
        point: [0,0,0]
    },
    {
        point: [10,0,10],
        time: 1000
    },
    {
        point: [10, 0, 20],
        time: 2000
    },
    {
        point: [20, 0, 20],
        time: 1000
    },
    {
        point: [20, 0, 10],
        time: 3000
    },
    {
        point: [10, 0, 10],
        time: 2000
    }
]

let pathAnmiMod = ent.add(vjmap3d.PathAnimateModule)
for(let n = 1; n < pathSegs.length; n++) {
    await pathAnmiMod.start({
        paths: [pathSegs[n - 1].point, pathSegs[n].point],
        animation: {
            // @ts-ignore
            duration: pathSegs[n].time
        },
        cameraFollow: true,
        startAimatorClipName: 'Walk',
        stopAimatorClipName: n == pathSegs.length - 1 ? "" : "Walk"
    })
}
```

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
container: "map", // 容器id
    scene: {  // 场景设置
        gridHelper: { visible: true } // 是否显示坐标网格
    }
})
let ent = await vjmap3d.ResManager.loadModel(env.assetsPath + "models/soldier.glb", {
    splitSubEntity: true
});
ent.addTo(app);
let pathSegs = [
    {
        point: [0,0,0]
    },
    {
        point: [10,0,10],
        time: 1000
    },
    {
        point: [10, 0, 20],
        time: 2000
    },
    {
        point: [20, 0, 20],
        time: 1000
    },
    {
        point: [20, 0, 10],
        time: 3000
    },
    {
        point: [10, 0, 10],
        time: 2000
    }
]
// 
let pathAnmiMod = ent.add(vjmap3d.PathAnimateModule)
for(let n = 1; n < pathSegs.length; n++) {
    await pathAnmiMod.start({
        paths: [pathSegs[n - 1].point, pathSegs[n].point],
        animation: {
            // @ts-ignore
            duration: pathSegs[n].time
        },
        cameraFollow: true,
        startAimatorClipName: 'Walk',
        stopAimatorClipName: n == pathSegs.length - 1 ? "" : "Walk"
    })
}
</pre>
</mapFrame>