# 实体Entity

 实体[Entity](https://vjmap.com/map3d/api/classes/Entity.html) 是模块的容器，用于连接实体模块[EntityModuleBase](https://vjmap.com/map3d/api/classes/EntityModuleBase.html)。它和一个`Object3D`相关联，通过连接的模块功能，来实现对`Object3D`的各种操作。

 ![image-20240524093658066](https://vjmap.com/blogimages/image-20240524093658066.png)

 ## 创建新的实体

当直接`new`一个`Entity`时，会自动创建一个新的`Object3D`与之相关联
 ```js
 let entity = new Entity();
 entity.addTo(app);
 ```

 如果往此实体上面加一个子对象时，其对象树状结构如下
  ```js
 let entity = new Entity();
 entity.addTo(app);
 let child = new Object3D();
 entity.addObjects(child);

 // └─object3d <-- 实体对应的对象
 //    └─child 增加的子对象
 ```

可以在创建实体时，直接通过参数`node`指定相关联的`Object3D`
  ```js
 let obj = new Object3D(); 
 let entity = new Entity({
    node: obj
 });
 entity.addTo(app);
 ```

通过一个或多个object3d对象创建一个实体, 上面的示例也可以用下面的语句代码
```js
let obj = new Object3D(); 
let entity = Entity.fromObject3d(obj);
entity.addTo(app);
```

如果实体在场景中已存在，可通过[attchObject](https://vjmap.com/map3d/api/classes/Entity.html#attchObject)给一个已加入场景中的对象关联一个实体
```js
let obj = mesh; // 场景中的一个实体对象 
let entity = Entity.attchObject(obj); // 不用再调用 addTo(app)了
```
## 实体移除

通过[remove](https://vjmap.com/map3d/api/classes/Entity.html#remove)进行移除
```js
entity.remove();
```
## 实体属性

### 基本属性
通过实体的`node`对象可获取实体相关联的`Object3D`对象。

调用实体的`position`、`rotation`,`quaternion`,`scale`,`matrix`,`matrixWorld`,`visible`属性，等同于调用其相关联实体的相关属性。如

`entity.visible = false` 执行结果与 `entity.node.visible = false` 相同

其主要区别是通过实体设置的属性是[响应式对象](./event.md#响应式机制)，可获取此属性的响应式对象进行监听位置的变化.

```
// 监听visible响应式对象变化
vjmap3d.reactivity.watch(entity.visibleRef, (v) => app.logInfo(v ? "可见" : "隐藏", 1000))
```

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
})
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
let entity = vjmap3d.Entity.fromObject3d(cube); // 创建一个来源于object3d的实体
entity.addTo(app);// 把实体加进应用中
const ui = await app.getConfigPane(); // 获取配置面板
let cfg = entity.uiConfig; // 获取实体属性配置
if (cfg) {
    ui.appendChild(cfg);
}
</pre>
</mapFrame>

#### pointerEvents 
   设置实体是否可响应事件

#### rayIntersectRecursive
  拾取时是否遍历子对象 默认为true

#### RayQueryType
  射线查询类型

### 效果属性

- selected 是否选中
- outline 是否描边效果 (与 `selected` 效果只能二选一)
- bloom 是否辉光
- boxHelper 是否显示包围盒

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
})
const geometry = new THREE.ConeGeometry(1, 4);
const material = new THREE.MeshBasicMaterial( { color: 0x00ffff } );
const cube = new THREE.Mesh( geometry, material );
let entity = vjmap3d.Entity.fromObject3d(cube); // 创建一个来源于object3d的实体
entity.addTo(app);// 把实体加进应用中
const ui = await app.getConfigPane();
let cfg = {
    type: 'folder',
    expanded: true,
    label: "实体效果属性",
    children: vjmap3d.generateUiConfig(entity, [
        {
            prop: "selected"
        },
        {
            prop: "bloom"
        },
        {
            prop: "boxHelper"
        },
        {
            prop: "outline"
        }
    ])
}
ui.appendChild(cfg)
</pre>
</mapFrame>

如果需修改属性效果的颜色等参数，`selected`、`outline`、`bloom`属于后期处理效果，不能单独设置，需要全局统一设置。
`boxHelper`可以直接通过属性单独进行设置。

<mapFrame>
<pre>
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    postProcess: {
        bloomPass: {
            luminanceThreshold: 0.01,
            luminanceSmoothing: 0.1,
            mipmapBlur: true,
            intensity: 3.0,
        },
        outlinePass: {
            edgeStrength: 4.0,
            pulseSpeed: 0.4,
            visibleEdgeColor: 0x00ffff,
            hiddenEdgeColor: 0x634ab0,
        },
        selectedPass: {
            edgeStrength: 8.0,
            pulseSpeed: 1,
            visibleEdgeColor: 0xff0000,
            hiddenEdgeColor: 0xb0634a,
        }
    }
})
const geometry = new THREE.ConeGeometry(1, 4);
const material = new THREE.MeshBasicMaterial( { color: 0x00ffff } );
const cube = new THREE.Mesh( geometry, material );
let entity = vjmap3d.Entity.fromObject3d(cube); // 创建一个来源于object3d的实体
entity.addTo(app);// 把实体加进应用中
entity.selected = true;
entity.bloom = true; 
entity.boxHelper = {
    lineColor: "#ff0",
    fillColor: "#f0f",
    fillOpacity: 0.01
}
</pre>
</mapFrame>
   
