# 模块

## 模块常用方法

### 基础用法
```
let entity = new Entity(); // 创建一个实体
entity.addObjects(mesh); // 在实体下面增加一个mesh
entity.addTo(app); // 把实体增加至应用中
```

### 增加模块
 [addModule 或 add](https://vjmap.com/map3d/api/classes/App.html#addModule) 增加模块,如果模块已存在，则返回之前的模型，等同于 add
 ```
// 增加一个事件模块
entity.add(vjmap3d.EventModule, {
    hoverSelect: true
});
 ```

 ### 获取模块
[getModule](https://vjmap.com/map3d/api/classes/App.html#getModule) 获取模块
 ```
// 获取事件模块
let mod = entity.getModule(vjmap3d.EventModule);
if (mod) {
    // 如果模块存在，调用模块中的方法
    mod.removeEvent();
}
 ```

### 移除模块
 [removeModule](https://vjmap.com/map3d/api/classes/App.html#removeModule) 移除模块
  ```
// 移除模块
entity.removeModule(vjmap3d.EventModule);
 ```

### 移除所有模块
 [removeAllModules](https://vjmap.com/map3d/api/classes/App.html#removeAllModules) 移除所有模块
```
// 移除实体中的所有模块
entity.removeAllModules();
 ```

### 调用模块中的方法
 [callModules](https://vjmap.com/map3d/api/classes/App.html#callModules) 调用模块中的方法，实体中的所有模块，如果有这个方法，都会调用 
 ```
// 所有模块都调用应用更新函数，（如果模块中有onAppUpdate方法)
entity.callModules("onAppUpdate", time);
 ```


示例：控制网格显示或隐藏
```js
let gridHelper = app.sceneEntity.getModule(vjmap3d.GridHelperModule);
if (gridHelper) {
    gridHelper.setVisible(!gridHelper.getVisible())
}
```

<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 control = new vjmap3d.ButtonGroupControl({
    buttons: [
        {
            id: "grid",
            html: "网格隐藏/显示",
            title: "显示或隐藏坐标网格",
            style: {
                width: "100px"
            },
            onclick: async () => {
                let gridHelper = app.sceneEntity.getModule(vjmap3d.GridHelperModule);
                if (gridHelper) {
                    gridHelper.setVisible(!gridHelper.getVisible())
                }
            }
        }
    ]
});
app.addControl(control, "top-right");
</pre>
</mapFrame>


## 模块生命周期

|周期|描述|
|:---|:---|
|onAppStart|只模块初始化调用一次，调用后就需要从数组删除|
|onAppBeforeUpdate|每帧更新前运行|
|onAppUpdate|每帧更新|
|onAppAfterUpdate|每帧更新后运行|
|onAppBeforeRender|每帧渲染前运行|
|onAppRender|每帧渲染|
|onAppAfterRender|每帧渲染后运行|



## 应用模块回调事件
[App](https://vjmap.com/map3d/api/classes/App.html)  模块基类 [AppModuleBase](https://vjmap.com/map3d/api/classes/AppModuleBase.html) , 相关回调事件如下:
|事件|描述|
|:---|:---|
|onAppBeforeUpdate|每帧更新前运行|
|onAppUpdate|每帧更新|
|onAppAfterUpdate|每帧更新后运行|
|onAppBeforeRender|每帧渲染前运行|
|onAppRender|每帧渲染|
|onAppAfterRender|每帧渲染后运行|
|onAppBeforeAddEntity|实体增加前回调|
|onAppAddEntity|实体增加回调|
|onAppBeforeRemoveObject|object3d对象移除前回调|
|onAppRemoveObject|object3d对象移除回调|
|onAppBeforeRemoveEntity|实体移除前回调|
|onAppRemoveEntity|实体移除回调|


## 实体模块回调事件
[Entity](https://vjmap.com/map3d/api/classes/Entity.html)  模块基类 [EntityModuleBase](https://vjmap.com/map3d/api/classes/EntityModuleBase.html) , 相关回调事件如下:
|事件|描述|
|:---|:---|
|onAppBeforeUpdate|每帧更新前运行|
|onAppUpdate|每帧更新|
|onAppAfterUpdate|每帧更新后运行|
|onAppBeforeRender|每帧渲染前运行|
|onAppRender|每帧渲染|
|onAppAfterRender|每帧渲染后运行|
|onAppBeforeAddEntity|实体增加前回调|
|onAppAddEntity|实体增加回调|
|onAppBeforeRemoveObject|object3d对象移除前回调|
|onAppRemoveObject|object3d对象移除回调|
|onAppBeforeRemoveEntity|实体移除前回调|
|onAppRemoveEntity|实体移除回调|
|onBeforeAdd|模块被增加至实体并增加至应用前回调|
|onAdd|模块被增加至实体并增加至应用时|
|onBeforeRemove|模块开始移除前|
|onRemove|模块移除时|
|onRayIntersect|射线相关检测判断时|
|onMouseUp|鼠标抬起|
|onMouseMove|鼠标移动|
|onMouseDown|鼠标按下|
|onMouseClick|鼠标单击|
|onMouseDblClick|鼠标双击|
|onMouseOver|鼠标指针穿过被选元素或其子元素，均会触发事件|
|onMouseOut|鼠标指针离开被选元素或其子元素则触发事件|
|onMouseEnter|鼠标指针穿过被选元素时才触发事件|
|onMouseLeave|鼠标指针离开被选元素时才触发事件|
|onPointerDown|指针按下(PC和移动端)|
|onPointerMove|指针移动(PC和移动端)|
|onPointerUp|指针抬起(PC和移动端)|
|onMouseWheel|鼠标滚轮|
|onKeyDown|键盘按下|
|onKeyUp|键盘抬起|
|setupUiConfig|UI设置界面|

## 自定义模块


我们可以自定义开发一些模块的功能，把这个模块增加至相关的实体上，让实体拥有这个模块的功能。

比如要让一个实体围绕y轴旋转。可以写这么一个模块，模块需要继承于 [EntityModuleBase](https://vjmap.com/map3d/api/classes/EntityModuleBase.html)
```js
class RotateYModule extends vjmap3d.EntityModuleBase {
    onAppUpdate() {
        this.entity.node.rotateY(-0.01);
    }
}
// 当实体增加这个模块后，这个实体会自动绕y轴旋转
entity.add(RotateYModule);

setTimeout(() => {
    // 过三十秒后，移动这个模块，实体将停止绕y轴旋转
    entity.removeModule(RotateYModule);
}, 30000)
```


<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true } // 是否显示视角指示器
    }
})
//
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
    color: "#ff0",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
class RotateYModule extends vjmap3d.EntityModuleBase {
    onAppUpdate() {
       this.entity.node.rotateY(-0.01);
    }
}
// 当实体增加这个模块后，这个实体会自动绕y轴旋转
entity.add(RotateYModule);
// 
setTimeout(() => {
    // 过三十秒后，移动这个模块，实体将停止绕y轴旋转
    entity.removeModule(RotateYModule);
}, 30000)
</pre>
</mapFrame>


## 模块中事件监听

有以下两种方式：

- 通过实现基类中的事件回调函数实现
 如上例中 在 RotateYModule中实现了 `onAppUpdate` 函数，应用每帧更新回调时，会自动回调此函数

 ```
 class RotateYModule extends vjmap3d.EntityModuleBase {
    onAppUpdate() {
        this.entity.node.rotateY(-0.01);
    }
}
```


- 监听事件实现
```
 class RotateYModule extends vjmap3d.EntityModuleBase {
   constructor(entity) {
    super(entity)
    // 实体增加至app时
    entity.onEntityAwake(app => {
        // 监听onAppUpdate事件，并在模块移除的时候自动取消监听
        this.addDispose(app.signal.onAppUpdate.add(() => {
            this.entity.node.rotateY(-0.01);
        }))
    });
  }
}
```


<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true } // 是否显示视角指示器
    }
})
//
let box = new THREE.BoxGeometry(2, 2, 2)
const material = new THREE.MeshStandardMaterial({
    color: "#ff0",
    side: THREE.DoubleSide
});
let mesh = new THREE.Mesh(box, material);
let entity = vjmap3d.Entity.fromObject3d(mesh);
entity.addTo(app);
class RotateYModule extends vjmap3d.EntityModuleBase {
    constructor(entity) {
    super(entity)
    // 实体增加至app时
    entity.onEntityAwake(app => {
        // 监听onAppUpdate事件，并在模块移除的时候自动取消监听
        this.addDispose(app.signal.onAppUpdate.add(() => {
            this.entity.node.rotateY(-0.01);
        }))
    });
    }
}
// 当实体增加这个模块后，这个实体会自动绕y轴旋转
entity.add(RotateYModule);
// 
setTimeout(() => {
    // 过三十秒后，移动这个模块，实体将停止绕y轴旋转
    entity.removeModule(RotateYModule);
}, 30000)
</pre>
</mapFrame>