### 设置高亮

- isHighlight 是否高亮
- setHighlight 设置高亮
- clearHighlight 清除高亮

### 替换材质

- replaceMaterial 替换材质
- resetMaterial 恢复材质

> ⚠️ **坑**：`setOpacity` 内部也走 `replaceMaterial` 的克隆路径——先 `setOpacity(a)` 再 `resetMaterial()` 恢复的是**克隆体**而非最初材质引用（对象 identity 变了）。要精确还原本引用，用纯净路径 `replaceMaterial(new) → resetMaterial()`，别混进 `setOpacity`。

### 模型边框线

- addEdges 增加模型边框线
- clearEdges 移除模型边框线
- getEdges 获取模型边框线

### 设置透明度

- setOpacity 设置透明度

### 模块
- addModule 或  add 增加模块
- getModule 获取模块
- removeModule 移除模块

通过[ResManager](https://vjmap.com/map3d/api/variables/ResManager.html)loadModel加载的资源如果模型的动画，会默认加一个动画模块[AnimatorModule](https://vjmap.com/map3d/api/classes/AnimatorModule.html)
<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);
//
const ui = await app.getConfigPane();
let cfg = [
    {
        type: 'slider',
        label: '透明度',
        value: 1.0,
        bounds: [0, 1],
        onChange: ({ value } )=> {
            ent.setOpacity(value)
        }
    },
    {
        type: 'checkbox',
        label: '开启动画',
        value: false,
        onChange: ({ value } )=> {
            let mod = ent.getModule(vjmap3d.AnimatorModule);
            if (!mod) return
            let animator = mod.getAnimator();
            if (value) { // 开启动画
                animator.play(0)
            } else { // 停止动画
                animator.stop(0)
            }
        }
    },
    {
        type: 'checkbox',
        label: '高亮',
        getValue: () => ent.isHighlight(),
        setValue: v => {
            if (v) { // 设置高亮
                ent.setHighlight("#0ff", 0.2)
            } else { // 清除高亮
                ent.clearHighlight()
            }
        }
    },
    {
        type: 'checkbox',
        label: '替换材质',
        value: false,
        onChange: ({ value } )=> {
            if (value) { // 替换材质
                const material = new THREE.MeshPhongMaterial({
                    color: '#f00'
                });
                ent.replaceMaterial(material)
            } else { // 恢复材质
                ent.resetMaterial()
            }
        }
    },
    {
        type: 'checkbox',
        label: '模型边框线',
        value: false,
        onChange: ({ value } )=> {
            if (value) { // 增加模型边框线
                ent.addEdges({
                    color: 0x00ffff,
                }, true)
            } else { // 移除模型边框线
                ent.clearEdges()
            }
        }
    }
]
cfg.forEach(c => ui.appendChild(c));
</pre>
</mapFrame>

## 常见方法

|方法名|描述|
|:---|:---|
|[create](https://vjmap.com/map3d/api/classes/Entity.html#create)|(静态方法)创建一个新的实体对象|
|[fromModule](https://vjmap.com/map3d/api/classes/Entity.html#fromModule)| (静态方法)基于模块创建一个新的实体对象|
|[fromObject3d](https://vjmap.com/map3d/api/classes/Entity.html#fromObject3d)| (静态方法)通过一个或多个object3d对象创建一个实体|
|[fromObjectModule](https://vjmap.com/map3d/api/classes/Entity.html#fromObjectModule)| (静态方法)通过一个或多个object3d对象和模块创建一个实体|
|[getOwerEntity](https://vjmap.com/map3d/api/classes/Entity.html#getOwerEntity)| (静态方法)通过一个obejct获取所属的实体|
|[onEntityAwake](https://vjmap.com/map3d/api/classes/Entity.html#onEntityAwake)|当实体加入应用app时触发，只触发一次 |
|[tween](https://vjmap.com/map3d/api/classes/Entity.html#tween)|补间动画|
|[addObjects](https://vjmap.com/map3d/api/classes/Entity.html#addObjects)|增加子对象|
|[removeObjects](https://vjmap.com/map3d/api/classes/Entity.html#removeObjects)|移除子对象|
|[addChildEntity](https://vjmap.com/map3d/api/classes/Entity.html#addChildEntity)|增加子实体|
|[removeChildEntity](https://vjmap.com/map3d/api/classes/Entity.html#removeChildEntity)|移除子实体|
|[addTo](https://vjmap.com/map3d/api/classes/Entity.html#addTo)|增加至应用app|
|[remove](https://vjmap.com/map3d/api/classes/Entity.html#remove)|移除|
|[traverse](https://vjmap.com/map3d/api/classes/Entity.html#traverse)|遍历|
|[traverseVisible](https://vjmap.com/map3d/api/classes/Entity.html#traverseVisible)|只遍历可见|
|[getObjectByName](https://vjmap.com/map3d/api/classes/Entity.html#getObjectByName)|通过名称获取对象|
|[getObjectById](https://vjmap.com/map3d/api/classes/Entity.html#getObjectById)|通过id获取对象|
|[getObjectByUuid](https://vjmap.com/map3d/api/classes/Entity.html#getObjectByUuid)|通过uuid获取对象|
|[getObjectsByName](https://vjmap.com/map3d/api/classes/Entity.html#getObjectsByName)|通过名称获取多个对象|
|[getObjectByProperty](https://vjmap.com/map3d/api/classes/Entity.html#getObjectByProperty)|通过属性获取对象|
|[getEntityByName](https://vjmap.com/map3d/api/classes/Entity.html#getEntityByName)|通过名称获取实体|
|[getEntityById](https://vjmap.com/map3d/api/classes/Entity.html#getEntityById)|通过id获取实体|
|[getEntityByUuid](https://vjmap.com/map3d/api/classes/Entity.html#getEntityByUuid)|通过uuid获取实体|
|[getMeshEntityByProperty](https://vjmap.com/map3d/api/classes/Entity.html#getMeshEntityByProperty)|通过属性获取mesh实体|
|[getMeshEntityByName](https://vjmap.com/map3d/api/classes/Entity.html#getMeshEntityByName)|通过名称获取mesh实体|
|[getMeshEntityById](https://vjmap.com/map3d/api/classes/Entity.html#getMeshEntityById)|通过id获取mesh实体|
|[getMeshEntityByUuid](https://vjmap.com/map3d/api/classes/Entity.html#getMeshEntityByUuid)|通过uuid获取mesh实体|
|[getEntitiesByName](https://vjmap.com/map3d/api/classes/Entity.html#getEntitiesByName)|通过名称获取多个实体|
|[getEntityByProperty](https://vjmap.com/map3d/api/classes/Entity.html#getEntityByProperty)|通过属性获取实体|
|[getEntitiesByProperty](https://vjmap.com/map3d/api/classes/Entity.html#getEntitiesByProperty)|通过属性获取多个实体|
|[getObjectPropertyValueCount](https://vjmap.com/map3d/api/classes/Entity.html#getObjectPropertyValueCount)|有此属性值的对象个数|
|[getEntitiesByModule](https://vjmap.com/map3d/api/classes/Entity.html#getEntitiesByModule)|通过模块获取实体|
|[isLikeMeshEntity](https://vjmap.com/map3d/api/classes/Entity.html#isLikeMeshEntity)|判断实体是否是mesh实体或包含mesh实体模块|
|[getMeshEntities](https://vjmap.com/map3d/api/classes/Entity.html#getMeshEntities)|获取所有Mesh实体
|[getLeafEntities](https://vjmap.com/map3d/api/classes/Entity.html#getLeafEntities)|获取所有叶子实体
|[getPointerEventsEntities](https://vjmap.com/map3d/api/classes/Entity.html#getPointerEventsEntities)|获取有事件的所有字实体
|[addEdges](https://vjmap.com/map3d/api/classes/Entity.html#addEdges)|增加模型边框线|
|[clearEdges](https://vjmap.com/map3d/api/classes/Entity.html#clearEdges)|移除模型边框线 |
|[replaceMaterial](https://vjmap.com/map3d/api/classes/Entity.html#replaceMaterial)|替换材质|
|[resetMaterial](https://vjmap.com/map3d/api/classes/Entity.html#resetMaterial)|恢复材质|
|[isHighlight](https://vjmap.com/map3d/api/classes/Entity.html#isHighlight)|是否高亮|
|[setHighlight](https://vjmap.com/map3d/api/classes/Entity.html#setHighlight)|设置高亮|
|[clearHighlight](https://vjmap.com/map3d/api/classes/Entity.html#clearHighlight)|清除高亮|
|[setOpacity](https://vjmap.com/map3d/api/classes/Entity.html#setOpacity)|设置透明度|
|[isFireEventsByIntersect](https://vjmap.com/map3d/api/classes/Entity.html#isFireEventsByIntersect)|根据同一个实体上的两个射线的交点信息，来决定是否是不同的事件。默认false不再触发|
|[getBoundingBox](https://vjmap.com/map3d/api/classes/Entity.html#getBoundingBox)|获取物体的外包盒|
|[addAction](https://vjmap.com/map3d/api/classes/Entity.html#addAction)|增加一个自定义动作脚本|
|[addAnimationAction](https://vjmap.com/map3d/api/classes/Entity.html#addAnimationAction)|增加一个自定义动画动作脚本|
|[removeAction](https://vjmap.com/map3d/api/classes/Entity.html#removeAction)|移除一个自定义动作脚本|

> ⚠️ **`traverseVisible` 的可见性过滤只在传了 `filter` 时生效**：`traverseVisible(cb)` 无第二参会遍历**全部**后代（含 `visible=false`），与"只遍历可见"的字面描述不符；`traverseVisible(cb, ()=>true)` 才会先按 `visible` 门控再走回调。要"仅可见且无条件"就传一个恒真 filter。

> ⚠️ **`getEntityByName`/`getEntityById`/`getEntityByUuid` 查的是底层 Object3D 属性**：实现是 `getEntityByProperty → 遍历 object.name/id/uuid → 反查实体`。给实体包装层赋 `name` 只改实体字段、**不传播**到其 node 的 `name`，查不到；要命中需设置 mesh/Object3D 的 `.name`。同理 `getObjectByUuid` 匹配的是 `object3d.uuid`。而 `getEntitiesByProperty("name", v)` 也走对象属性——实体自身字段要用别的方式筛。

> ⚠️ **本节 `getEntityByXxx`/`getObjectByXxx` 是 Entity 树上的方法，不是 App 上的**：App 实例上没有这两个方法（运行时均为 `undefined`）；要全局查找请从场景根实体（或 `app.scene` 对应的实体入口）调用，查询范围是该实体的后代子树。
